Skip to content

Commit

Permalink
[lib/utils/process] try to workaround freeze with buggy signal handler
Browse files Browse the repository at this point in the history
  • Loading branch information
yuyichao committed Jan 21, 2014
1 parent 7d24122 commit 8bdfdb7
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 6 deletions.
27 changes: 22 additions & 5 deletions lib/utils/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@
#include <errno.h>
#include <poll.h>

static bool
qtcSignalHandlerSet(int sig)
{
struct sigaction oact;
QTC_RET_IF_FAIL(sigaction(sig, NULL, &oact) == 0, false);
void *handler = ((oact.sa_flags & SA_SIGINFO) ? (void*)oact.sa_handler :
(void*)oact.sa_sigaction);
return qtcNoneOf(handler, SIG_DFL, SIG_IGN);
}

QTC_EXPORT bool
qtcForkBackground(QtcCallback cb, void *data, QtcCallback fail_cb)
{
Expand All @@ -38,11 +48,10 @@ qtcForkBackground(QtcCallback cb, void *data, QtcCallback fail_cb)
// a signal handler registered for SIGCHLD and the child process exit
// inside waitpid()/wait(), it will be run after the process state is
// cleared and would therefore block if it call wait() (or waitpid(-1))
// and if there are other child processes. As a workaround we use vfork()
// to block the parent until direct child exit so that waitpid() will always
// be called after the process exit and would never hang because of signal
// handler. See (the RATIONALE section of) wait(3P) for more detail.
pid_t child = vfork();
// and if there are other child processes. As a workaround we only call
// waitpid() if the main program did not set up any signal handlers for
// SIGCHLD. See (the RATIONALE section of) wait(3P) for more detail.
pid_t child = fork();
if (child < 0) {
return false;
} else if (child == 0) {
Expand All @@ -60,6 +69,14 @@ qtcForkBackground(QtcCallback cb, void *data, QtcCallback fail_cb)
return true;
} else {
/* parent */
if (qtcSignalHandlerSet(SIGCHLD)) {
// If we create a child process, the signal handler will recieve
// the signal anyway (and there is no way to only block SIGCHLD
// only for our child process). Since the signal handler may
// hang and should already take care of getting rid of
// zombie processes, we do not call waitpid in this case....
return true;
}
// If SIGCHLD is ignored, waitpid will return -1 with errno
// set to ECHILD, treat this as success (good enough for our purpose
// and not likely to fail anyway...)
Expand Down
24 changes: 23 additions & 1 deletion test/test-process.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <assert.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>

int pipe_fds[2];
long num;
Expand All @@ -34,16 +36,36 @@ forkCb(void *data)
write(pipe_fds[1], &num, sizeof(num));
}

static void
sigchld_handler(int signum)
{
QTC_UNUSED(signum);
wait(NULL);
}

int
main()
{
int res = pipe(pipe_fds);
assert(res == 0);
srandom(time(NULL));
num = random();

struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);

if (fork() == 0) {
sleep(4);
return 0;
}

alarm(1);
bool fork_res = qtcForkBackground(forkCb, NULL);
assert(fork_res);
alarm(1);
long num_sent = 0;
ssize_t read_res = read(pipe_fds[0], &num_sent, sizeof(num_sent));
assert(read_res == sizeof(num_sent));
Expand Down

0 comments on commit 8bdfdb7

Please sign in to comment.