From: Eric Wong <e@80x24.org>
To: spew@80x24.org
Subject: [PATCH] hijack SIGCHLD handler for internal use
Date: Sat, 23 Jun 2018 15:58:26 +0000 [thread overview]
Message-ID: <20180623155826.29681-1-e@80x24.org> (raw)
Use a global SIGCHLD handler to guard all callers of rb_waitpid.
To work safely with multi-threaded programs, we introduce a
VM-wide waitpid_lock to be acquired BEFORE fork/vfork spawns the
process. This is to be combined with the new ruby_waitpid_locked
function used by mjit.c in a non-Ruby thread.
LIFO ordering of the waiting_pids list matches Linux wait4
syscall behavior and ensures ordering of conflicting
rb_waitpid calls remains unchanged.
The disabling of SIGCHLD in rb_f_system is longer necessary,
as we use deferred signal handling and no longer make ANY
blocking waitpid syscalls in other threads which could "beat"
the waitpid call made by rb_f_system.
Since this SIGCHLD handling relies on normal Ruby VM interrupt
handling, we must continue to check VM interrupts during
mjit_finish.
Ruby-level SIGCHLD handlers registered with Signal.trap(:CHLD)
continues to work as before and there should be no regressions
in any existing use cases.
---
mjit.c | 50 ++++++++++++---
process.c | 189 +++++++++++++++++++++++++++++++++++++++++++-----------
signal.c | 40 ++++++++++--
thread.c | 2 +
vm_core.h | 3 +
5 files changed, 232 insertions(+), 52 deletions(-)
diff --git a/mjit.c b/mjit.c
index 3dc34e8f45..55ff7e21ee 100644
--- a/mjit.c
+++ b/mjit.c
@@ -80,6 +80,7 @@
#include "constant.h"
#include "id_table.h"
#include "ruby_assert.h"
+#include "ruby/thread.h"
#include "ruby/util.h"
#include "ruby/version.h"
@@ -109,6 +110,9 @@ extern void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lo
extern int rb_thread_create_mjit_thread(void (*child_hook)(void), void (*worker_func)(void));
+
+pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options);
+
#define RB_CONDATTR_CLOCK_MONOTONIC 1
#ifdef _WIN32
@@ -380,21 +384,32 @@ exec_process(const char *path, char *const argv[])
{
int stat, exit_code;
pid_t pid;
+ rb_vm_t *vm = GET_VM();
+ rb_nativethread_lock_lock(&vm->waitpid_lock);
pid = start_process(path, argv);
- if (pid <= 0)
+ if (pid <= 0) {
+ rb_nativethread_lock_unlock(&vm->waitpid_lock);
return -2;
-
+ }
for (;;) {
- waitpid(pid, &stat, 0);
- if (WIFEXITED(stat)) {
- exit_code = WEXITSTATUS(stat);
- break;
- } else if (WIFSIGNALED(stat)) {
- exit_code = -1;
+ pid_t r = ruby_waitpid_locked(vm, pid, &stat, 0);
+ if (r == -1) {
+ if (errno == EINTR) continue;
+ fprintf(stderr, "waitpid: %s\n", strerror(errno));
break;
}
+ else if (r == pid) {
+ if (WIFEXITED(stat)) {
+ exit_code = WEXITSTATUS(stat);
+ break;
+ } else if (WIFSIGNALED(stat)) {
+ exit_code = -1;
+ break;
+ }
+ }
}
+ rb_nativethread_lock_unlock(&vm->waitpid_lock);
return exit_code;
}
@@ -1474,6 +1489,7 @@ stop_worker(void)
CRITICAL_SECTION_START(3, "in stop_worker");
rb_native_cond_broadcast(&mjit_worker_wakeup);
CRITICAL_SECTION_FINISH(3, "in stop_worker");
+ RUBY_VM_CHECK_INTS(GET_EC());
}
}
@@ -1509,6 +1525,21 @@ mjit_resume(void)
return Qtrue;
}
+static void *
+wait_pch(void *ignored)
+{
+ rb_native_cond_wait(&mjit_pch_wakeup, &mjit_engine_mutex);
+ return 0;
+}
+
+static void
+ubf_pch(void *ignored)
+{
+ rb_native_mutex_lock(&mjit_engine_mutex);
+ rb_native_cond_signal(&mjit_pch_wakeup);
+ rb_native_mutex_unlock(&mjit_engine_mutex);
+}
+
/* Finish the threads processing units and creating PCH, finalize
and free MJIT data. It should be called last during MJIT
life. */
@@ -1528,7 +1559,8 @@ mjit_finish(void)
absence. So wait for a clean finish of the threads. */
while (pch_status == PCH_NOT_READY) {
verbose(3, "Waiting wakeup from make_pch");
- rb_native_cond_wait(&mjit_pch_wakeup, &mjit_engine_mutex);
+ /* release GVL to handle interrupts */
+ rb_thread_call_without_gvl(wait_pch, 0, ubf_pch, 0);
}
CRITICAL_SECTION_FINISH(3, "in mjit_finish to wakeup from pch");
diff --git a/process.c b/process.c
index a60c0df16b..bdb92036b7 100644
--- a/process.c
+++ b/process.c
@@ -885,12 +885,6 @@ pst_wcoredump(VALUE st)
#endif
}
-struct waitpid_arg {
- rb_pid_t pid;
- int flags;
- int *st;
-};
-
static rb_pid_t
do_waitpid(rb_pid_t pid, int *st, int flags)
{
@@ -903,25 +897,155 @@ do_waitpid(rb_pid_t pid, int *st, int flags)
#endif
}
+struct waitpid_state {
+ struct list_node wnode;
+ rb_nativethread_cond_t cond;
+ rb_pid_t ret;
+ rb_pid_t pid;
+ int status;
+ int options;
+ int errnum;
+ rb_vm_t *vm;
+};
+
+void rb_native_cond_signal(rb_nativethread_cond_t *);
+void rb_native_cond_wait(rb_nativethread_cond_t *, rb_nativethread_lock_t *);
+void rb_native_cond_initialize(rb_nativethread_cond_t *);
+void rb_native_cond_destroy(rb_nativethread_cond_t *);
+
+/* only called by vm->main_thread */
+void
+rb_sigchld(rb_vm_t *vm)
+{
+ struct waitpid_state *w = 0, *next;
+
+ rb_nativethread_lock_lock(&vm->waitpid_lock);
+ list_for_each_safe(&vm->waiting_pids, w, next, wnode) {
+ w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
+ if (w->ret == 0) continue;
+ if (w->ret == -1) w->errnum = errno;
+ list_del_init(&w->wnode);
+ rb_native_cond_signal(&w->cond);
+ }
+ rb_nativethread_lock_unlock(&vm->waitpid_lock);
+}
+
+static void
+waitpid_state_init(struct waitpid_state *w, rb_vm_t *vm, pid_t pid, int options)
+{
+ rb_native_cond_initialize(&w->cond);
+ w->ret = 0;
+ w->pid = pid;
+ w->status = 0;
+ w->options = options;
+ w->vm = vm;
+ list_node_init(&w->wnode);
+}
+
+/* must be called with vm->waitpid_lock held, this is not interruptible */
+pid_t
+ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options)
+{
+ struct waitpid_state w;
+
+ assert(!ruby_thread_has_gvl_p() && "must not have GVL");
+
+ waitpid_state_init(&w, vm, pid, options);
+ w.ret = do_waitpid(w.pid, &w.status, w.options | WNOHANG);
+ if (w.ret) {
+ if (w.ret == -1) {
+ w.errnum = errno;
+ }
+ }
+ else {
+ list_add(&vm->waiting_pids, &w.wnode);
+ while (!w.ret) {
+ rb_native_cond_wait(&w.cond, &vm->waitpid_lock);
+ }
+ list_del(&w.wnode);
+ }
+ if (status) {
+ *status = w.status;
+ }
+ rb_native_cond_destroy(&w.cond);
+ errno = w.errnum;
+ return w.ret;
+}
+
+static void
+waitpid_ubf(void *x)
+{
+ struct waitpid_state *w = x;
+ rb_nativethread_lock_lock(&w->vm->waitpid_lock);
+ if (!w->ret) {
+ w->errnum = EINTR;
+ w->ret = -1;
+ }
+ rb_native_cond_signal(&w->cond);
+ rb_nativethread_lock_unlock(&w->vm->waitpid_lock);
+}
+
static void *
-rb_waitpid_blocking(void *data)
+waitpid_nogvl(void *x)
{
- struct waitpid_arg *arg = data;
- rb_pid_t result = do_waitpid(arg->pid, arg->st, arg->flags);
- return (void *)(VALUE)result;
+ struct waitpid_state *w = x;
+
+ /* let rb_sigchld handle it */
+ rb_native_cond_wait(&w->cond, &w->vm->waitpid_lock);
+
+ return 0;
}
-static rb_pid_t
-do_waitpid_nonblocking(rb_pid_t pid, int *st, int flags)
+static VALUE
+waitpid_wait(VALUE x)
+{
+ struct waitpid_state *w = (struct waitpid_state *)x;
+
+ rb_nativethread_lock_lock(&w->vm->waitpid_lock);
+ w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
+ if (w->ret) {
+ if (w->ret == -1) {
+ w->errnum = errno;
+ }
+ }
+ else {
+ rb_execution_context_t *ec = GET_EC();
+
+ list_add(&w->vm->waiting_pids, &w->wnode);
+ do {
+ rb_thread_call_without_gvl2(waitpid_nogvl, w, waitpid_ubf, w);
+ if (RUBY_VM_INTERRUPTED_ANY(ec) ||
+ (w->ret == -1 && w->errnum == EINTR)) {
+ rb_nativethread_lock_unlock(&w->vm->waitpid_lock);
+
+ RUBY_VM_CHECK_INTS(ec);
+
+ rb_nativethread_lock_lock(&w->vm->waitpid_lock);
+ if (w->ret == -1 && w->errnum == EINTR) {
+ w->ret = do_waitpid(w->pid, &w->status, w->options|WNOHANG);
+ if (w->ret == -1)
+ w->errnum = errno;
+ }
+ }
+ } while (!w->ret);
+ }
+ rb_nativethread_lock_unlock(&w->vm->waitpid_lock);
+ return Qfalse;
+}
+
+static VALUE
+waitpid_ensure(VALUE x)
{
- void *result;
- struct waitpid_arg arg;
- arg.pid = pid;
- arg.st = st;
- arg.flags = flags;
- result = rb_thread_call_without_gvl(rb_waitpid_blocking, &arg,
- RUBY_UBF_PROCESS, 0);
- return (rb_pid_t)(VALUE)result;
+ struct waitpid_state *w = (struct waitpid_state *)x;
+
+ if (w->ret <= 0) {
+ rb_nativethread_lock_lock(&w->vm->waitpid_lock);
+ list_del_init(&w->wnode);
+ rb_nativethread_lock_unlock(&w->vm->waitpid_lock);
+ }
+
+ rb_native_cond_destroy(&w->cond);
+ return Qfalse;
}
rb_pid_t
@@ -933,10 +1057,14 @@ rb_waitpid(rb_pid_t pid, int *st, int flags)
result = do_waitpid(pid, st, flags);
}
else {
- while ((result = do_waitpid_nonblocking(pid, st, flags)) < 0 &&
- (errno == EINTR)) {
- RUBY_VM_CHECK_INTS(GET_EC());
- }
+ struct waitpid_state w;
+
+ waitpid_state_init(&w, GET_VM(), pid, flags);
+ rb_ensure(waitpid_wait, (VALUE)&w, waitpid_ensure, (VALUE)&w);
+ if (st) {
+ *st = w.status;
+ }
+ result = w.ret;
}
if (result > 0) {
rb_last_status_set(*st, result);
@@ -4081,16 +4209,6 @@ rb_f_system(int argc, VALUE *argv)
VALUE execarg_obj;
struct rb_execarg *eargp;
-#if defined(SIGCLD) && !defined(SIGCHLD)
-# define SIGCHLD SIGCLD
-#endif
-
-#ifdef SIGCHLD
- RETSIGTYPE (*chfunc)(int);
-
- rb_last_status_clear();
- chfunc = signal(SIGCHLD, SIG_DFL);
-#endif
execarg_obj = rb_execarg_new(argc, argv, TRUE, TRUE);
pid = rb_execarg_spawn(execarg_obj, NULL, 0);
#if defined(HAVE_WORKING_FORK) || defined(HAVE_SPAWNV)
@@ -4100,9 +4218,6 @@ rb_f_system(int argc, VALUE *argv)
if (ret == (rb_pid_t)-1)
rb_sys_fail("Another thread waited the process started by system().");
}
-#endif
-#ifdef SIGCHLD
- signal(SIGCHLD, chfunc);
#endif
TypedData_Get_Struct(execarg_obj, struct rb_execarg, &exec_arg_data_type, eargp);
if (pid < 0) {
diff --git a/signal.c b/signal.c
index c781c38c62..c20b01ea36 100644
--- a/signal.c
+++ b/signal.c
@@ -1052,6 +1052,19 @@ rb_trap_exit(void)
}
}
+static int
+sig_is_chld(int sig)
+{
+#if defined(SIGCLD)
+ return (sig == SIGCLD);
+#elif defined(SIGCHLD)
+ return (sig == SIGCHLD);
+#endif
+ return 0;
+}
+
+void rb_sigchld(rb_vm_t *); /* process.c */
+
void
rb_signal_exec(rb_thread_t *th, int sig)
{
@@ -1059,6 +1072,9 @@ rb_signal_exec(rb_thread_t *th, int sig)
VALUE cmd = vm->trap_list.cmd[sig];
int safe = vm->trap_list.safe[sig];
+ if (sig_is_chld(sig)) {
+ rb_sigchld(vm);
+ }
if (cmd == 0) {
switch (sig) {
case SIGINT:
@@ -1117,6 +1133,11 @@ default_handler(int sig)
#endif
#ifdef SIGUSR2
case SIGUSR2:
+#endif
+#ifdef SIGCLD
+ case SIGCLD:
+#elif defined(SIGCHLD)
+ case SIGCHLD:
#endif
func = sighandler;
break;
@@ -1155,6 +1176,9 @@ trap_handler(VALUE *cmd, int sig)
VALUE command;
if (NIL_P(*cmd)) {
+ if (sig_is_chld(sig)) {
+ goto sig_dfl;
+ }
func = SIG_IGN;
}
else {
@@ -1175,6 +1199,9 @@ trap_handler(VALUE *cmd, int sig)
break;
case 14:
if (memcmp(cptr, "SYSTEM_DEFAULT", 14) == 0) {
+ if (sig_is_chld(sig)) {
+ goto sig_dfl;
+ }
func = SIG_DFL;
*cmd = 0;
}
@@ -1182,6 +1209,9 @@ trap_handler(VALUE *cmd, int sig)
case 7:
if (memcmp(cptr, "SIG_IGN", 7) == 0) {
sig_ign:
+ if (sig_is_chld(sig)) {
+ goto sig_dfl;
+ }
func = SIG_IGN;
*cmd = Qtrue;
}
@@ -1418,15 +1448,13 @@ static int
init_sigchld(int sig)
{
sighandler_t oldfunc;
+ sighandler_t func = sighandler;
oldfunc = ruby_signal(sig, SIG_DFL);
if (oldfunc == SIG_ERR) return -1;
- if (oldfunc != SIG_DFL && oldfunc != SIG_IGN) {
- ruby_signal(sig, oldfunc);
- }
- else {
- GET_VM()->trap_list.cmd[sig] = 0;
- }
+ ruby_signal(sig, func);
+ GET_VM()->trap_list.cmd[sig] = 0;
+
return 0;
}
diff --git a/thread.c b/thread.c
index 5a5e32379d..8c9aafe07a 100644
--- a/thread.c
+++ b/thread.c
@@ -413,6 +413,7 @@ rb_vm_gvl_destroy(rb_vm_t *vm)
gvl_release(vm);
gvl_destroy(vm);
rb_native_mutex_destroy(&vm->thread_destruct_lock);
+ rb_native_mutex_destroy(&vm->waitpid_lock);
}
void
@@ -4999,6 +5000,7 @@ Init_Thread(void)
gvl_init(th->vm);
gvl_acquire(th->vm, th);
rb_native_mutex_initialize(&th->vm->thread_destruct_lock);
+ rb_native_mutex_initialize(&th->vm->waitpid_lock);
rb_native_mutex_initialize(&th->interrupt_lock);
th->pending_interrupt_queue = rb_ary_tmp_new(0);
diff --git a/vm_core.h b/vm_core.h
index ee151195d5..d0689e5ba6 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -553,6 +553,8 @@ typedef struct rb_vm_struct {
#endif
rb_serial_t fork_gen;
+ rb_nativethread_lock_t waitpid_lock;
+ struct list_head waiting_pids; /* <=> struct waitpid_state */
struct list_head waiting_fds; /* <=> struct waiting_fd */
struct list_head living_threads;
VALUE thgroup_default;
@@ -1561,6 +1563,7 @@ static inline void
rb_vm_living_threads_init(rb_vm_t *vm)
{
list_head_init(&vm->waiting_fds);
+ list_head_init(&vm->waiting_pids);
list_head_init(&vm->living_threads);
vm->living_thread_num = 0;
}
--
EW
reply other threads:[~2018-06-23 15:58 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180623155826.29681-1-e@80x24.org \
--to=e@80x24.org \
--cc=spew@80x24.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).