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

@ -26,6 +26,7 @@
#define _GNU_SOURCE
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
@ -55,12 +56,12 @@ div_roundup(uint32_t n, size_t a)
}
struct wl_ring_buffer {
char data[4096];
uint32_t head, tail;
char *data;
size_t head, tail;
uint32_t size_bits;
uint32_t max_size_bits; /* 0 for unlimited */
};
#define MASK(i) ((i) & 4095)
#define MAX_FDS_OUT 28
#define CLEN (CMSG_LEN(MAX_FDS_OUT * sizeof(int32_t)))
@ -71,26 +72,38 @@ struct wl_connection {
int want_flush;
};
static inline size_t
size_pot(uint32_t size_bits)
{
assert(size_bits < 8 * sizeof(size_t));
return ((size_t)1) << size_bits;
}
static size_t
ring_buffer_capacity(const struct wl_ring_buffer *b) {
return size_pot(b->size_bits);
}
static size_t
ring_buffer_mask(const struct wl_ring_buffer *b, size_t i) {
size_t m = ring_buffer_capacity(b) - 1;
return i & m;
}
static int
ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count)
{
uint32_t head, size;
if (count > sizeof(b->data)) {
wl_log("Data too big for buffer (%d > %d).\n",
count, sizeof(b->data));
errno = E2BIG;
return -1;
}
size_t head, size;
if (count == 0)
return 0;
head = MASK(b->head);
if (head + count <= sizeof b->data) {
head = ring_buffer_mask(b, b->head);
if (head + count <= ring_buffer_capacity(b)) {
memcpy(b->data + head, data, count);
} else {
size = sizeof b->data - head;
size = ring_buffer_capacity(b) - head;
memcpy(b->data + head, data, size);
memcpy(b->data, (const char *) data + size, count - size);
}
@ -103,21 +116,21 @@ ring_buffer_put(struct wl_ring_buffer *b, const void *data, size_t count)
static void
ring_buffer_put_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
{
uint32_t head, tail;
size_t head, tail;
head = MASK(b->head);
tail = MASK(b->tail);
head = ring_buffer_mask(b, b->head);
tail = ring_buffer_mask(b, b->tail);
if (head < tail) {
iov[0].iov_base = b->data + head;
iov[0].iov_len = tail - head;
*count = 1;
} else if (tail == 0) {
iov[0].iov_base = b->data + head;
iov[0].iov_len = sizeof b->data - head;
iov[0].iov_len = ring_buffer_capacity(b) - head;
*count = 1;
} else {
iov[0].iov_base = b->data + head;
iov[0].iov_len = sizeof b->data - head;
iov[0].iov_len = ring_buffer_capacity(b) - head;
iov[1].iov_base = b->data;
iov[1].iov_len = tail;
*count = 2;
@ -127,21 +140,21 @@ ring_buffer_put_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
static void
ring_buffer_get_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
{
uint32_t head, tail;
size_t head, tail;
head = MASK(b->head);
tail = MASK(b->tail);
head = ring_buffer_mask(b, b->head);
tail = ring_buffer_mask(b, b->tail);
if (tail < head) {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = head - tail;
*count = 1;
} else if (head == 0) {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = sizeof b->data - tail;
iov[0].iov_len = ring_buffer_capacity(b) - tail;
*count = 1;
} else {
iov[0].iov_base = b->data + tail;
iov[0].iov_len = sizeof b->data - tail;
iov[0].iov_len = ring_buffer_capacity(b) - tail;
iov[1].iov_base = b->data;
iov[1].iov_len = head;
*count = 2;
@ -151,29 +164,158 @@ ring_buffer_get_iov(struct wl_ring_buffer *b, struct iovec *iov, int *count)
static void
ring_buffer_copy(struct wl_ring_buffer *b, void *data, size_t count)
{
uint32_t tail, size;
size_t tail, size;
if (count == 0)
return;
tail = MASK(b->tail);
if (tail + count <= sizeof b->data) {
tail = ring_buffer_mask(b, b->tail);
if (tail + count <= ring_buffer_capacity(b)) {
memcpy(data, b->data + tail, count);
} else {
size = sizeof b->data - tail;
size = ring_buffer_capacity(b) - tail;
memcpy(data, b->data + tail, size);
memcpy((char *) data + size, b->data, count - size);
}
}
static uint32_t
static size_t
ring_buffer_size(struct wl_ring_buffer *b)
{
return b->head - b->tail;
}
static char *
ring_buffer_tail(const struct wl_ring_buffer *b)
{
return b->data + ring_buffer_mask(b, b->tail);
}
static uint32_t
get_max_size_bits_for_size(size_t buffer_size)
{
uint32_t max_size_bits = WL_BUFFER_DEFAULT_SIZE_POT;
/* buffer_size == 0 means unbound buffer size */
if (buffer_size == 0)
return 0;
while (max_size_bits < 8 * sizeof(size_t) && size_pot(max_size_bits) < buffer_size)
max_size_bits++;
return max_size_bits;
}
static int
ring_buffer_allocate(struct wl_ring_buffer *b, size_t size_bits)
{
char *new_data;
new_data = calloc(size_pot(size_bits), 1);
if (!new_data)
return -1;
ring_buffer_copy(b, new_data, ring_buffer_size(b));
free(b->data);
b->data = new_data;
b->size_bits = size_bits;
b->head = ring_buffer_size(b);
b->tail = 0;
return 0;
}
static size_t
ring_buffer_get_bits_for_size(struct wl_ring_buffer *b, size_t net_size)
{
size_t max_size_bits = get_max_size_bits_for_size(net_size);
if (max_size_bits < WL_BUFFER_DEFAULT_SIZE_POT)
max_size_bits = WL_BUFFER_DEFAULT_SIZE_POT;
if (b->max_size_bits > 0 && max_size_bits > b->max_size_bits)
max_size_bits = b->max_size_bits;
return max_size_bits;
}
static bool
ring_buffer_is_max_size_reached(struct wl_ring_buffer *b)
{
size_t net_size = ring_buffer_size(b) + 1;
size_t size_bits = ring_buffer_get_bits_for_size(b, net_size);
return net_size >= size_pot(size_bits);
}
static int
ring_buffer_ensure_space(struct wl_ring_buffer *b, size_t count)
{
size_t net_size = ring_buffer_size(b) + count;
size_t size_bits = ring_buffer_get_bits_for_size(b, net_size);
/* The 'size_bits' value represents the required size (in POT) to store
* 'net_size', which depending whether the buffers are bounded or not
* might not be sufficient (i.e. we might have reached the maximum size
* allowed).
*/
if (net_size > size_pot(size_bits)) {
wl_log("Data too big for buffer (%d + %zd > %zd).\n",
ring_buffer_size(b), count, size_pot(size_bits));
errno = E2BIG;
return -1;
}
/* The following test here is a short-cut to avoid reallocating a buffer
* of the same size.
*/
if (size_bits == b->size_bits)
return 0;
/* Otherwise, we (re)allocate the buffer to match the required size */
return ring_buffer_allocate(b, size_bits);
}
static void
ring_buffer_close_fds(struct wl_ring_buffer *buffer, int32_t count)
{
int32_t i, *p;
size_t size, tail;
size = ring_buffer_capacity(buffer);
tail = ring_buffer_mask(buffer, buffer->tail);
p = (int32_t *) (buffer->data + tail);
for (i = 0; i < count; i++) {
if (p >= (int32_t *) (buffer->data + size))
p = (int32_t *) buffer->data;
close(*p++);
}
}
void
wl_connection_set_max_buffer_size(struct wl_connection *connection,
size_t max_buffer_size)
{
uint32_t max_size_bits;
max_size_bits = get_max_size_bits_for_size(max_buffer_size);
connection->fds_in.max_size_bits = max_size_bits;
ring_buffer_ensure_space(&connection->fds_in, 0);
connection->fds_out.max_size_bits = max_size_bits;
ring_buffer_ensure_space(&connection->fds_out, 0);
connection->in.max_size_bits = max_size_bits;
ring_buffer_ensure_space(&connection->in, 0);
connection->out.max_size_bits = max_size_bits;
ring_buffer_ensure_space(&connection->out, 0);
}
struct wl_connection *
wl_connection_create(int fd)
wl_connection_create(int fd, size_t max_buffer_size)
{
struct wl_connection *connection;
@ -181,6 +323,8 @@ wl_connection_create(int fd)
if (connection == NULL)
return NULL;
wl_connection_set_max_buffer_size(connection, max_buffer_size);
connection->fd = fd;
return connection;
@ -189,20 +333,20 @@ wl_connection_create(int fd)
static void
close_fds(struct wl_ring_buffer *buffer, int max)
{
int32_t fds[sizeof(buffer->data) / sizeof(int32_t)], i, count;
size_t size;
int32_t count;
size = ring_buffer_size(buffer);
if (size == 0)
return;
ring_buffer_copy(buffer, fds, size);
count = size / sizeof fds[0];
count = size / sizeof(int32_t);
if (max > 0 && max < count)
count = max;
size = count * sizeof fds[0];
for (i = 0; i < count; i++)
close(fds[i]);
ring_buffer_close_fds(buffer, count);
size = count * sizeof(int32_t);
buffer->tail += size;
}
@ -218,7 +362,13 @@ wl_connection_destroy(struct wl_connection *connection)
int fd = connection->fd;
close_fds(&connection->fds_out, -1);
free(connection->fds_out.data);
free(connection->out.data);
close_fds(&connection->fds_in, -1);
free(connection->fds_in.data);
free(connection->in.data);
free(connection);
return fd;
@ -262,7 +412,7 @@ static int
decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg)
{
struct cmsghdr *cmsg;
size_t size, max, i;
size_t size, i;
int overflow = 0;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL;
@ -272,8 +422,8 @@ decode_cmsg(struct wl_ring_buffer *buffer, struct msghdr *msg)
continue;
size = cmsg->cmsg_len - CMSG_LEN(0);
max = sizeof(buffer->data) - ring_buffer_size(buffer);
if (size > max || overflow) {
if (ring_buffer_ensure_space(buffer, size) < 0 || overflow) {
overflow = 1;
size /= sizeof(int32_t);
for (i = 0; i < size; i++)
@ -299,17 +449,40 @@ wl_connection_flush(struct wl_connection *connection)
char cmsg[CLEN];
int len = 0, count;
size_t clen;
uint32_t tail;
size_t tail;
if (!connection->want_flush)
return 0;
tail = connection->out.tail;
while (connection->out.head - connection->out.tail > 0) {
ring_buffer_get_iov(&connection->out, iov, &count);
while (ring_buffer_size(&connection->out) > 0) {
build_cmsg(&connection->fds_out, cmsg, &clen);
if (clen >= CLEN) {
/* UNIX domain sockets allows to send file descriptors
* using ancillary data.
*
* As per the UNIX domain sockets man page (man 7 unix),
* "at least one byte of real data should be sent when
* sending ancillary data".
*
* This is why we send only a single byte here, to ensure
* all file descriptors are sent before the bytes are
* cleared out.
*
* Otherwise This can fail to clear the file descriptors
* first if individual messages are allowed to have 224
* (8 bytes * MAX_FDS_OUT = 224) file descriptors .
*/
iov[0].iov_base = ring_buffer_tail(&connection->out);
iov[0].iov_len = 1;
count = 1;
} else {
ring_buffer_get_iov(&connection->out, iov, &count);
}
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = count;
msg.msg_control = (clen > 0) ? cmsg : NULL;
@ -347,35 +520,48 @@ wl_connection_read(struct wl_connection *connection)
char cmsg[CLEN];
int len, count, ret;
if (ring_buffer_size(&connection->in) >= sizeof(connection->in.data)) {
errno = EOVERFLOW;
return -1;
while (1) {
int data_size = ring_buffer_size(&connection->in);
/* Stop once we've read the max buffer size. */
if (ring_buffer_is_max_size_reached(&connection->in))
return data_size;
if (ring_buffer_ensure_space(&connection->in, 1) < 0)
return -1;
ring_buffer_put_iov(&connection->in, iov, &count);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = count;
msg.msg_control = cmsg;
msg.msg_controllen = sizeof cmsg;
msg.msg_flags = 0;
do {
len = wl_os_recvmsg_cloexec(connection->fd, &msg, MSG_DONTWAIT);
} while (len < 0 && errno == EINTR);
if (len == 0) {
/* EOF, return previously read data first */
return data_size;
}
if (len < 0) {
if (errno == EAGAIN && data_size > 0) {
/* nothing new read, return previously read data */
return data_size;
}
return len;
}
ret = decode_cmsg(&connection->fds_in, &msg);
if (ret)
return -1;
connection->in.head += len;
}
ring_buffer_put_iov(&connection->in, iov, &count);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = count;
msg.msg_control = cmsg;
msg.msg_controllen = sizeof cmsg;
msg.msg_flags = 0;
do {
len = wl_os_recvmsg_cloexec(connection->fd, &msg, MSG_DONTWAIT);
} while (len < 0 && errno == EINTR);
if (len <= 0)
return len;
ret = decode_cmsg(&connection->fds_in, &msg);
if (ret)
return -1;
connection->in.head += len;
return wl_connection_pending_input(connection);
}
int
@ -394,13 +580,23 @@ int
wl_connection_queue(struct wl_connection *connection,
const void *data, size_t count)
{
if (connection->out.head - connection->out.tail +
count > ARRAY_LENGTH(connection->out.data)) {
/* We want to try to flush when the buffer reaches the default maximum
* size even if the buffer has been previously expanded.
*
* Otherwise the larger buffer will cause us to flush less frequently,
* which could increase lag.
*
* We'd like to flush often and get the buffer size back down if possible.
*/
if (ring_buffer_size(&connection->out) + count > WL_BUFFER_DEFAULT_MAX_SIZE) {
connection->want_flush = 1;
if (wl_connection_flush(connection) < 0)
if (wl_connection_flush(connection) < 0 && errno != EAGAIN)
return -1;
}
if (ring_buffer_ensure_space(&connection->out, count) < 0)
return -1;
return ring_buffer_put(&connection->out, data, count);
}
@ -426,12 +622,15 @@ wl_connection_get_fd(struct wl_connection *connection)
static int
wl_connection_put_fd(struct wl_connection *connection, int32_t fd)
{
if (ring_buffer_size(&connection->fds_out) == MAX_FDS_OUT * sizeof fd) {
if (ring_buffer_size(&connection->fds_out) >= MAX_FDS_OUT * sizeof fd) {
connection->want_flush = 1;
if (wl_connection_flush(connection) < 0)
if (wl_connection_flush(connection) < 0 && errno != EAGAIN)
return -1;
}
if (ring_buffer_ensure_space(&connection->fds_out, sizeof fd) < 0)
return -1;
return ring_buffer_put(&connection->fds_out, &fd, sizeof fd);
}

View file

@ -298,6 +298,10 @@ wl_display_read_events(struct wl_display *display);
void
wl_log_set_handler_client(wl_log_func_t handler);
void
wl_display_set_max_buffer_size(struct wl_display *display,
size_t max_buffer_size);
#ifdef __cplusplus
}
#endif

View file

@ -1269,7 +1269,7 @@ wl_display_connect_to_fd(int fd)
*/
display->proxy.version = 0;
display->connection = wl_connection_create(display->fd);
display->connection = wl_connection_create(display->fd, 0);
if (display->connection == NULL)
goto err_connection;
@ -2238,6 +2238,32 @@ wl_display_flush(struct wl_display *display)
return ret;
}
/** Adjust the maximum size of the client connection buffers
*
* \param display The display context object
* \param max_buffer_size The maximum size of the connection buffers
*
* Client buffers are unbounded by default. This function sets a limit to the
* size of the connection buffers.
*
* A value of 0 for \a max_buffer_size requests the buffers to be unbounded.
*
* 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.
*
* \memberof wl_display
* \since 1.22.90
*/
WL_EXPORT void
wl_display_set_max_buffer_size(struct wl_display *display,
size_t max_buffer_size)
{
wl_connection_set_max_buffer_size(display->connection, max_buffer_size);
}
/** Set the user data associated with a proxy
*
* \param proxy The proxy object

View file

@ -47,6 +47,8 @@
#define WL_SERVER_ID_START 0xff000000
#define WL_MAP_MAX_OBJECTS 0x00f00000
#define WL_CLOSURE_MAX_ARGS 20
#define WL_BUFFER_DEFAULT_SIZE_POT 12
#define WL_BUFFER_DEFAULT_MAX_SIZE (1 << WL_BUFFER_DEFAULT_SIZE_POT)
/**
* Argument types used in signatures.
@ -120,7 +122,7 @@ void
wl_map_for_each(struct wl_map *map, wl_iterator_func_t func, void *data);
struct wl_connection *
wl_connection_create(int fd);
wl_connection_create(int fd, size_t max_buffer_size);
int
wl_connection_destroy(struct wl_connection *connection);
@ -252,4 +254,8 @@ zalloc(size_t s)
void
wl_connection_close_fds_in(struct wl_connection *connection, int max);
void
wl_connection_set_max_buffer_size(struct wl_connection *connection,
size_t max_buffer_size);
#endif

View file

@ -217,6 +217,10 @@ wl_display_flush_clients(struct wl_display *display);
void
wl_display_destroy_clients(struct wl_display *display);
void
wl_display_set_default_max_buffer_size(struct wl_display *display,
size_t max_buffer_size);
struct wl_client;
typedef void (*wl_global_bind_func_t)(struct wl_client *client, void *data,
@ -375,6 +379,9 @@ wl_client_set_user_data(struct wl_client *client,
void *
wl_client_get_user_data(struct wl_client *client);
void
wl_client_set_max_buffer_size(struct wl_client *client, size_t max_buffer_size);
/** \class wl_listener
*
* \brief A single listener for Wayland signals

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

View file

@ -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, ...)
{

View file

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

View file

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