Handle write() returning EAGAIN by sleeping and retrying.
authorEmil Mikulic <emikulic@gmail.com>
Sun, 10 Mar 2013 16:57:56 +0000 (03:57 +1100)
committerEmil Mikulic <emikulic@gmail.com>
Sun, 10 Mar 2013 17:26:25 +0000 (04:26 +1100)
Before this change, flow control on the tty caused the entire
program to exit.  After the change, we survive fine, and continue to
write to other file descriptors while the tty is blocked.

buftee.c

index 77cba3a..feb213b 100644 (file)
--- a/buftee.c
+++ b/buftee.c
@@ -215,31 +215,54 @@ static int xread(const int fd, char* const restrict buf, const int count) {
   return (int)read_ret;
 }
 
+static void wait_until_writable(const int fd) {
+  fd_set write_fds;
+  FD_ZERO(&write_fds);
+  FD_SET(fd, &write_fds);
+  int select_ret = select(fd + 1, NULL, &write_fds, NULL, NULL);
+  if (select_ret == -1) {
+    if (errno == EINTR) {
+      assert(stopping);  // that should have been SIGTERM
+      return;
+    }
+    err(1, "select(write fd = %d) failed", fd);
+  }
+  if (!FD_ISSET(fd, &write_fds))
+    errx(1, "select() did not return writable fd = %d", fd);
+}
+
 static int xwrite(const int fd, struct buf* const buf) {
   ssize_t write_ret;
   int saved_errno;
   struct timespec t0;
   struct timespec t1;
 
-  get_mono_time(&t0);
-  write_ret = write(fd, buf->data, (size_t)buf->len);
-  saved_errno = errno;
-  get_mono_time(&t1);
-  warn_time("write()", &t0, &t1);
-
-  errno = saved_errno;
-  if (write_ret == -1)
-    err(1, "write(fd = %d, count = %d) failed", fd, buf->len);
-  //FIXME: EAGAIN?
-  if (write_ret == 0)
-    return 0;
-  assert(write_ret >= 0);
-  if (write_ret < buf->len)
-    err(1, "write(fd = %d, count = %d) stopped short (returned %d)",
-        fd, buf->len, (int)write_ret);
-    // FIXME: handle this
-  assert(write_ret == buf->len);
-  return (int)write_ret;
+  for (;;) {
+    get_mono_time(&t0);
+    write_ret = write(fd, buf->data, (size_t)buf->len);
+    saved_errno = errno;
+    get_mono_time(&t1);
+    warn_time("write()", &t0, &t1);
+
+    errno = saved_errno;
+    if (write_ret == -1) {
+      if (errno == EAGAIN) {
+        warn("write(fd = %d) got EAGAIN, sleeping and retrying", fd);
+        wait_until_writable(fd);
+        continue;
+      }
+      err(1, "write(fd = %d, count = %d) failed", fd, buf->len);
+    }
+    if (write_ret == 0)
+      return 0;  // EOF
+    assert(write_ret >= 0);
+    if (write_ret < buf->len)
+      err(1, "write(fd = %d, count = %d) stopped short (returned %d)",
+          fd, buf->len, (int)write_ret);
+      // FIXME: handle this
+    assert(write_ret == buf->len);
+    return (int)write_ret;
+  }
 }
 
 static void wait_until_readable(const int fd) {
@@ -252,7 +275,7 @@ static void wait_until_readable(const int fd) {
       assert(stopping);  // that should have been SIGTERM
       return;
     }
-    err(1, "select() failed");
+    err(1, "select(read fd = %d) failed", fd);
   }
   if (!FD_ISSET(fd, &read_fds))
     errx(1, "select() did not return readable fd = %d", fd);