From 10868cecc6cabdf3db190fad7731bb84d58c62a1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Feb 2026 18:35:35 +0100 Subject: [PATCH] Introduce xcb_flush_immediately() Currently, there is no way for xcb users to flush without blocking. This results in Wayland compositors getting blocked while flushing and freezing the entire desktop. With this addition, a xcb user can poll for POLLOUT and call xcb_flush_immediately() without blocking. Signed-off-by: Simon Ser --- src/xcb.h | 10 ++++++++++ src/xcb_conn.c | 11 +++++++---- src/xcb_in.c | 10 +++++----- src/xcb_out.c | 51 ++++++++++++++++++++++++++++++++------------------ src/xcbint.h | 6 +++--- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/xcb.h b/src/xcb.h index 59c779d..53b885d 100644 --- a/src/xcb.h +++ b/src/xcb.h @@ -269,6 +269,16 @@ typedef struct xcb_auth_info_t { */ int xcb_flush(xcb_connection_t *c); +/** + * @brief Forces any buffered output to be written to the server without blocking. + * @param c The connection to the X server. + * @return > @c 0 on success, <= @c 0 otherwise. + * + * Forces any buffered output to be written to the server. Does not block, + * returns immediately with errno set to EAGAIN if the write would block. + */ +int xcb_flush_nonblock(xcb_connection_t *c); + /** * @brief Returns the maximum request length that this server accepts. * @param c The connection to the X server. diff --git a/src/xcb_conn.c b/src/xcb_conn.c index 8f91f43..98fc7fc 100644 --- a/src/xcb_conn.c +++ b/src/xcb_conn.c @@ -163,7 +163,7 @@ static int write_setup(xcb_connection_t *c, xcb_auth_info_t *auth_info) assert(count <= (int) (sizeof(parts) / sizeof(*parts))); pthread_mutex_lock(&c->iolock); - ret = _xcb_out_send(c, parts, count); + ret = _xcb_out_send(c, parts, count, 1); pthread_mutex_unlock(&c->iolock); return ret; } @@ -463,13 +463,14 @@ xcb_connection_t *_xcb_conn_ret_error(int err) } } -int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count) +int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count, int timeout) { int ret; #if USE_POLL struct pollfd fd; #else fd_set rfds, wfds; + struct timeval timeout_timeval; #endif /* If the thing I should be doing is already being done, wait for it. */ @@ -507,7 +508,7 @@ int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vec pthread_mutex_unlock(&c->iolock); do { #if USE_POLL - ret = poll(&fd, 1, -1); + ret = poll(&fd, 1, timeout); /* If poll() returns an event we didn't expect, such as POLLNVAL, treat * it as if it failed. */ if(ret >= 0 && (fd.revents & ~fd.events)) @@ -516,7 +517,9 @@ int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vec break; } #else - ret = select(c->fd + 1, &rfds, &wfds, 0, 0); + timeout_timeval.tv_sec = timeout / 1000; + timeout_timeval.tv_usec = (timeout % 1000) * 1000; + ret = select(c->fd + 1, &rfds, &wfds, 0, timeout >= 0 ? &timeout_timeval : 0); #endif } while (ret == -1 && errno == EINTR); if(ret < 0) diff --git a/src/xcb_in.c b/src/xcb_in.c index c9a264d..80e5df7 100644 --- a/src/xcb_in.c +++ b/src/xcb_in.c @@ -511,7 +511,7 @@ static void *wait_for_reply(xcb_connection_t *c, uint64_t request, xcb_generic_e void *ret = 0; /* If this request has not been written yet, write it. */ - if(c->out.return_socket || _xcb_out_flush_to(c, request)) + if(c->out.return_socket || _xcb_out_flush_to(c, request, 1)) { pthread_cond_t cond = PTHREAD_COND_INITIALIZER; reader_list reader; @@ -519,7 +519,7 @@ static void *wait_for_reply(xcb_connection_t *c, uint64_t request, xcb_generic_e insert_reader(&c->in.readers, &reader, request, &cond); while(!poll_for_reply(c, request, &ret, e)) - if(!_xcb_conn_wait(c, &cond, 0, 0)) + if(!_xcb_conn_wait(c, &cond, 0, 0, -1)) break; remove_reader(&c->in.readers, &reader); @@ -700,7 +700,7 @@ xcb_generic_event_t *xcb_wait_for_event(xcb_connection_t *c) pthread_mutex_lock(&c->iolock); /* get_event returns 0 on empty list. */ while(!(ret = get_event(c))) - if(!_xcb_conn_wait(c, &c->in.event_cond, 0, 0)) + if(!_xcb_conn_wait(c, &c->in.event_cond, 0, 0, -1)) break; _xcb_in_wake_up_next_reader(c); @@ -750,7 +750,7 @@ xcb_generic_error_t *xcb_request_check(xcb_connection_t *c, xcb_void_cookie_t co } if (XCB_SEQUENCE_COMPARE(request, >=, c->out.request_expected_written)) { - _xcb_out_flush_to(c, c->out.request); + _xcb_out_flush_to(c, c->out.request, 1); } } reply = wait_for_reply(c, request, &ret); @@ -803,7 +803,7 @@ xcb_generic_event_t *xcb_wait_for_special_event(xcb_connection_t *c, /* get_special_event returns 0 on empty list. */ while(!(event = get_special_event(c, se))) - if(!_xcb_conn_wait(c, &se->special_event_cond, 0, 0)) + if(!_xcb_conn_wait(c, &se->special_event_cond, 0, 0, -1)) break; remove_special(&c->in.special_waiters, &special); diff --git a/src/xcb_out.c b/src/xcb_out.c index a4fe823..d461879 100644 --- a/src/xcb_out.c +++ b/src/xcb_out.c @@ -30,6 +30,7 @@ #endif #include +#include #include #ifdef _WIN32 #include @@ -69,7 +70,7 @@ static inline void send_request(xcb_connection_t *c, int isvoid, enum workaround vector[0].iov_base = c->out.queue; vector[0].iov_len = c->out.queue_len; c->out.queue_len = 0; - _xcb_out_send(c, vector, count); + _xcb_out_send(c, vector, count, 1); } static void send_sync(xcb_connection_t *c) @@ -204,7 +205,7 @@ static void send_fds(xcb_connection_t *c, int *fds, unsigned int num_fds) /* XXX: if c->out.writing > 0, this releases the iolock and * potentially allows other threads to interfere with their own fds. */ - _xcb_out_flush_to(c, c->out.request); + _xcb_out_flush_to(c, c->out.request, 1); if (c->out.out_fd.nfd == XCB_MAX_PASS_FD) { /* We need some request to send FDs with */ @@ -385,7 +386,7 @@ int xcb_take_socket(xcb_connection_t *c, void (*return_socket)(void *closure), v * write requests, so keep flushing until we're done */ do - ret = _xcb_out_flush_to(c, c->out.request); + ret = _xcb_out_flush_to(c, c->out.request, 1); while (ret && c->out.request != c->out.request_written); if(ret) { @@ -413,20 +414,30 @@ int xcb_writev(xcb_connection_t *c, struct iovec *vector, int count, uint64_t re return 0; pthread_mutex_lock(&c->iolock); c->out.request += requests; - ret = _xcb_out_send(c, vector, count); + ret = _xcb_out_send(c, vector, count, 1); + pthread_mutex_unlock(&c->iolock); + return ret; +} + +static int flush(xcb_connection_t *c, int block) +{ + int ret; + if(c->has_error) + return 0; + pthread_mutex_lock(&c->iolock); + ret = _xcb_out_flush_to(c, c->out.request, block); pthread_mutex_unlock(&c->iolock); return ret; } int xcb_flush(xcb_connection_t *c) { - int ret; - if(c->has_error) - return 0; - pthread_mutex_lock(&c->iolock); - ret = _xcb_out_flush_to(c, c->out.request); - pthread_mutex_unlock(&c->iolock); - return ret; + return flush(c, 1); +} + +int xcb_flush_nonblock(xcb_connection_t *c) +{ + return flush(c, 0); } /* Private interface */ @@ -463,13 +474,17 @@ void _xcb_out_destroy(_xcb_out *out) pthread_cond_destroy(&out->socket_cond); } -int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count) +int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count, int block) { + int timeout = block ? -1 : 0; int ret = 1; - while(ret && count) - ret = _xcb_conn_wait(c, &c->out.cond, &vector, &count); - c->out.request_written = c->out.request; - c->out.request_expected_written = c->in.request_expected; + errno = 0; + while(ret && count && errno != EAGAIN) + ret = _xcb_conn_wait(c, &c->out.cond, &vector, &count, timeout); + if (count == 0) { + c->out.request_written = c->out.request; + c->out.request_expected_written = c->in.request_expected; + } pthread_cond_broadcast(&c->out.cond); _xcb_in_wake_up_next_reader(c); return ret; @@ -481,7 +496,7 @@ void _xcb_out_send_sync(xcb_connection_t *c) send_sync(c); } -int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request) +int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request, int block) { assert(XCB_SEQUENCE_COMPARE(request, <=, c->out.request)); if(XCB_SEQUENCE_COMPARE(c->out.request_written, >=, request)) @@ -492,7 +507,7 @@ int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request) vec.iov_base = c->out.queue; vec.iov_len = c->out.queue_len; c->out.queue_len = 0; - return _xcb_out_send(c, &vec, 1); + return _xcb_out_send(c, &vec, 1, block); } while(c->out.writing) pthread_cond_wait(&c->out.cond, &c->iolock); diff --git a/src/xcbint.h b/src/xcbint.h index fdb9b97..7c60458 100644 --- a/src/xcbint.h +++ b/src/xcbint.h @@ -128,9 +128,9 @@ typedef struct _xcb_out { int _xcb_out_init(_xcb_out *out); void _xcb_out_destroy(_xcb_out *out); -int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count); +int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count, int timeout); void _xcb_out_send_sync(xcb_connection_t *c); -int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request); +int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request, int timeout); /* xcb_in.c */ @@ -226,7 +226,7 @@ void _xcb_conn_shutdown(xcb_connection_t *c, int err); XCB_CONST_FUNCTION xcb_connection_t *_xcb_conn_ret_error(int err); -int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count); +int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count, int timeout); /* xcb_auth.c */