server: improve blocked-write behaviour for handlers
We currently don't implement POLLOUT properly; we never set this for
polled events, and will repeat calls to write() if we see EAGAIN.
This change improves the behaviour when writes start to block, by
tracking when a fd is blocked. Once we detect blocking behaviour, we
supress future (non-forced) writes, and wait for POLLOUT so we know when
we can write again.
Change-Id: I809bde4e1c7c78a58ea296d5c076b3d93c272558
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/socket-handler.c b/socket-handler.c
index cf5a2de..84cd55a 100644
--- a/socket-handler.c
+++ b/socket-handler.c
@@ -37,6 +37,7 @@
struct poller *poller;
struct ringbuffer_consumer *rbc;
int fd;
+ bool blocked;
};
struct socket_handler {
@@ -82,11 +83,30 @@
sizeof(*sh->clients) * sh->n_clients);
}
-static ssize_t send_all(int fd, void *buf, size_t len, bool block)
+static void client_set_blocked(struct client *client, bool blocked)
{
- int rc, flags;
+ int events;
+
+ if (client->blocked == blocked)
+ return;
+
+ client->blocked = blocked;
+
+ events = POLLIN;
+ if (client->blocked)
+ events |= POLLOUT;
+
+ console_poller_set_events(client->sh->console, client->poller, events);
+}
+
+static ssize_t send_all(struct client *client, void *buf,
+ size_t len, bool block)
+{
+ int fd, rc, flags;
size_t pos;
+ fd = client->fd;
+
flags = MSG_NOSIGNAL;
if (!block)
flags |= MSG_DONTWAIT;
@@ -94,8 +114,11 @@
for (pos = 0; pos < len; pos += rc) {
rc = send(fd, buf + pos, len - pos, flags);
if (rc < 0) {
- if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
+ if (!block && (errno == EAGAIN ||
+ errno == EWOULDBLOCK)) {
+ client_set_blocked(client, true);
break;
+ }
if (errno == EINTR)
continue;
@@ -123,12 +146,16 @@
wlen = 0;
block = !!force_len;
+ /* if we're already blocked, no need for the write */
+ if (!block && client->blocked)
+ return 0;
+
for (;;) {
len = ringbuffer_dequeue_peek(client->rbc, total_len, &buf);
if (!len)
break;
- wlen = send_all(client->fd, buf, len, block);
+ wlen = send_all(client, buf, len, block);
if (wlen <= 0)
break;
@@ -187,6 +214,7 @@
}
if (events & POLLOUT) {
+ client_set_blocked(client, false);
rc = client_drain_queue(client, 0);
if (rc)
goto err_close;