[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-changelog] [xen master] libxl: fork: Share SIGCHLD handler amongst ctxs
commit 5f0c4a78100382972b4d2a71a04b90e015e9fe87 Author: Ian Jackson <ian.jackson@xxxxxxxxxxxxx> AuthorDate: Fri Jan 17 11:58:55 2014 +0000 Commit: Ian Jackson <Ian.Jackson@xxxxxxxxxxxxx> CommitDate: Thu Feb 6 14:21:03 2014 +0000 libxl: fork: Share SIGCHLD handler amongst ctxs Previously, an application which had multiple libxl ctxs in multiple threads, would have to itself plumb SIGCHLD through to each ctx. Instead, permit multiple libxl ctxs to all share the SIGCHLD handler. We keep a list of all the ctxs which are interested in SIGCHLD and notify all of their self-pipes. In more detail: * sigchld_owner, the ctx* of the SIGCHLD owner, is replaced by sigchld_users, a list of SIGCHLD users. * Each ctx keeps track of whether it is on the users list, so that libxl__sigchld_needed and libxl__sigchld_notneeded now instead of idempotently installing and removing the handler, idempotently add or remove the ctx from the list. We ensure that we always have the SIGCHLD handler installed iff the sigchld_users list is nonempty. To make this a bit easier we make sigchld_installhandler_core and sigchld_removehandler_core idempotent. Specifically, the call sites for sigchld_installhandler_core and sigchld_removehandler_core are updated to manipulate sigchld_users and only call the install or remove functions as applicable. * In the signal handler we walk the list of SIGCHLD users and write to each of their self-pipes. That means that we need to arrange to defer SIGCHLD when we are manipulating the list (to avoid the signal handler interrupting our list manipulation); this is quite tiresome to arrange. The code as written will, on the first installation of the SIGCHLD handler, firstly install the real handler, then immediately replace it with the deferral handler. Doing it this way makes the code clearer as it makes the SIGCHLD deferral machinery much more self-contained (and hence easier to reason about). * The first part of libxl__sigchld_notneeded is broken out into a new function sigchld_user_remove (which is also needed during for postfork). And of course that first part of the function is now rather different, as explained above. * sigchld_installhandler_core no longer takes the gc argument, because it now deals with SIGCHLD for all ctxs. Signed-off-by: Ian Jackson <Ian.Jackson@xxxxxxxxxxxxx> Cc: Jim Fehlig <jfehlig@xxxxxxxx> Cc: Ian Campbell <Ian.Campbell@xxxxxxxxxx> Acked-by: Ian Campbell <ian.campbell@xxxxxxxxxx> --- v3: Include bugfixes from "Fixup SIGCHLD sharing" patch: * Use a mutex for defer_sigchld, to guard against concurrency between the thread calling defer_sigchld and an instance of the primary signal handler on another thread. * libxl_sigchld_owner_libxl_always is incompatible with SIGCHLD sharing. Document this correctly. Fix "have have" error in comment. Move removal of newly unused variables to previous patch. v2.1: Provide feature test macro LIBXL_HAVE_SIGCHLD_SHARING --- tools/libxl/libxl.h | 9 +++ tools/libxl/libxl_event.h | 14 ++-- tools/libxl/libxl_fork.c | 153 ++++++++++++++++++++++++++++++++++++------ tools/libxl/libxl_internal.h | 3 + 4 files changed, 153 insertions(+), 26 deletions(-) diff --git a/tools/libxl/libxl.h b/tools/libxl/libxl.h index 1ac34c3..0b992d1 100644 --- a/tools/libxl/libxl.h +++ b/tools/libxl/libxl.h @@ -422,6 +422,15 @@ */ #define LIBXL_HAVE_SIGCHLD_OWNER_SELECTIVE_REAP 1 +/* + * LIBXL_HAVE_SIGCHLD_SHARING + * + * If this is defined, it is permissible for multiple libxl ctxs + * to simultaneously "own" SIGCHLD. See "Subprocess handling" + * in libxl_event.h. + */ +#define LIBXL_HAVE_SIGCHLD_SHARING 1 + /* Functions annotated with LIBXL_EXTERNAL_CALLERS_ONLY may not be * called from within libxl itself. Callers outside libxl, who * do not #include libxl_internal.h, are fine. */ diff --git a/tools/libxl/libxl_event.h b/tools/libxl/libxl_event.h index 824ac88..ca43cb9 100644 --- a/tools/libxl/libxl_event.h +++ b/tools/libxl/libxl_event.h @@ -470,16 +470,18 @@ void libxl_osevent_occurred_timeout(libxl_ctx *ctx, void *for_libxl) * * libxl_sigchld_owner_libxl_always: * - * The application expects libxl to reap all of its children, - * and provides a callback to be notified of their exit - * statues. The application must have only one libxl_ctx - * configured this way. + * The application expects this libxl ctx to reap all of the + * process's children, and provides a callback to be notified of + * their exit statuses. The application must have only one + * libxl_ctx configured this way. * * libxl_sigchld_owner_libxl_always_selective_reap: * * The application expects to reap all of its own children - * synchronously, and does not use SIGCHLD. libxl is - * to install a SIGCHLD handler. + * synchronously, and does not use SIGCHLD. libxl is to install + * a SIGCHLD handler. The application may have multiple + * libxl_ctxs configured this way; in which case all of its ctxs + * must be so configured. */ diff --git a/tools/libxl/libxl_fork.c b/tools/libxl/libxl_fork.c index 084d86a..2432512 100644 --- a/tools/libxl/libxl_fork.c +++ b/tools/libxl/libxl_fork.c @@ -46,11 +46,19 @@ static int atfork_registered; static LIBXL_LIST_HEAD(, libxl__carefd) carefds = LIBXL_LIST_HEAD_INITIALIZER(carefds); -/* non-null iff installed, protected by no_forking */ -static libxl_ctx *sigchld_owner; +/* Protected against concurrency by no_forking. sigchld_users is + * protected against being interrupted by SIGCHLD (and thus read + * asynchronously by the signal handler) by sigchld_defer (see + * below). */ +static bool sigchld_installed; /* 0 means not */ +static pthread_mutex_t sigchld_defer_mutex = PTHREAD_MUTEX_INITIALIZER; +static LIBXL_LIST_HEAD(, libxl_ctx) sigchld_users = + LIBXL_LIST_HEAD_INITIALIZER(sigchld_users); static struct sigaction sigchld_saved_action; -static void sigchld_removehandler_core(void); +static void sigchld_removehandler_core(void); /* idempotent */ +static void sigchld_user_remove(libxl_ctx *ctx); /* idempotent */ +static void sigchld_sethandler_raw(void (*handler)(int), struct sigaction *old); static void atfork_lock(void) { @@ -126,8 +134,7 @@ void libxl_postfork_child_noexec(libxl_ctx *ctx) } LIBXL_LIST_INIT(&carefds); - if (sigchld_owner) - sigchld_removehandler_core(); + sigchld_user_remove(ctx); atfork_unlock(); } @@ -152,7 +159,8 @@ int libxl__carefd_fd(const libxl__carefd *cf) } /* - * Actual child process handling + * Low-level functions for child process handling, including + * the main SIGCHLD handler. */ /* Like waitpid(,,WNOHANG) but handles all errors except ECHILD. */ @@ -176,9 +184,22 @@ static void sigchld_selfpipe_handler(libxl__egc *egc, libxl__ev_fd *ev, static void sigchld_handler(int signo) { + /* This function has to be reentrant! Luckily it is. */ + + libxl_ctx *notify; int esave = errno; - int e = libxl__self_pipe_wakeup(sigchld_owner->sigchld_selfpipe[1]); - assert(!e); /* errors are probably EBADF, very bad */ + + int r = pthread_mutex_lock(&sigchld_defer_mutex); + assert(!r); + + LIBXL_LIST_FOREACH(notify, &sigchld_users, sigchld_users_entry) { + int e = libxl__self_pipe_wakeup(notify->sigchld_selfpipe[1]); + assert(!e); /* errors are probably EBADF, very bad */ + } + + r = pthread_mutex_unlock(&sigchld_defer_mutex); + assert(!r); + errno = esave; } @@ -195,22 +216,89 @@ static void sigchld_sethandler_raw(void (*handler)(int), struct sigaction *old) assert(!r); } -static void sigchld_removehandler_core(void) +/* + * SIGCHLD deferral + * + * sigchld_defer and sigchld_release are a bit like using sigprocmask + * to block the signal only they work for the whole process. Sadly + * this has to be done by setting a special handler that records the + * "pendingness" of the signal here in the program. How tedious. + * + * A property of this approach is that the signal handler itself + * must be reentrant (see the comment in release_sigchld). + * + * Callers have the atfork_lock so there is no risk of concurrency + * within these functions, aside from the risk of being interrupted by + * the signal. We use sigchld_defer_mutex to guard against the + * possibility of the real signal handler being still running on + * another thread. + */ + +static volatile sig_atomic_t sigchld_occurred_while_deferred; + +static void sigchld_handler_when_deferred(int signo) +{ + sigchld_occurred_while_deferred = 1; +} + +static void defer_sigchld(void) +{ + assert(sigchld_installed); + + sigchld_sethandler_raw(sigchld_handler_when_deferred, 0); + + /* Now _this thread_ cannot any longer be interrupted by the + * signal, so we can take the mutex without risk of deadlock. If + * another thread is in the signal handler, either it or we will + * block and wait for the other. */ + + int r = pthread_mutex_lock(&sigchld_defer_mutex); + assert(!r); +} + +static void release_sigchld(void) +{ + assert(sigchld_installed); + + int r = pthread_mutex_unlock(&sigchld_defer_mutex); + assert(!r); + + sigchld_sethandler_raw(sigchld_handler, 0); + if (sigchld_occurred_while_deferred) { + sigchld_occurred_while_deferred = 0; + /* We might get another SIGCHLD here, in which case + * sigchld_handler will be interrupted and re-entered. + * This is OK. */ + sigchld_handler(SIGCHLD); + } +} + +/* + * Meat of the child process handling. + */ + +static void sigchld_removehandler_core(void) /* idempotent */ { struct sigaction was; int r; + if (!sigchld_installed) + return; + r = sigaction(SIGCHLD, &sigchld_saved_action, &was); assert(!r); assert(!(was.sa_flags & SA_SIGINFO)); assert(was.sa_handler == sigchld_handler); - sigchld_owner = 0; + + sigchld_installed = 0; } -static void sigchld_installhandler_core(libxl__gc *gc) +static void sigchld_installhandler_core(void) /* idempotent */ { - assert(!sigchld_owner); - sigchld_owner = CTX; + if (sigchld_installed) + return; + + sigchld_installed = 1; sigchld_sethandler_raw(sigchld_handler, &sigchld_saved_action); @@ -220,15 +308,32 @@ static void sigchld_installhandler_core(libxl__gc *gc) sigchld_saved_action.sa_handler == SIG_IGN))); } -void libxl__sigchld_notneeded(libxl__gc *gc) /* non-reentrant, idempotent */ +static void sigchld_user_remove(libxl_ctx *ctx) /* idempotent */ { - int rc; + if (!ctx->sigchld_user_registered) + return; atfork_lock(); - if (sigchld_owner == CTX) + defer_sigchld(); + + LIBXL_LIST_REMOVE(ctx, sigchld_users_entry); + + release_sigchld(); + + if (LIBXL_LIST_EMPTY(&sigchld_users)) sigchld_removehandler_core(); + atfork_unlock(); + ctx->sigchld_user_registered = 0; +} + +void libxl__sigchld_notneeded(libxl__gc *gc) /* non-reentrant, idempotent */ +{ + int rc; + + sigchld_user_remove(CTX); + if (libxl__ev_fd_isregistered(&CTX->sigchld_selfpipe_efd)) { rc = libxl__ev_fd_modify(gc, &CTX->sigchld_selfpipe_efd, 0); if (rc) @@ -259,12 +364,20 @@ int libxl__sigchld_needed(libxl__gc *gc) /* non-reentrant, idempotent */ rc = libxl__ev_fd_modify(gc, &CTX->sigchld_selfpipe_efd, POLLIN); if (rc) goto out; } + if (!CTX->sigchld_user_registered) { + atfork_lock(); - atfork_lock(); - if (sigchld_owner != CTX) { - sigchld_installhandler_core(gc); + sigchld_installhandler_core(); + + defer_sigchld(); + + LIBXL_LIST_INSERT_HEAD(&sigchld_users, CTX, sigchld_users_entry); + + release_sigchld(); + atfork_unlock(); + + CTX->sigchld_user_registered = 1; } - atfork_unlock(); rc = 0; out: diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h index fba681d..8429448 100644 --- a/tools/libxl/libxl_internal.h +++ b/tools/libxl/libxl_internal.h @@ -355,6 +355,8 @@ struct libxl__ctx { int sigchld_selfpipe[2]; /* [0]==-1 means handler not installed */ libxl__ev_fd sigchld_selfpipe_efd; LIBXL_LIST_HEAD(, libxl__ev_child) children; + bool sigchld_user_registered; + LIBXL_LIST_ENTRY(libxl_ctx) sigchld_users_entry; libxl_version_info version_info; }; @@ -840,6 +842,7 @@ _hidden void libxl__poller_wakeup(libxl__egc *egc, libxl__poller *p); extern const libxl_childproc_hooks libxl__childproc_default_hooks; int libxl__sigchld_needed(libxl__gc*); /* non-reentrant idempotent, logs errs */ void libxl__sigchld_notneeded(libxl__gc*); /* non-reentrant idempotent */ +void libxl__sigchld_check_stale_handler(void); int libxl__self_pipe_wakeup(int fd); /* returns 0 or -1 setting errno */ int libxl__self_pipe_eatall(int fd); /* returns 0 or -1 setting errno */ -- generated by git-patchbot for /home/xen/git/xen.git#master _______________________________________________ Xen-changelog mailing list Xen-changelog@xxxxxxxxxxxxx http://lists.xensource.com/xen-changelog
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |