about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2006-12-03T03·16+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2006-12-03T03·16+0000
commita9f92410541e15e994c3306215608cb33ff101e2 (patch)
tree76b2624ca04c2773585c7e65dd07b303899b38bf
parent3ed9e4ad9b72dfbe59d47823beec829fe550351e (diff)
* Handle a subtle race condition: the client closing the socket
  between the last worker read/write and the enabling of the signal
  handler.

-rw-r--r--src/nix-worker/main.cc32
1 files changed, 30 insertions, 2 deletions
diff --git a/src/nix-worker/main.cc b/src/nix-worker/main.cc
index c4a2d8a7a026..666f615723ae 100644
--- a/src/nix-worker/main.cc
+++ b/src/nix-worker/main.cc
@@ -76,7 +76,34 @@ static void startWork()
     canSendStderr = true;
 
     /* Handle client death asynchronously. */
-    signal(SIGIO, sigioHandler);
+    if (signal(SIGIO, sigioHandler) == SIG_ERR)
+        throw SysError("setting handler for SIGIO");
+
+    /* Of course, there is a race condition here: the socket could
+       have closed between when we last read from / wrote to it, and
+       between the time we set the handler for SIGIO.  In that case we
+       won't get the signal.  So do a non-blocking select() to find
+       out if any input is available on the socket.  If there is, it
+       has to be the 0-byte read that indicates that the socket has
+       closed. */
+    
+    struct timeval timeout;
+    timeout.tv_sec = timeout.tv_usec = 0;
+
+    fd_set fds;
+    FD_ZERO(&fds);
+    FD_SET(STDIN_FILENO, &fds);
+        
+    if (select(STDIN_FILENO + 1, &fds, 0, 0, &timeout) == -1)
+        throw SysError("select()");
+
+    if (FD_ISSET(STDIN_FILENO, &fds)) {
+        char c;
+        if (read(STDIN_FILENO, &c, 1) != 0)
+            throw Error("EOF expected (protocol error?)");
+        _isInterrupted = 1;
+        checkInterrupt();
+    }
 }
 
 
@@ -87,7 +114,8 @@ static void stopWork()
     /* Stop handling async client death; we're going to a state where
        we're either sending or receiving from the client, so we'll be
        notified of client death anyway. */
-    signal(SIGIO, SIG_IGN);
+    if (signal(SIGIO, SIG_IGN) == SIG_ERR)
+        throw SysError("ignoring SIGIO");
     
     canSendStderr = false;
     writeInt(STDERR_LAST, *_to);