From d074d52902633d8700ce06b484508db0f8fba02b Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 25 Sep 2021 22:34:44 -0400 Subject: 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 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 Co-authored-by: John Lindgren Co-authored-by: Sebastian Wick Signed-off-by: Manuel Stoeckl Signed-off-by: Olivier Fourdan Signed-off-by: John Lindgren Signed-off-by: Sebastian Wick Closes: https://gitlab.freedesktop.org/wayland/wayland/-/issues/237 --- tests/connection-test.c | 67 +++++++++++++++++++++++++++++++++++++++++++++--- tests/display-test.c | 9 +++++-- tests/os-wrappers-test.c | 6 +++-- 3 files changed, 75 insertions(+), 7 deletions(-) (limited to 'tests') diff --git a/tests/connection-test.c b/tests/connection-test.c index 9762e0d..dde5d89 100644 --- a/tests/connection-test.c +++ b/tests/connection-test.c @@ -50,7 +50,7 @@ setup(int *s) assert(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, s) == 0); - connection = wl_connection_create(s[0]); + connection = wl_connection_create(s[0], WL_BUFFER_DEFAULT_MAX_SIZE); assert(connection); return connection; @@ -183,9 +183,11 @@ setup_marshal_data(struct marshal_data *data) { assert(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, data->s) == 0); - data->read_connection = wl_connection_create(data->s[0]); + data->read_connection = wl_connection_create(data->s[0], + WL_BUFFER_DEFAULT_MAX_SIZE); assert(data->read_connection); - data->write_connection = wl_connection_create(data->s[1]); + data->write_connection = wl_connection_create(data->s[1], + WL_BUFFER_DEFAULT_MAX_SIZE); assert(data->write_connection); } @@ -277,6 +279,25 @@ expected_fail_marshal(int expected_error, const char *format, ...) assert(errno == expected_error); } +static void +marshal_send(struct marshal_data *data, const char *format, ...) +{ + struct wl_closure *closure; + static const uint32_t opcode = 4444; + static struct wl_object sender = { NULL, NULL, 1234 }; + struct wl_message message = { "test", format, NULL }; + va_list ap; + + va_start(ap, format); + closure = wl_closure_vmarshal(&sender, opcode, ap, &message); + va_end(ap); + + assert(closure); + assert(wl_closure_send(closure, data->write_connection) == 0); + + wl_closure_destroy(closure); +} + static void expected_fail_marshal_send(struct marshal_data *data, int expected_error, const char *format, ...) @@ -644,6 +665,46 @@ TEST(connection_marshal_too_big) free(big_string); } +TEST(connection_marshal_big_enough) +{ + struct marshal_data data; + char *big_string = malloc(5000); + + assert(big_string); + + memset(big_string, ' ', 4999); + big_string[4999] = '\0'; + + setup_marshal_data(&data); + wl_connection_set_max_buffer_size(data.write_connection, 5120); + + marshal_send(&data, "s", big_string); + + release_marshal_data(&data); + free(big_string); +} + +TEST(connection_marshal_unbounded_boundary_size) +{ + /* A string of lenth 8178 requires a buffer size of exactly 2^13. */ + struct marshal_data data; + char *big_string = malloc(8178); + assert(big_string); + + memset(big_string, ' ', 8177); + big_string[8177] = '\0'; + + setup_marshal_data(&data); + + /* Set the max size to 0 (unbounded). */ + wl_connection_set_max_buffer_size(data.write_connection, 0); + + marshal_send(&data, "s", big_string); + + release_marshal_data(&data); + free(big_string); +} + static void marshal_helper(const char *format, void *handler, ...) { diff --git a/tests/display-test.c b/tests/display-test.c index bcb3267..c2def44 100644 --- a/tests/display-test.c +++ b/tests/display-test.c @@ -1492,6 +1492,10 @@ send_overflow_client(void *data) char tmp = '\0'; int sock, optval = 16384; + /* By default, client buffers are now unbounded, set a limit to cause + * an overflow, otherwise the client buffers will grow indefinitely. */ + wl_display_set_max_buffer_size(c->wl_display, 4096); + /* Limit the send buffer size for the display socket to guarantee * that the test will cause an overflow. */ sock = wl_display_get_fd(c->wl_display); @@ -1505,6 +1509,7 @@ send_overflow_client(void *data) * within <=4096 iterations. */ for (i = 0; i < 1000000; i++) { noop_request(c); + fprintf(stderr, "Send loop %i\n", i); err = wl_display_get_error(c->wl_display); if (err) break; @@ -1514,9 +1519,9 @@ send_overflow_client(void *data) * check verifies that the initial/final FD counts are the same */ assert(write(pipes[1], &tmp, sizeof(tmp)) == (ssize_t)sizeof(tmp)); - /* Expect an error */ + /* Expect an error - ring_buffer_ensure_space() returns E2BIG */ fprintf(stderr, "Send loop failed on try %d, err = %d, %s\n", i, err, strerror(err)); - assert(err == EAGAIN); + assert(err == EAGAIN || err == E2BIG); client_disconnect_nocheck(c); } diff --git a/tests/os-wrappers-test.c b/tests/os-wrappers-test.c index 23ddced..061d29e 100644 --- a/tests/os-wrappers-test.c +++ b/tests/os-wrappers-test.c @@ -235,10 +235,12 @@ setup_marshal_data(struct marshal_data *data) assert(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, data->s) == 0); - data->read_connection = wl_connection_create(data->s[0]); + data->read_connection = wl_connection_create(data->s[0], + WL_BUFFER_DEFAULT_MAX_SIZE); assert(data->read_connection); - data->write_connection = wl_connection_create(data->s[1]); + data->write_connection = wl_connection_create(data->s[1], + WL_BUFFER_DEFAULT_MAX_SIZE); assert(data->write_connection); } -- cgit v1.2.3-70-g09d2