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 <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2026-02-15 10:50:06 +01:00
parent 93ee2ac73c
commit 49d1169287
4 changed files with 72 additions and 0 deletions

View file

@ -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.

View file

@ -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;

View file

@ -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)

View file

@ -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 */