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;