* Some hardcore magic to handle asynchronous client disconnects.

The problem is that when we kill the client while the worker is
  building, and the builder is not writing anything to stderr, then
  the worker never notice that the socket is closed on the other side,
  so it just continues indefinitely.  The solution is to catch SIGIO,
  which is sent when the far side of the socket closes, and simulate
  an normal interruption.  Of course, SIGIO is also sent every time
  the client sends data over the socket, so we only enable the signal
  handler when we're not expecting any data...
This commit is contained in:
Eelco Dolstra 2006-12-03 03:03:36 +00:00
parent 4251f94b32
commit 3ed9e4ad9b

View file

@ -6,6 +6,9 @@
#include "archive.hh" #include "archive.hh"
#include <iostream> #include <iostream>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
using namespace nix; using namespace nix;
@ -31,6 +34,10 @@ static Sink * _to; /* !!! should make writeToStderr an object */
bool canSendStderr; bool canSendStderr;
/* This function is called anytime we want to write something to
stderr. If we're in a state where the protocol allows it (i.e.,
when canSendStderr), send the message to the client over the
socket. */
static void tunnelStderr(const unsigned char * buf, size_t count) static void tunnelStderr(const unsigned char * buf, size_t count)
{ {
writeFull(STDERR_FILENO, buf, count); writeFull(STDERR_FILENO, buf, count);
@ -48,11 +55,28 @@ static void tunnelStderr(const unsigned char * buf, size_t count)
} }
/* A SIGIO signal is received when data is available on the client
communication scoket, or when the client has closed its side of the
socket. This handler is enabled at precisely those moments in the
protocol when we're doing work and the client is supposed to be
quiet. Thus, if we get a SIGIO signal, it means that the client
has quit. So we should quit as well. */
static void sigioHandler(int sigNo)
{
_isInterrupted = 1;
canSendStderr = false;
write(STDERR_FILENO, "SIGIO\n", 6);
}
/* startWork() means that we're starting an operation for which we /* startWork() means that we're starting an operation for which we
want to send out stderr to the client. */ want to send out stderr to the client. */
static void startWork() static void startWork()
{ {
canSendStderr = true; canSendStderr = true;
/* Handle client death asynchronously. */
signal(SIGIO, sigioHandler);
} }
@ -60,6 +84,11 @@ static void startWork()
client. */ client. */
static void stopWork() 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);
canSendStderr = false; canSendStderr = false;
writeInt(STDERR_LAST, *_to); writeInt(STDERR_LAST, *_to);
} }
@ -216,6 +245,13 @@ void run(Strings args)
if (arg == "--daemon") daemon = true; if (arg == "--daemon") daemon = true;
} }
/* Allow us to receive SIGIO for events on the client socket. */
signal(SIGIO, SIG_IGN);
if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1)
throw SysError("F_SETOWN");
if (fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | FASYNC) == -1)
throw SysError("F_SETFL");
if (slave) { if (slave) {
FdSource source(STDIN_FILENO); FdSource source(STDIN_FILENO);
FdSink sink(STDOUT_FILENO); FdSink sink(STDOUT_FILENO);