From 49d1169287a678b657cb3ecd8b4eed315f2e653e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 15 Feb 2026 10:50:06 +0100 Subject: [PATCH] Introduce xcb_try_flush() 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_try_flush() without blocking. Idea and commit message by Simon Ser. I am just trying out a different implementation approach. Signed-off-by: Uli Schlachter --- src/xcb.h | 10 ++++++++++ src/xcb_conn.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/xcb_out.c | 11 +++++++++++ src/xcbint.h | 2 ++ 4 files changed, 72 insertions(+) diff --git a/src/xcb.h b/src/xcb.h index 59c779d..d9ff54f 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 Try to write any buffered output to the server. + * @param c The connection to the X server. + * @return > @c 0 on success, <= @c 0 otherwise. + * + * Writes any buffered output to the server. Does not block until the + * write is complete, but instead returns immediately with a success. + */ +int xcb_try_flush(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..63fdd11 100644 --- a/src/xcb_conn.c +++ b/src/xcb_conn.c @@ -560,6 +560,55 @@ int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vec return ret; } +int _xcb_conn_try_flush(xcb_connection_t *c) +{ + int ret; + int count; + int original_out_queue_len; + struct iovec vec; + struct iovec *vec_ptr; + + original_out_queue_len = c->out.queue_len; + if (c->out.writing || original_out_queue_len == 0) { + /* If another thread is already writing, we would have to block, waiting + * for it. If there is nothing to write, there is nothing to write. So + * just return success. + */ + return 1; + } + + c->out.queue_len = 0; + + count = 1; + vec.iov_base = c->out.queue; + vec.iov_len = original_out_queue_len; + vec_ptr = &vec; + ret = write_vec(c, &vec_ptr, &count); + + /* Remove all written data from the queue */ + if (vec.iov_len == 0) { + /* Everything was written */ + c->out.queue_len = 0; + + /* Update state on which requests are already flushed */ + c->out.request_written = c->out.request; + c->out.request_expected_written = c->in.request_expected; + } else { + /* Move the remaining data to the front of the queue */ + int remaining = vec.iov_len; + int written = original_out_queue_len - remaining; + memmove(&c->out.queue[0], &c->out.queue[written], remaining); + c->out.queue_len = remaining; + } + + /* + * No need to wake up readers since we did not read. + * No need to wake up sleeping writers since there can be none. + */ + + return ret; +} + uint64_t xcb_total_read(xcb_connection_t *c) { uint64_t n; diff --git a/src/xcb_out.c b/src/xcb_out.c index a4fe823..48fd7fb 100644 --- a/src/xcb_out.c +++ b/src/xcb_out.c @@ -429,6 +429,17 @@ int xcb_flush(xcb_connection_t *c) return ret; } +int xcb_try_flush(xcb_connection_t *c) +{ + int ret; + if(c->has_error) + return 0; + pthread_mutex_lock(&c->iolock); + ret = _xcb_conn_try_flush(c); + pthread_mutex_unlock(&c->iolock); + return ret; +} + /* Private interface */ int _xcb_out_init(_xcb_out *out) diff --git a/src/xcbint.h b/src/xcbint.h index fdb9b97..b27df4a 100644 --- a/src/xcbint.h +++ b/src/xcbint.h @@ -228,6 +228,8 @@ 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_try_flush(xcb_connection_t *c); + /* xcb_auth.c */