diff options
author | Brad King <brad.king@kitware.com> | 2006-05-21 14:26:28 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2006-05-21 14:26:28 (GMT) |
commit | 0d594a453845e083207e6a85797a9b96ec278c75 (patch) | |
tree | e06808fbe4f0d7245b866b00c10c8bf5e0a445c2 /Source/kwsys/ProcessUNIX.c | |
parent | 3dd70af5a0114ec8708c2ab06be647277a3067ac (diff) | |
download | CMake-0d594a453845e083207e6a85797a9b96ec278c75.zip CMake-0d594a453845e083207e6a85797a9b96ec278c75.tar.gz CMake-0d594a453845e083207e6a85797a9b96ec278c75.tar.bz2 |
ENH: Implemented handling of SIGCHLD to detect the termination of immediate children. This allows grandchildren to remain running after the children exit.
Diffstat (limited to 'Source/kwsys/ProcessUNIX.c')
-rw-r--r-- | Source/kwsys/ProcessUNIX.c | 390 |
1 files changed, 310 insertions, 80 deletions
diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c index 9eedad0..6f9dd84 100644 --- a/Source/kwsys/ProcessUNIX.c +++ b/Source/kwsys/ProcessUNIX.c @@ -24,17 +24,18 @@ Implementation for UNIX -On UNIX, a child process is forked to exec the program. Three -output pipes from the child are read by the parent process using a -select call to block until data are ready. Two of the pipes are -stdout and stderr for the child. The third is a special error pipe -that has two purposes. First, if the child cannot exec the program, -the error is reported through the error pipe. Second, the error -pipe is left open until the child exits. This is used in -conjunction with the timeout on the select call to implement a -timeout for program even when it closes stdout and stderr. +On UNIX, a child process is forked to exec the program. Three output +pipes are read by the parent process using a select call to block +until data are ready. Two of the pipes are stdout and stderr for the +child. The third is a special pipe populated by a signal handler to +indicate that a child has terminated. This is used in conjunction +with the timeout on the select call to implement a timeout for program +even when it closes stdout and stderr and at the same time avoiding +races. + */ + /* TODO: @@ -68,7 +69,7 @@ do. #define KWSYSPE_PIPE_COUNT 3 #define KWSYSPE_PIPE_STDOUT 0 #define KWSYSPE_PIPE_STDERR 1 -#define KWSYSPE_PIPE_TERM 2 +#define KWSYSPE_PIPE_SIGNAL 2 /* The maximum amount to read from a pipe at a time. */ #define KWSYSPE_PIPE_BUFFER_SIZE 1024 @@ -89,7 +90,6 @@ typedef struct kwsysProcessCreateInformation_s int StdIn; int StdOut; int StdErr; - int TermPipe; int ErrorPipe[2]; } kwsysProcessCreateInformation; @@ -99,6 +99,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error); static void kwsysProcessCleanupDescriptor(int* pfd); static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, kwsysProcessCreateInformation* si, int* readEnd); +static void kwsysProcessDestroy(kwsysProcess* cp); static int kwsysProcessSetupOutputPipeFile(int* p, const char* name); static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout, kwsysProcessTime* timeoutTime); @@ -117,6 +118,10 @@ static void kwsysProcessRestoreDefaultSignalHandlers(void); static pid_t kwsysProcessFork(kwsysProcess* cp, kwsysProcessCreateInformation* si); static void kwsysProcessKill(pid_t process_id); +static int kwsysProcessesAdd(kwsysProcess* cp); +static void kwsysProcessesRemove(kwsysProcess* cp); +static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, + void* ucontext); /*--------------------------------------------------------------------------*/ /* Structure containing data used to implement the child's execution. */ @@ -126,9 +131,13 @@ struct kwsysProcess_s char*** Commands; int NumberOfCommands; - /* Descriptors for the read ends of the child's output pipes. */ + /* Descriptors for the read ends of the child's output pipes and + the signal pipe. */ int PipeReadEnds[KWSYSPE_PIPE_COUNT]; + /* Write descriptor for child termination signal pipe. */ + int SignalPipe; + /* Buffer for pipe data. */ char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE]; @@ -159,15 +168,15 @@ struct kwsysProcess_s /* Flag for whether the timeout expired. */ int TimeoutExpired; - /* The old SIGCHLD handler. */ - struct sigaction OldSigChldAction; - /* The number of pipes left open during execution. */ int PipesLeft; /* File descriptor set for call to select. */ fd_set PipeSet; + /* The number of children still executing. */ + int CommandsLeft; + /* The current status of the child process. */ int State; @@ -558,8 +567,7 @@ const char* kwsysProcess_GetExceptionString(kwsysProcess* cp) void kwsysProcess_Execute(kwsysProcess* cp) { int i; - struct sigaction newSigChldAction; - kwsysProcessCreateInformation si = {-1, -1, -1, -1, {-1, -1}}; + kwsysProcessCreateInformation si = {-1, -1, -1, {-1, -1}}; /* Do not execute a second copy simultaneously. */ if(!cp || cp->State == kwsysProcess_State_Executing) @@ -597,45 +605,41 @@ void kwsysProcess_Execute(kwsysProcess* cp) } } - /* We want no special handling of SIGCHLD. Repeat call until it is - not interrupted. */ - memset(&newSigChldAction, 0, sizeof(struct sigaction)); - newSigChldAction.sa_handler = SIG_DFL; - while((sigaction(SIGCHLD, &newSigChldAction, &cp->OldSigChldAction) < 0) && - (errno == EINTR)); - - /* Setup the stderr and termination pipes to be shared by all processes. */ - for(i=KWSYSPE_PIPE_STDERR; i < KWSYSPE_PIPE_COUNT; ++i) + /* If not running a detached child, add this object to the global + set of process objects that wish to be notified when a child + exits. */ + if(!cp->OptionDetach) { - /* Create the pipe. */ - int p[2]; - if(pipe(p) < 0) + if(!kwsysProcessesAdd(cp)) { kwsysProcessCleanup(cp, 1); return; } + } - /* Store the pipe. */ - cp->PipeReadEnds[i] = p[0]; - if(i == KWSYSPE_PIPE_STDERR) - { - si.StdErr = p[1]; - } - else - { - si.TermPipe = p[1]; - } + /* Setup the stderr pipe to be shared by all processes. */ + { + /* Create the pipe. */ + int p[2]; + if(pipe(p) < 0) + { + kwsysProcessCleanup(cp, 1); + return; + } - /* Set close-on-exec flag on the pipe's ends. */ - if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) || - (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0)) - { - kwsysProcessCleanup(cp, 1); - kwsysProcessCleanupDescriptor(&si.StdErr); - kwsysProcessCleanupDescriptor(&si.TermPipe); - return; - } + /* Store the pipe. */ + cp->PipeReadEnds[KWSYSPE_PIPE_STDERR] = p[0]; + si.StdErr = p[1]; + + /* Set close-on-exec flag on the pipe's ends. */ + if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) || + (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0)) + { + kwsysProcessCleanup(cp, 1); + kwsysProcessCleanupDescriptor(&si.StdErr); + return; } + } /* Replace the stderr pipe with a file if requested. In this case the select call will report that stderr is closed immediately. */ @@ -645,7 +649,6 @@ void kwsysProcess_Execute(kwsysProcess* cp) { kwsysProcessCleanup(cp, 1); kwsysProcessCleanupDescriptor(&si.StdErr); - kwsysProcessCleanupDescriptor(&si.TermPipe); return; } } @@ -688,7 +691,6 @@ void kwsysProcess_Execute(kwsysProcess* cp) { kwsysProcessCleanupDescriptor(&si.StdErr); } - kwsysProcessCleanupDescriptor(&si.TermPipe); kwsysProcessCleanupDescriptor(&si.ErrorPipe[0]); kwsysProcessCleanupDescriptor(&si.ErrorPipe[1]); return; @@ -703,7 +705,6 @@ void kwsysProcess_Execute(kwsysProcess* cp) { kwsysProcessCleanupDescriptor(&si.StdErr); } - kwsysProcessCleanupDescriptor(&si.TermPipe); /* Restore the working directory. */ if(cp->RealWorkingDirectory) @@ -823,9 +824,10 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, if(n > 0) { /* We have data on this pipe. */ - if(i == KWSYSPE_PIPE_TERM) + if(i == KWSYSPE_PIPE_SIGNAL) { - /* This is data on the special termination pipe. Ignore it. */ + /* A child process has terminated. */ + kwsysProcessDestroy(cp); } else if(data && length) { @@ -970,7 +972,6 @@ int kwsysProcess_WaitForData(kwsysProcess* cp, char** data, int* length, /*--------------------------------------------------------------------------*/ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) { - int result = 0; int status = 0; int prPipe = 0; @@ -989,26 +990,6 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout) } } - /* Wait for each child to terminate. The process should have - already exited because KWSYSPE_PIPE_TERM has been closed by this - point. Repeat the call until it is not interrupted. */ - if(!cp->Detached) - { - int i; - for(i=0; i < cp->NumberOfCommands; ++i) - { - while(((result = waitpid(cp->ForkPIDs[i], - &cp->CommandExitCodes[i], 0)) < 0) && - (errno == EINTR)); - if(result <= 0 && cp->State != kwsysProcess_State_Error) - { - /* Unexpected error. Report the first time this happens. */ - strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); - cp->State = kwsysProcess_State_Error; - } - } - } - /* Check if there was an error in one of the waitpid calls. */ if(cp->State == kwsysProcess_State_Error) { @@ -1084,11 +1065,18 @@ void kwsysProcess_Kill(kwsysProcess* cp) cp->Killed = 1; for(i=0; i < cp->NumberOfCommands; ++i) { + int status; if(cp->ForkPIDs[i]) { + /* Kill the child. */ kwsysProcessKill(cp->ForkPIDs[i]); + + /* Reap the child. Keep trying until the call is not + interrupted. */ + while((waitpid(cp->ForkPIDs[i], &status, 0) < 0) && (errno == EINTR)); } } + cp->CommandsLeft = 0; /* Close all the pipe read ends. */ for(i=0; i < KWSYSPE_PIPE_COUNT; ++i) @@ -1107,6 +1095,7 @@ static int kwsysProcessInitialize(kwsysProcess* cp) { cp->PipeReadEnds[i] = -1; } + cp->SignalPipe = -1; cp->SelectError = 0; cp->StartTime.tv_sec = -1; cp->StartTime.tv_usec = -1; @@ -1114,6 +1103,7 @@ static int kwsysProcessInitialize(kwsysProcess* cp) cp->TimeoutTime.tv_usec = -1; cp->TimeoutExpired = 0; cp->PipesLeft = 0; + cp->CommandsLeft = 0; FD_ZERO(&cp->PipeSet); cp->State = kwsysProcess_State_Starting; cp->Killed = 0; @@ -1194,6 +1184,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error) { /* Kill the child. */ kwsysProcessKill(cp->ForkPIDs[i]); + /* Reap the child. Keep trying until the call is not interrupted. */ while((waitpid(cp->ForkPIDs[i], &status, 0) < 0) && @@ -1209,9 +1200,13 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error) } } - /* Restore the SIGCHLD handler. */ - while((sigaction(SIGCHLD, &cp->OldSigChldAction, 0) < 0) && - (errno == EINTR)); + /* If not creating a detached child, remove this object from the + global set of process objects that wish to be notified when a + child exits. */ + if(!cp->OptionDetach) + { + kwsysProcessesRemove(cp); + } /* Free memory. */ if(cp->ForkPIDs) @@ -1360,12 +1355,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, } /* Clear the close-on-exec flag for stdin, stdout, and stderr. - Also clear it for the termination pipe. All other pipe handles - will be closed when exec succeeds. */ + All other pipe handles will be closed when exec succeeds. */ fcntl(0, F_SETFD, 0); fcntl(1, F_SETFD, 0); fcntl(2, F_SETFD, 0); - fcntl(si->TermPipe, F_SETFD, 0); /* Restore all default signal handlers. */ kwsysProcessRestoreDefaultSignalHandlers(); @@ -1377,6 +1370,9 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, kwsysProcessChildErrorExit(si->ErrorPipe[1]); } + /* A child has been created. */ + ++cp->CommandsLeft; + /* We are done with the error reporting pipe write end. */ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]); @@ -1425,6 +1421,47 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex, } /*--------------------------------------------------------------------------*/ +static void kwsysProcessDestroy(kwsysProcess* cp) +{ + /* A child process has terminated. Reap it if it is one handled by + this object. */ + int i; + for(i=0; i < cp->NumberOfCommands; ++i) + { + if(cp->ForkPIDs[i]) + { + int result; + while(((result = waitpid(cp->ForkPIDs[i], + &cp->CommandExitCodes[i], WNOHANG)) < 0) && + (errno == EINTR)); + if(result > 0) + { + /* This child has termianted. */ + cp->ForkPIDs[i] = 0; + if(--cp->CommandsLeft == 0) + { + /* All children have terminated. Close the signal pipe + write end so that no more notifications are sent to this + object. */ + kwsysProcessCleanupDescriptor(&cp->SignalPipe); + + /* TODO: Once the children have terminated, switch + WaitForData to use a non-blocking read to get the + rest of the data from the pipe. This is needed when + grandchildren keep the output pipes open. */ + } + } + else if(result < 0 && cp->State != kwsysProcess_State_Error) + { + /* Unexpected error. Report the first time this happens. */ + strncpy(cp->ErrorMessage, strerror(errno), KWSYSPE_PIPE_BUFFER_SIZE); + cp->State = kwsysProcess_State_Error; + } + } + } +} + +/*--------------------------------------------------------------------------*/ static int kwsysProcessSetupOutputPipeFile(int* p, const char* name) { int fout; @@ -2014,3 +2051,196 @@ static void kwsysProcessKill(pid_t process_id) /* Kill the process. */ kill(process_id, SIGKILL); } + +/*--------------------------------------------------------------------------*/ +/* Global set of executing processes for use by the signal handler. + This global instance will be zero-initialized by the compiler. */ +typedef struct kwsysProcessInstances_s +{ + int Count; + int Size; + kwsysProcess** Processes; +} kwsysProcessInstances; +static kwsysProcessInstances kwsysProcesses; + +/* The old SIGCHLD handler. */ +static struct sigaction kwsysProcessesOldSigChldAction; + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses) +{ + /* Block SIGCHLD while we update the set of pipes to check. + TODO: sigprocmask is undefined for threaded apps. See + pthread_sigmask. */ + sigset_t newset; + sigset_t oldset; + sigemptyset(&newset); + sigaddset(&newset, SIGCHLD); + sigprocmask(SIG_BLOCK, &newset, &oldset); + + /* Store the new set in that seen by the signal handler. */ + kwsysProcesses = *newProcesses; + + /* Restore the signal mask to the previous setting. */ + sigprocmask(SIG_SETMASK, &oldset, 0); +} + +/*--------------------------------------------------------------------------*/ +static int kwsysProcessesAdd(kwsysProcess* cp) +{ + /* Create a pipe through which the signal handler can notify the + given process object that a child has exited. */ + { + /* Create the pipe. */ + int oldfl[2]; + int p[2]; + if(pipe(p) < 0) + { + return 0; + } + + /* Store the pipes now to be sure they are cleaned up later. */ + cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL] = p[0]; + cp->SignalPipe = p[1]; + + /* Switch the pipe to non-blocking mode so that reading a byte can + be an atomic test-and-set. */ + if((oldfl[0] = fcntl(p[0], F_GETFL) < 0) || + (oldfl[1] = fcntl(p[1], F_GETFL) < 0) || + (fcntl(p[0], F_SETFL, oldfl[0] | O_NONBLOCK) < 0) || + (fcntl(p[1], F_SETFL, oldfl[1] | O_NONBLOCK) < 0)) + { + return 0; + } + + /* The children do not need this pipe. Set close-on-exec flag on + the pipe's ends. */ + if((fcntl(p[0], F_SETFD, FD_CLOEXEC) < 0) || + (fcntl(p[1], F_SETFD, FD_CLOEXEC) < 0)) + { + return 0; + } + } + + /* Attempt to add the given signal pipe to the signal handler set. */ + { + + /* Make sure there is enough space for the new signal pipe. */ + kwsysProcessInstances oldProcesses = kwsysProcesses; + kwsysProcessInstances newProcesses = oldProcesses; + if(oldProcesses.Count == oldProcesses.Size) + { + /* Start with enough space for a small number of process instances + and double the size each time more is needed. */ + newProcesses.Size = oldProcesses.Size? oldProcesses.Size*2 : 4; + + /* Try allocating the new block of memory. */ + if((newProcesses.Processes = ((kwsysProcess**) + malloc(newProcesses.Size* + sizeof(kwsysProcess*))))) + { + /* Copy the old pipe set to the new memory. */ + if(oldProcesses.Count > 0) + { + memcpy(newProcesses.Processes, oldProcesses.Processes, + (oldProcesses.Count * sizeof(kwsysProcess*))); + } + } + else + { + /* Failed to allocate memory for the new signal pipe set. */ + return 0; + } + } + + /* Append the new signal pipe to the set. */ + newProcesses.Processes[newProcesses.Count++] = cp; + + /* Store the new set in that seen by the signal handler. */ + kwsysProcessesUpdate(&newProcesses); + + /* Free the original pipes if new ones were allocated. */ + if(newProcesses.Processes != oldProcesses.Processes) + { + free(oldProcesses.Processes); + } + + /* If this is the first process, enable the signal handler. */ + if(newProcesses.Count == 1) + { + /* Install our handler for SIGCHLD. Repeat call until it is not + interrupted. */ + struct sigaction newSigChldAction; + memset(&newSigChldAction, 0, sizeof(struct sigaction)); + newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler; + newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO; + while((sigaction(SIGCHLD, &newSigChldAction, + &kwsysProcessesOldSigChldAction) < 0) && + (errno == EINTR)); + } + } + + return 1; +} + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessesRemove(kwsysProcess* cp) +{ + /* Attempt to remove the given signal pipe from the signal handler set. */ + { + /* Find the given process in the set. */ + kwsysProcessInstances newProcesses = kwsysProcesses; + int i; + for(i=0; i < newProcesses.Count; ++i) + { + if(newProcesses.Processes[i] == cp) + { + break; + } + } + if(i < newProcesses.Count) + { + /* Remove the process from the set. */ + --newProcesses.Count; + for(; i < newProcesses.Count; ++i) + { + newProcesses.Processes[i] = newProcesses.Processes[i+1]; + } + + /* Store the new set in that seen by the signal handler. */ + kwsysProcessesUpdate(&newProcesses); + + /* If this was the last process, disable the signal handler. */ + if(newProcesses.Count == 0) + { + /* Restore the SIGCHLD handler. Repeat call until it is not + interrupted. */ + while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) && + (errno == EINTR)); + } + } + } + + /* Close the pipe through which the signal handler may have notified + the given process object that a child has exited. */ + kwsysProcessCleanupDescriptor(&cp->SignalPipe); +} + +/*--------------------------------------------------------------------------*/ +static void kwsysProcessesSignalHandler(int signum, siginfo_t* info, + void* ucontext) +{ + /* Signal all process objects that a child has terminated. */ + int i; + (void)signum; + (void)info; + (void)ucontext; + for(i=0; i < kwsysProcesses.Count; ++i) + { + /* Set the pipe in a signalled state. */ + char buf = 1; + kwsysProcess* cp = kwsysProcesses.Processes[i]; + read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); + write(cp->SignalPipe, &buf, 1); + } +} |