connection: Dynamically resize connection buffers

When using fixed size connection buffers, if either the client or the
server is sending requests faster than the other end can cope with, the
connection buffers will fill up, eventually killing the connection.

This can be a problem for example with Xwayland mapping a lot of
windows, faster than the Wayland compositor can cope with, or a
high-rate mouse flooding the Wayland client with pointer events.

To avoid the issue, resize the connection buffers dynamically when they
get full.

Both data and fd buffers are resized on demand.

The default max buffer size is controlled via the wl_display interface
while each client's connection buffer size is adjustable for finer
control.

The purpose is to explicitly have larger connection buffers for specific
clients such as Xwayland, or set a larger buffer size for the client
with pointer focus to deal with a higher input events rate.

v0: Manuel:
   Dynamically resize connection buffers - Both data and fd buffers are
   resized on demand.
v1: Olivier
1. Add support for unbounded buffers on the client side and growable
   (yet limited) connection buffers on the server side.
2. Add the API to set the default maximum size and a limit for a given
   client.
3. Add tests for growable connection buffers and adjustable limits.
v2: Additional fixes by John:
1. Fix the size calculation in ring_buffer_check_space()
2. Fix wl_connection_read() to return gracefully once it has read up to
   the max buffer size, rather than returning an error.
3. If wl_connection_flush() fails with EAGAIN but the transmit
   ring-buffer has space remaining (or can be expanded),
   wl_connection_queue() should store the message rather than
   returning an error.
4. When the receive ring-buffer is at capacity but more data is
   available to be read, wl_connection_read() should attempt to
   expand the ring-buffer in order to read the remaining data.
v3: Thomas Lukaszewicz <tluk@chromium.org>
   Add a test for unbounded buffers
v4: Add a client API as well to force bounded buffers (unbounded
    by default (Olivier)
v5: Simplify ring_buffer_ensure_space() (Sebastian)

Co-authored-by: Olivier Fourdan <ofourdan@redhat.com>
Co-authored-by: John Lindgren <john@jlindgren.net>
Co-authored-by: Sebastian Wick <sebastian@sebastianwick.net>
Signed-off-by: Manuel Stoeckl <code@mstoeckl.com>
Signed-off-by: Olivier Fourdan <ofourdan@redhat.com>
Signed-off-by: John Lindgren <john@jlindgren.net>
Signed-off-by: Sebastian Wick <sebastian@sebastianwick.net>
Closes: https://gitlab.freedesktop.org/wayland/wayland/-/issues/237
This commit is contained in:
Manuel Stoeckl 2021-09-25 22:34:44 -04:00 committed by Simon Ser
parent 36cef8653f
commit d074d52902
9 changed files with 460 additions and 87 deletions

View file

@ -112,6 +112,8 @@ struct wl_display {
int terminate_efd;
struct wl_event_source *term_source;
size_t max_buffer_size;
};
struct wl_global {
@ -542,7 +544,8 @@ wl_client_create(struct wl_display *display, int fd)
&client->pid) != 0)
goto err_source;
client->connection = wl_connection_create(fd);
client->connection = wl_connection_create(fd, display->max_buffer_size);
if (client->connection == NULL)
goto err_source;
@ -1172,6 +1175,7 @@ wl_display_create(void)
display->global_filter = NULL;
display->global_filter_data = NULL;
display->max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE;
wl_array_init(&display->additional_shm_formats);
@ -1580,6 +1584,37 @@ wl_display_destroy_clients(struct wl_display *display)
}
}
/** Sets the default maximum size for connection buffers of new clients
*
* \param display The display object
* \param max_buffer_size The default maximum size of the connection buffers
*
* This function sets the default size of the internal connection buffers for
* new clients. It doesn't change the buffer size for existing wl_client.
*
* The connection buffer size of an existing wl_client can be adjusted using
* wl_client_set_max_buffer_size().
*
* The actual size of the connection buffers is a power of two, the requested
* \a max_buffer_size is therefore rounded up to the nearest power of two value.
*
* The minimum buffer size is 4096.
*
* \sa wl_client_set_max_buffer_size
*
* \memberof wl_display
* \since 1.22.90
*/
WL_EXPORT void
wl_display_set_default_max_buffer_size(struct wl_display *display,
size_t max_buffer_size)
{
if (max_buffer_size < WL_BUFFER_DEFAULT_MAX_SIZE)
max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE;
display->max_buffer_size = max_buffer_size;
}
static int
socket_data(int fd, uint32_t mask, void *data)
{
@ -2275,6 +2310,34 @@ wl_signal_emit_mutable(struct wl_signal *signal, void *data)
wl_list_remove(&end.link);
}
/** Adjust the maximum size of the client connection buffers
*
* \param client The client object
* \param max_buffer_size The maximum size of the connection buffers
*
* The actual size of the connection buffers is a power of two, the requested
* \a max_buffer_size is therefore rounded up to the nearest power of two value.
*
* Lowering the maximum size may not take effect immediately if the current content
* of the buffer does not fit within the new size limit.
*
* The minimum buffer size is 4096. The default buffers size can be set using
* wl_display_set_default_max_buffer_size().
*
* \sa wl_display_set_default_max_buffer_size()
*
* \memberof wl_client
* \since 1.22.90
*/
WL_EXPORT void
wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size)
{
if (max_buffer_size < WL_BUFFER_DEFAULT_MAX_SIZE)
max_buffer_size = WL_BUFFER_DEFAULT_MAX_SIZE;
wl_connection_set_max_buffer_size(client->connection, max_buffer_size);
}
/** \cond INTERNAL */
/** Initialize a wl_priv_signal object