[Xcb] Help with external socket handling and xcb

Uli Schlachter psychon at znc.in
Wed Sep 8 15:58:39 UTC 2021


Am 08.09.21 um 15:29 schrieb Carlo Wood:
> Unfortunately, not many libraries that provide protocol handling
> provide such an API - most of them insist on also handling the socket
> side of things; and as far as I can tell xcb is like that too.

This might be a bit off-topic, but how would such a library be
thread-safe? xcb does quite a dance internally to provide a thread-safe
API and hide all these implementation details.

For example, xcb_get_input_focus_reply() blocks until the reply was
received from the X11 server (and does so in a way that does not block
other threads from using the X11 connection). Sure, you can use
xcb_poll_for_reply() to check whether the reply was received yet, but
going down this route results in quite spaghetti-y code.

> Using xcb_get_file_descriptor I can start monitoring it for input
> (aka, a function is called whenever the socket is readable).
> At this point I NEED a substantial amount of data to be read
> from the socket (preferably everything, so that epoll won't report
> it being readable again). I understand that this is possible by
> calling xcb_poll_for_event until it returns NULL. Again: I'd rather
> do the reading and buffering of the data myself and then feed
> that to xcb to decode it; but this will work... or does it?

Well, there is no public API for this. Internally, _xcb_in_read() reads
from the socket into a buffer and then reads individual packets from
this via read_packet(). However, inside a packet, the remainder of a
packet is again read blockingly (see call to _xcb_in_read_block()). The
expectation here is that the X11 server will write the whole packet in
one go.

This is necessary because X11 packets can be quite large, certainly
larger than the 4 KiB that is the default read buffer size.

Also: Your approach would mean that the data would have to be copied
again. In some cases (the _xcb_in_read_block() call I mention above),
libxcb can read the data directly into its final place; avoiding an
extra copy.

> Because I assume that xcb internally will keep reading the socket
> until that returns that there is no data left to read. And in order
> for that not to be blocking, the fd has to be non-blocking (which
> is obviously what I normally want).

xcb actually calls poll()/select() before doing anything with the socket
to make sure that following call will not block "too badly". Anything
else would again interfere with thread-safety. However, functions like
xcb_wait_for_reply() still call this code in a loop until they are done
with what they want to do.

I don't really see how this could be done with an external "loop
driver". Every function returning some kind of EAGAIN? I wouldn't want
to try to use such an API.

> However, doesn't there need to be data written to the socket too?
> When does that happen?

Whenever it needs to be. There is a buffer for outgoing data. When that
is full, the current call blocks until the buffered data was written.
Also, xcb_flush() will, well, flush the buffer.

So basically: Any function that sends a request can block waiting for
some data to be written.

> With the current set up I can't monitor the
> socket to see if it writable because it is ALWAYS writable. I can
> only do this when xcb tells me (through a callback) that it NEEDS
> me to monitor the socket because it has something to write (and
> also tells me when I can stop monitoring the fd again).

What would you want a function like xcb_get_input_focus() to return when
it needs to write some data, but the buffer is currently full?

> So, since it doesn't do that (as far as I know), it will have to
> make sure that every time it has something to write, it writes
> ALL of it before returning to my code... and that sounds very
> blocking. How can this ever work reliable with a non-blocking
> fd? Or is xcb just calling write in a loop until it wrote everything
> it has to write (also when write returns EAGAIN)?

Basically yes. _xcb_conn_wait() calls write_vec() to write some data
whenever poll()/select() say that the FD is writeable and there is some
pending data to send. Here is the loop that then calls _xcb_conn_wait()
until everything was written:

> If anyone can give me pointers, or maybe tell me about a part of
> the API that I missed that is suitable for this, that would be great!

For non-blockingly waiting for a reply to a request: Do you know about
xcb_poll_for_reply()? There is an example on how to use that to get a
callback-based "your reply arrived"-API here:
(Hm, it seems like this code does not handle sequence number overflows...)

I hope some of this helps.

If you have to type the letters "A-E-S" into your source code, you're
doing it wrong.

More information about the Xcb mailing list