dumping ground for random patches and texts
 help / color / mirror / Atom feed
* [PATCHv3 0/8] mjit SIGCHLD hijack series
@ 2018-06-25 23:50 Eric Wong
  2018-06-25 23:50 ` [PATCH 1/8] hijack SIGCHLD handler for internal use Eric Wong
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew

The following changes since commit 4444025d16ae1a586eee6a0ac9bdd09e33833f3c:

  mjit_compile.inc.erb: drop unnecessary variable (2018-06-25 14:15:26 +0000)

are available in the Git repository at:

  git://80x24.org/ruby.git mjit-chld

for you to fetch changes up to 25400c556e48f01ec8acb1bb8ce3c15b391485dc:

  wip testing (2018-06-25 23:44:47 +0000)

----------------------------------------------------------------
Eric Wong (8):
      hijack SIGCHLD handler for internal use
      fix SIGCHLD hijacking race conditions
      mjit.c: allow working on platforms without SIGCHLD
      cleanups
      handle SIGCHLD in both the timer-thread and main thread
      Revert "test_process.rb: skip tests for Bug 14867"
      Revert "spec: skip Process wait specs on MJIT"
      wip testing

 mjit.c                                 |  64 ++++++++--
 process.c                              | 222 ++++++++++++++++++++++++++-------
 signal.c                               |  71 +++++++----
 spec/mspec/lib/mspec/guards/feature.rb |   6 -
 spec/ruby/core/process/wait2_spec.rb   |  26 ++--
 spec/ruby/core/process/wait_spec.rb    | 122 +++++++++---------
 spec/ruby/core/process/waitall_spec.rb |  66 +++++-----
 test/ruby/test_process.rb              |   3 -
 thread.c                               |  31 +++++
 thread_pthread.c                       |  18 +++
 vm_core.h                              |  11 ++
 11 files changed, 443 insertions(+), 197 deletions(-)


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/8] hijack SIGCHLD handler for internal use
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 2/8] fix SIGCHLD hijacking race conditions Eric Wong
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

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 07e9328aff..4aa1524a55 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"
 
@@ -118,6 +119,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
@@ -403,21 +407,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;
 }
 
@@ -1497,6 +1512,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());
     }
 }
 
@@ -1532,6 +1548,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.  */
@@ -1551,7 +1582,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 12ea6eac86..c92351b83d 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);
@@ -4086,16 +4214,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)
@@ -4105,9 +4223,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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/8] fix SIGCHLD hijacking race conditions
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
  2018-06-25 23:50 ` [PATCH 1/8] hijack SIGCHLD handler for internal use Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 3/8] mjit.c: allow working on platforms without SIGCHLD Eric Wong
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

From: Eric Wong <normalperson@yhbt.net>

It's a bit tricky, and I introduced yet another sleep function
(rb_thread_sleep_interruptible) to make it work.

Original problem in my patch was holding a native mutex while
forking is dangerous, so I went to normal ruby sleep methods
(same as Mutex and autoload).

However, it's impossible to resume main thread from signal
handler because rb_threadptr_execute_interrupts always restores
th->status after trap handlers.  In other words, this doesn't work:

        main = Thread.current
        trap(:USR1) { main.run }
        Thread.new { sleep 0.1; Process.kill(:USR1, $$) }
        sleep

So I tried moving rb_sigchld/rb_waitpid_all into timer-thread;
but that's racy and not doable unless the Ruby thread holds
a native mutex while sleeping on a native condvar (w/o GVL).
And that brings us back to the original problem...
(Well, I could use futex :P)

Now, back to using the main thread to handle SIGCHLD...

It seems to be working...
---
 mjit.c    |  13 ++++--
 process.c | 156 +++++++++++++++++++++++++++++---------------------------------
 signal.c  |  66 +++++++++++---------------
 thread.c  |  11 +++++
 4 files changed, 120 insertions(+), 126 deletions(-)

diff --git a/mjit.c b/mjit.c
index 4aa1524a55..63cd35da21 100644
--- a/mjit.c
+++ b/mjit.c
@@ -120,7 +120,8 @@ 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);
+pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options,
+                          rb_nativethread_cond_t *cond);
 
 #define RB_CONDATTR_CLOCK_MONOTONIC 1
 
@@ -408,6 +409,7 @@ exec_process(const char *path, char *const argv[])
     int stat, exit_code;
     pid_t pid;
     rb_vm_t *vm = GET_VM();
+    rb_nativethread_cond_t cond;
 
     rb_nativethread_lock_lock(&vm->waitpid_lock);
     pid = start_process(path, argv);
@@ -415,11 +417,13 @@ exec_process(const char *path, char *const argv[])
         rb_nativethread_lock_unlock(&vm->waitpid_lock);
         return -2;
     }
+    rb_native_cond_initialize(&cond);
     for (;;) {
-        pid_t r = ruby_waitpid_locked(vm, pid, &stat, 0);
+        pid_t r = ruby_waitpid_locked(vm, pid, &stat, 0, &cond);
         if (r == -1) {
-            if (errno == EINTR) continue;
+            if (errno == EINTR) continue; /* should never happen */
             fprintf(stderr, "waitpid: %s\n", strerror(errno));
+            exit_code = -2;
             break;
         }
         else if (r == pid) {
@@ -433,6 +437,7 @@ exec_process(const char *path, char *const argv[])
         }
     }
     rb_nativethread_lock_unlock(&vm->waitpid_lock);
+    rb_native_cond_destroy(&cond);
     return exit_code;
 }
 
@@ -1582,7 +1587,7 @@ 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");
-	/* release GVL to handle interrupts */
+        /* 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 c92351b83d..9f0995e194 100644
--- a/process.c
+++ b/process.c
@@ -899,153 +899,143 @@ do_waitpid(rb_pid_t pid, int *st, int flags)
 
 struct waitpid_state {
     struct list_node wnode;
-    rb_nativethread_cond_t cond;
+    union {
+        rb_nativethread_cond_t *cond; /* non-Ruby thread */
+        rb_execution_context_t *ec; /* normal Ruby execution context */
+    } wake;
     rb_pid_t ret;
     rb_pid_t pid;
     int status;
     int options;
     int errnum;
-    rb_vm_t *vm;
+    unsigned int is_ruby : 1;
 };
 
+void rb_native_mutex_lock(rb_nativethread_lock_t *);
+void rb_native_mutex_unlock(rb_nativethread_lock_t *);
 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 */
+/* called by vm->main_thread */
 void
-rb_sigchld(rb_vm_t *vm)
+rb_waitpid_all(rb_vm_t *vm)
 {
     struct waitpid_state *w = 0, *next;
 
-    rb_nativethread_lock_lock(&vm->waitpid_lock);
+    rb_native_mutex_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);
+        if (w->is_ruby) {
+            rb_thread_wakeup_alive(rb_ec_thread_ptr(w->wake.ec)->self);
+        }
+        else {
+            rb_native_cond_signal(w->wake.cond);
+        }
     }
-    rb_nativethread_lock_unlock(&vm->waitpid_lock);
+    rb_native_mutex_unlock(&vm->waitpid_lock);
 }
 
 static void
-waitpid_state_init(struct waitpid_state *w, rb_vm_t *vm, pid_t pid, int options)
+waitpid_state_init(struct waitpid_state *w, 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 */
+/*
+ * 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)
+ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options,
+                    rb_nativethread_cond_t *cond)
 {
     struct waitpid_state w;
 
     assert(!ruby_thread_has_gvl_p() && "must not have GVL");
 
-    waitpid_state_init(&w, vm, pid, options);
+    waitpid_state_init(&w, pid, options);
+    w.is_ruby = 0;
     w.ret = do_waitpid(w.pid, &w.status, w.options | WNOHANG);
     if (w.ret) {
-        if (w.ret == -1) {
-            w.errnum = errno;
-        }
+        if (w.ret == -1) w.errnum = errno;
     }
     else {
+        w.wake.cond = cond;
         list_add(&vm->waiting_pids, &w.wnode);
-        while (!w.ret) {
-            rb_native_cond_wait(&w.cond, &vm->waitpid_lock);
-        }
+        do {
+            rb_native_cond_wait(w.wake.cond, &vm->waitpid_lock);
+        } while (!w.ret);
         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);
-}
+void rb_thread_sleep_interruptible(struct timespec *ts); /* thread.c */
 
-static void *
-waitpid_nogvl(void *x)
+static VALUE
+waitpid_sleep(VALUE x)
 {
-    struct waitpid_state *w = x;
+    struct waitpid_state *w = (struct waitpid_state *)x;
 
-    /* let rb_sigchld handle it */
-    rb_native_cond_wait(&w->cond, &w->vm->waitpid_lock);
+    rb_thread_check_ints();
+    while (!w->ret) {
+        rb_thread_sleep_interruptible(0);
+        rb_thread_check_ints();
+    }
 
-    return 0;
+    return Qfalse;
 }
 
 static VALUE
-waitpid_wait(VALUE x)
+waitpid_ensure(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();
+    if (w->ret == 0) {
+        rb_vm_t *vm = rb_ec_vm_ptr(w->wake.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_native_mutex_lock(&vm->waitpid_lock);
+        list_del(&w->wnode);
+        rb_native_mutex_unlock(&vm->waitpid_lock);
     }
-    rb_nativethread_lock_unlock(&w->vm->waitpid_lock);
     return Qfalse;
 }
 
-static VALUE
-waitpid_ensure(VALUE x)
+static void
+waitpid_wait(struct waitpid_state *w)
 {
-    struct waitpid_state *w = (struct waitpid_state *)x;
+    rb_vm_t *vm = rb_ec_vm_ptr(w->wake.ec);
 
-    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);
+    /*
+     * Lock here to prevent do_waitpid from stealing work from the
+     * ruby_waitpid_locked done by mjit workers since mjit works
+     * outside of GVL
+     */
+    rb_native_mutex_lock(&vm->waitpid_lock);
+
+    w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
+    if (w->ret) {
+        if (w->ret == -1) w->errnum = errno;
+
+        rb_native_mutex_unlock(&vm->waitpid_lock);
     }
+    else {
+        list_add(&vm->waiting_pids, &w->wnode);
+        rb_native_mutex_unlock(&vm->waitpid_lock);
 
-    rb_native_cond_destroy(&w->cond);
-    return Qfalse;
+        rb_ensure(waitpid_sleep, (VALUE)w, waitpid_ensure, (VALUE)w);
+    }
 }
 
 rb_pid_t
@@ -1059,11 +1049,11 @@ rb_waitpid(rb_pid_t pid, int *st, int flags)
     else {
         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;
-        }
+        waitpid_state_init(&w, pid, flags);
+        w.is_ruby = 1;
+        w.wake.ec = GET_EC();
+        waitpid_wait(&w);
+        if (st) *st = w.status;
         result = w.ret;
     }
     if (result > 0) {
diff --git a/signal.c b/signal.c
index c20b01ea36..22fc4286d9 100644
--- a/signal.c
+++ b/signal.c
@@ -66,6 +66,14 @@ ruby_atomic_compare_and_swap(rb_atomic_t *ptr, rb_atomic_t cmp,
 # define NSIG (_SIGMAX + 1)      /* For QNX */
 #endif
 
+#if defined(SIGCLD)
+#  define RUBY_SIGCHLD    (SIGCLD)
+#elif defined(SIGCHLD)
+#  define RUBY_SIGCHLD    (SIGCHLD)
+#else
+#  define RUBY_SIGCHLD    (0)
+#endif
+
 static const struct signals {
     const char *signm;
     int  signo;
@@ -129,15 +137,9 @@ static const struct signals {
 #ifdef SIGCONT
     {"CONT", SIGCONT},
 #endif
-#ifdef SIGCHLD
-    {"CHLD", SIGCHLD},
-#endif
-#ifdef SIGCLD
-    {"CLD", SIGCLD},
-#else
-# ifdef SIGCHLD
-    {"CLD", SIGCHLD},
-# endif
+#if RUBY_SIGCHLD
+    {"CHLD", RUBY_SIGCHLD },
+    {"CLD", RUBY_SIGCHLD },
 #endif
 #ifdef SIGTTIN
     {"TTIN", SIGTTIN},
@@ -1052,18 +1054,7 @@ 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_waitpid_all(rb_vm_t *); /* process.c */
 
 void
 rb_signal_exec(rb_thread_t *th, int sig)
@@ -1072,9 +1063,10 @@ 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 (sig == RUBY_SIGCHLD) {
+        rb_waitpid_all(vm);
     }
+
     if (cmd == 0) {
 	switch (sig) {
 	  case SIGINT:
@@ -1134,10 +1126,8 @@ default_handler(int sig)
 #ifdef SIGUSR2
       case SIGUSR2:
 #endif
-#ifdef SIGCLD
-      case SIGCLD:
-#elif defined(SIGCHLD)
-      case SIGCHLD:
+#if RUBY_SIGCHLD
+      case RUBY_SIGCHLD:
 #endif
         func = sighandler;
         break;
@@ -1176,7 +1166,7 @@ trap_handler(VALUE *cmd, int sig)
     VALUE command;
 
     if (NIL_P(*cmd)) {
-	if (sig_is_chld(sig)) {
+	if (sig == RUBY_SIGCHLD) {
 	    goto sig_dfl;
 	}
 	func = SIG_IGN;
@@ -1199,9 +1189,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;
-		    }
+                    if (sig == RUBY_SIGCHLD) {
+                        goto sig_dfl;
+                    }
                     func = SIG_DFL;
                     *cmd = 0;
 		}
@@ -1209,9 +1199,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;
-		    }
+                    if (sig == RUBY_SIGCHLD) {
+                        goto sig_dfl;
+                    }
                     func = SIG_IGN;
                     *cmd = Qtrue;
 		}
@@ -1443,7 +1433,7 @@ install_sighandler(int signum, sighandler_t handler)
 #  define install_sighandler(signum, handler) \
     INSTALL_SIGHANDLER(install_sighandler(signum, handler), #signum, signum)
 
-#if defined(SIGCLD) || defined(SIGCHLD)
+#if RUBY_SIGCHLD
 static int
 init_sigchld(int sig)
 {
@@ -1570,10 +1560,8 @@ Init_signal(void)
     install_sighandler(SIGSYS, sig_do_nothing);
 #endif
 
-#if defined(SIGCLD)
-    init_sigchld(SIGCLD);
-#elif defined(SIGCHLD)
-    init_sigchld(SIGCHLD);
+#if RUBY_SIGCHLD
+    init_sigchld(RUBY_SIGCHLD);
 #endif
 
     rb_enable_interrupt();
diff --git a/thread.c b/thread.c
index 8c9aafe07a..ab27c60632 100644
--- a/thread.c
+++ b/thread.c
@@ -1287,6 +1287,17 @@ rb_thread_sleep_forever(void)
     sleep_forever(GET_THREAD(), SLEEP_SPURIOUS_CHECK);
 }
 
+void
+rb_thread_sleep_interruptible(struct timespec *ts)
+{
+    rb_thread_t *th = GET_THREAD();
+    enum rb_thread_status prev_status = th->status;
+
+    th->status = THREAD_STOPPED;
+    native_sleep(th, ts);
+    th->status = prev_status;
+}
+
 void
 rb_thread_sleep_deadly(void)
 {
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/8] mjit.c: allow working on platforms without SIGCHLD
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
  2018-06-25 23:50 ` [PATCH 1/8] hijack SIGCHLD handler for internal use Eric Wong
  2018-06-25 23:50 ` [PATCH 2/8] fix SIGCHLD hijacking race conditions Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 4/8] cleanups Eric Wong
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

While we're at it, simplify lock management in exec_process by
avoiding early returns.
---
 mjit.c | 33 ++++++++++++++++++++-------------
 1 file changed, 20 insertions(+), 13 deletions(-)

diff --git a/mjit.c b/mjit.c
index 63cd35da21..659461cea6 100644
--- a/mjit.c
+++ b/mjit.c
@@ -137,6 +137,7 @@ pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options,
 #define WEXITSTATUS(S) (S)
 #define WIFSIGNALED(S) (0)
 typedef intptr_t pid_t;
+#define USE_RUBY_WAITPID_LOCKED (0)
 #endif
 
 /* Atomically set function pointer if possible. */
@@ -150,6 +151,10 @@ typedef intptr_t pid_t;
 # define MJIT_ATOMIC_SET(var, val) ATOMIC_SET(var, val)
 #endif
 
+#ifndef USE_RUBY_WAITPID_LOCKED /* platforms with real waitpid */
+#  define USE_RUBY_WAITPID_LOCKED (1)
+#endif /* USE_RUBY_WAITPID_LOCKED */
+
 /* A copy of MJIT portion of MRI options since MJIT initialization.  We
    need them as MJIT threads still can work when the most MRI data were
    freed. */
@@ -406,24 +411,23 @@ start_process(const char *path, char *const *argv)
 static int
 exec_process(const char *path, char *const argv[])
 {
-    int stat, exit_code;
+    int stat, exit_code = -2;
     pid_t pid;
-    rb_vm_t *vm = GET_VM();
+    rb_vm_t *vm = USE_RUBY_WAITPID_LOCKED ? GET_VM() : 0;
     rb_nativethread_cond_t cond;
 
-    rb_nativethread_lock_lock(&vm->waitpid_lock);
-    pid = start_process(path, argv);
-    if (pid <= 0) {
-        rb_nativethread_lock_unlock(&vm->waitpid_lock);
-        return -2;
+    if (vm) {
+        rb_native_cond_initialize(&cond);
+        rb_nativethread_lock_lock(&vm->waitpid_lock);
     }
-    rb_native_cond_initialize(&cond);
-    for (;;) {
-        pid_t r = ruby_waitpid_locked(vm, pid, &stat, 0, &cond);
+
+    pid = start_process(path, argv);
+    for (;pid > 0;) {
+        pid_t r = vm ? ruby_waitpid_locked(vm, pid, &stat, 0, &cond)
+                     : waitpid(pid, &stat, 0);
         if (r == -1) {
             if (errno == EINTR) continue; /* should never happen */
             fprintf(stderr, "waitpid: %s\n", strerror(errno));
-            exit_code = -2;
             break;
         }
         else if (r == pid) {
@@ -436,8 +440,11 @@ exec_process(const char *path, char *const argv[])
             }
         }
     }
-    rb_nativethread_lock_unlock(&vm->waitpid_lock);
-    rb_native_cond_destroy(&cond);
+
+    if (vm) {
+        rb_native_cond_destroy(&cond);
+        rb_nativethread_lock_unlock(&vm->waitpid_lock);
+    }
     return exit_code;
 }
 
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/8] cleanups
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
                   ` (2 preceding siblings ...)
  2018-06-25 23:50 ` [PATCH 3/8] mjit.c: allow working on platforms without SIGCHLD Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 5/8] handle SIGCHLD in both the timer-thread and main thread Eric Wong
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

---
 mjit.c    |  9 ++-------
 signal.c  | 12 ------------
 vm_core.h |  8 ++++++++
 3 files changed, 10 insertions(+), 19 deletions(-)

diff --git a/mjit.c b/mjit.c
index 659461cea6..a550d1aabd 100644
--- a/mjit.c
+++ b/mjit.c
@@ -119,7 +119,7 @@ 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));
 
-
+/* process.c */
 pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options,
                           rb_nativethread_cond_t *cond);
 
@@ -137,7 +137,6 @@ pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options,
 #define WEXITSTATUS(S) (S)
 #define WIFSIGNALED(S) (0)
 typedef intptr_t pid_t;
-#define USE_RUBY_WAITPID_LOCKED (0)
 #endif
 
 /* Atomically set function pointer if possible. */
@@ -151,10 +150,6 @@ typedef intptr_t pid_t;
 # define MJIT_ATOMIC_SET(var, val) ATOMIC_SET(var, val)
 #endif
 
-#ifndef USE_RUBY_WAITPID_LOCKED /* platforms with real waitpid */
-#  define USE_RUBY_WAITPID_LOCKED (1)
-#endif /* USE_RUBY_WAITPID_LOCKED */
-
 /* A copy of MJIT portion of MRI options since MJIT initialization.  We
    need them as MJIT threads still can work when the most MRI data were
    freed. */
@@ -413,7 +408,7 @@ exec_process(const char *path, char *const argv[])
 {
     int stat, exit_code = -2;
     pid_t pid;
-    rb_vm_t *vm = USE_RUBY_WAITPID_LOCKED ? GET_VM() : 0;
+    rb_vm_t *vm = RUBY_SIGCHLD ? GET_VM() : 0;
     rb_nativethread_cond_t cond;
 
     if (vm) {
diff --git a/signal.c b/signal.c
index 22fc4286d9..b816403cb9 100644
--- a/signal.c
+++ b/signal.c
@@ -62,18 +62,6 @@ ruby_atomic_compare_and_swap(rb_atomic_t *ptr, rb_atomic_t cmp,
 }
 #endif
 
-#ifndef NSIG
-# define NSIG (_SIGMAX + 1)      /* For QNX */
-#endif
-
-#if defined(SIGCLD)
-#  define RUBY_SIGCHLD    (SIGCLD)
-#elif defined(SIGCHLD)
-#  define RUBY_SIGCHLD    (SIGCHLD)
-#else
-#  define RUBY_SIGCHLD    (0)
-#endif
-
 static const struct signals {
     const char *signm;
     int  signo;
diff --git a/vm_core.h b/vm_core.h
index d0689e5ba6..936b22d439 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -92,6 +92,14 @@
 
 #define RUBY_NSIG NSIG
 
+#if defined(SIGCLD)
+#  define RUBY_SIGCHLD    (SIGCLD)
+#elif defined(SIGCHLD)
+#  define RUBY_SIGCHLD    (SIGCHLD)
+#else
+#  define RUBY_SIGCHLD    (0)
+#endif
+
 #ifdef HAVE_STDARG_PROTOTYPES
 #include <stdarg.h>
 #define va_init_list(a,b) va_start((a),(b))
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 5/8] handle SIGCHLD in both the timer-thread and main thread
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
                   ` (3 preceding siblings ...)
  2018-06-25 23:50 ` [PATCH 4/8] cleanups Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 6/8] Revert "test_process.rb: skip tests for Bug 14867" Eric Wong
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

It is needed to avoid race conditions.  I could use
separate counters, but the long-term goal is to make
timer-thread unnecessary for single-threaded programs.
---
 process.c        | 113 ++++++++++++++++++++++++++++++++++---------------------
 signal.c         |  25 ++++++++++--
 thread.c         |  42 +++++++++++++++------
 thread_pthread.c |  18 +++++++++
 4 files changed, 140 insertions(+), 58 deletions(-)

diff --git a/process.c b/process.c
index 9f0995e194..0b91b30075 100644
--- a/process.c
+++ b/process.c
@@ -899,42 +899,51 @@ do_waitpid(rb_pid_t pid, int *st, int flags)
 
 struct waitpid_state {
     struct list_node wnode;
-    union {
-        rb_nativethread_cond_t *cond; /* non-Ruby thread */
-        rb_execution_context_t *ec; /* normal Ruby execution context */
-    } wake;
+    rb_execution_context_t *ec;
+    rb_nativethread_cond_t *cond;
     rb_pid_t ret;
     rb_pid_t pid;
     int status;
     int options;
     int errnum;
-    unsigned int is_ruby : 1;
 };
 
 void rb_native_mutex_lock(rb_nativethread_lock_t *);
 void rb_native_mutex_unlock(rb_nativethread_lock_t *);
 void rb_native_cond_signal(rb_nativethread_cond_t *);
 void rb_native_cond_wait(rb_nativethread_cond_t *, rb_nativethread_lock_t *);
+rb_nativethread_cond_t *rb_sleep_cond_get(const rb_execution_context_t *);
+void rb_sleep_cond_put(rb_nativethread_cond_t *);
 
-/* called by vm->main_thread */
+static void
+waitpid_notify(struct waitpid_state *w, pid_t ret)
+{
+    w->ret = ret;
+    if (w->ret == -1) w->errnum = errno;
+    list_del_init(&w->wnode);
+    rb_native_cond_signal(w->cond);
+}
+
+/* called by both timer thread and main thread */
 void
-rb_waitpid_all(rb_vm_t *vm)
+ruby_waitpid_all(rb_vm_t *vm)
 {
     struct waitpid_state *w = 0, *next;
 
     rb_native_mutex_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);
+        pid_t ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
+        if (!ret) continue;
 
-        if (w->ret == 0) continue;
-        if (w->ret == -1) w->errnum = errno;
+        if (w->ec) { /* rb_waitpid */
+            rb_thread_t *th = rb_ec_thread_ptr(w->ec);
 
-        list_del_init(&w->wnode);
-        if (w->is_ruby) {
-            rb_thread_wakeup_alive(rb_ec_thread_ptr(w->wake.ec)->self);
+            rb_native_mutex_lock(&th->interrupt_lock);
+            waitpid_notify(w, ret);
+            rb_native_mutex_unlock(&th->interrupt_lock);
         }
-        else {
-            rb_native_cond_signal(w->wake.cond);
+        else { /* ruby_waitpid_locked */
+            waitpid_notify(w, ret);
         }
     }
     rb_native_mutex_unlock(&vm->waitpid_lock);
@@ -946,7 +955,6 @@ waitpid_state_init(struct waitpid_state *w, pid_t pid, int options)
     w->ret = 0;
     w->pid = pid;
     w->options = options;
-    list_node_init(&w->wnode);
 }
 
 /*
@@ -961,16 +969,16 @@ ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options,
     assert(!ruby_thread_has_gvl_p() && "must not have GVL");
 
     waitpid_state_init(&w, pid, options);
-    w.is_ruby = 0;
     w.ret = do_waitpid(w.pid, &w.status, w.options | WNOHANG);
     if (w.ret) {
         if (w.ret == -1) w.errnum = errno;
     }
     else {
-        w.wake.cond = cond;
+        w.cond = cond;
+        w.ec = 0;
         list_add(&vm->waiting_pids, &w.wnode);
         do {
-            rb_native_cond_wait(w.wake.cond, &vm->waitpid_lock);
+            rb_native_cond_wait(w.cond, &vm->waitpid_lock);
         } while (!w.ret);
         list_del(&w.wnode);
     }
@@ -981,41 +989,63 @@ ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options,
     return w.ret;
 }
 
-void rb_thread_sleep_interruptible(struct timespec *ts); /* thread.c */
+static void
+waitpid_wake(void *x)
+{
+    struct waitpid_state *w = x;
+
+    /* th->interrupt_lock is already held by rb_threadptr_interrupt_common */
+    rb_native_cond_signal(w->cond);
+}
+
+static void *
+waitpid_nogvl(void *x)
+{
+    struct waitpid_state *w = x;
+    rb_thread_t *th = rb_ec_thread_ptr(w->ec);
+
+    rb_native_mutex_lock(&th->interrupt_lock);
+    if (!w->ret) { /* we must check this before waiting */
+        rb_native_cond_wait(w->cond, &th->interrupt_lock);
+    }
+    rb_native_mutex_unlock(&th->interrupt_lock);
+
+    return 0;
+}
 
 static VALUE
 waitpid_sleep(VALUE x)
 {
     struct waitpid_state *w = (struct waitpid_state *)x;
 
-    rb_thread_check_ints();
     while (!w->ret) {
-        rb_thread_sleep_interruptible(0);
-        rb_thread_check_ints();
+        rb_thread_call_without_gvl(waitpid_nogvl, w, waitpid_wake, w);
     }
 
     return Qfalse;
 }
 
 static VALUE
-waitpid_ensure(VALUE x)
+waitpid_cleanup(VALUE x)
 {
     struct waitpid_state *w = (struct waitpid_state *)x;
 
     if (w->ret == 0) {
-        rb_vm_t *vm = rb_ec_vm_ptr(w->wake.ec);
+        rb_vm_t *vm = rb_ec_vm_ptr(w->ec);
 
         rb_native_mutex_lock(&vm->waitpid_lock);
         list_del(&w->wnode);
         rb_native_mutex_unlock(&vm->waitpid_lock);
     }
+    rb_sleep_cond_put(w->cond);
+
     return Qfalse;
 }
 
 static void
 waitpid_wait(struct waitpid_state *w)
 {
-    rb_vm_t *vm = rb_ec_vm_ptr(w->wake.ec);
+    rb_vm_t *vm = rb_ec_vm_ptr(w->ec);
 
     /*
      * Lock here to prevent do_waitpid from stealing work from the
@@ -1025,16 +1055,19 @@ waitpid_wait(struct waitpid_state *w)
     rb_native_mutex_lock(&vm->waitpid_lock);
 
     w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
-    if (w->ret) {
+    if (w->ret || (w->options & WNOHANG)) {
+        w->cond = 0;
         if (w->ret == -1) w->errnum = errno;
-
-        rb_native_mutex_unlock(&vm->waitpid_lock);
     }
     else {
+        w->cond = rb_sleep_cond_get(w->ec);
         list_add(&vm->waiting_pids, &w->wnode);
-        rb_native_mutex_unlock(&vm->waitpid_lock);
+    }
+
+    rb_native_mutex_unlock(&vm->waitpid_lock);
 
-        rb_ensure(waitpid_sleep, (VALUE)w, waitpid_ensure, (VALUE)w);
+    if (w->cond) {
+        rb_ensure(waitpid_sleep, (VALUE)w, waitpid_cleanup, (VALUE)w);
     }
 }
 
@@ -1042,20 +1075,14 @@ rb_pid_t
 rb_waitpid(rb_pid_t pid, int *st, int flags)
 {
     rb_pid_t result;
+    struct waitpid_state w;
 
-    if (flags & WNOHANG) {
-	result = do_waitpid(pid, st, flags);
-    }
-    else {
-        struct waitpid_state w;
+    waitpid_state_init(&w, pid, flags);
+    w.ec = GET_EC();
+    waitpid_wait(&w);
+    if (st) *st = w.status;
+    result = w.ret;
 
-        waitpid_state_init(&w, pid, flags);
-        w.is_ruby = 1;
-        w.wake.ec = GET_EC();
-        waitpid_wait(&w);
-        if (st) *st = w.status;
-        result = w.ret;
-    }
     if (result > 0) {
 	rb_last_status_set(*st, result);
     }
diff --git a/signal.c b/signal.c
index b816403cb9..9f74c43c5d 100644
--- a/signal.c
+++ b/signal.c
@@ -1042,7 +1042,22 @@ rb_trap_exit(void)
     }
 }
 
-void rb_waitpid_all(rb_vm_t *); /* process.c */
+void ruby_waitpid_all(rb_vm_t *); /* process.c */
+
+/* only runs in the timer-thread */
+void
+ruby_sigchld_handler(rb_vm_t *vm)
+{
+    /*
+     * Checking signal_buff.cnt[RUBY_SIGCHLD] here is not completely
+     * reliable as it can race with rb_get_next_signal in the
+     * main thread.  However, this remains useful when the main thread
+     * is blocked in an uninterruptible state:
+     */
+    if (signal_buff.cnt[RUBY_SIGCHLD]) {
+        ruby_waitpid_all(vm);
+    }
+}
 
 void
 rb_signal_exec(rb_thread_t *th, int sig)
@@ -1051,8 +1066,12 @@ 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 == RUBY_SIGCHLD) {
-        rb_waitpid_all(vm);
+    /*
+     * This is necessary as rb_get_next_signal from this (main) thread
+     * can steal work from the timer-thread running ruby_sigchld_handler
+     */
+    if (RUBY_SIGCHLD == sig) {
+        ruby_waitpid_all(vm);
     }
 
     if (cmd == 0) {
diff --git a/thread.c b/thread.c
index ab27c60632..66961efbf0 100644
--- a/thread.c
+++ b/thread.c
@@ -413,7 +413,10 @@ 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);
+    if (0) {
+        /* may be held by running threads */
+        rb_native_mutex_destroy(&vm->waitpid_lock);
+    }
 }
 
 void
@@ -1287,17 +1290,6 @@ rb_thread_sleep_forever(void)
     sleep_forever(GET_THREAD(), SLEEP_SPURIOUS_CHECK);
 }
 
-void
-rb_thread_sleep_interruptible(struct timespec *ts)
-{
-    rb_thread_t *th = GET_THREAD();
-    enum rb_thread_status prev_status = th->status;
-
-    th->status = THREAD_STOPPED;
-    native_sleep(th, ts);
-    th->status = prev_status;
-}
-
 void
 rb_thread_sleep_deadly(void)
 {
@@ -4143,6 +4135,9 @@ rb_gc_set_stack_end(VALUE **stack_end_p)
 #endif
 
 
+/* signal.c */
+void ruby_sigchld_handler(rb_vm_t *);
+
 /*
  *
  */
@@ -4175,6 +4170,7 @@ timer_thread_function(void *arg)
     rb_native_mutex_unlock(&vm->thread_destruct_lock);
 
     /* check signal */
+    ruby_sigchld_handler(vm);
     rb_threadptr_check_signal(vm->main_thread);
 
 #if 0
@@ -5315,3 +5311,25 @@ rb_uninterruptible(VALUE (*b_proc)(ANYARGS), VALUE data)
 
     return rb_ensure(b_proc, data, rb_ary_pop, cur_th->pending_interrupt_mask_stack);
 }
+
+#ifndef USE_NATIVE_SLEEP_COND
+#  define USE_NATIVE_SLEEP_COND (0)
+#endif
+
+#if !USE_NATIVE_SLEEP_COND
+rb_nativethread_cond_t *
+rb_sleep_cond_get(const rb_execution_context_t *ec)
+{
+    rb_nativethread_cond_t *cond = ALLOC(rb_nativethread_cond_t);
+    rb_native_cond_initialize(cond);
+
+    return cond;
+}
+
+void
+rb_sleep_cond_put(rb_nativethread_cond_t *cond)
+{
+    rb_native_cond_destroy(cond);
+    xfree(cond);
+}
+#endif /* !USE_NATIVE_SLEEP_COND */
diff --git a/thread_pthread.c b/thread_pthread.c
index 1a1a6fc0c6..4053a22eac 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1764,4 +1764,22 @@ rb_thread_create_mjit_thread(void (*child_hook)(void), void (*worker_func)(void)
     return ret;
 }
 
+#define USE_NATIVE_SLEEP_COND (1)
+
+#if USE_NATIVE_SLEEP_COND
+rb_nativethread_cond_t *
+rb_sleep_cond_get(const rb_execution_context_t *ec)
+{
+    rb_thread_t *th = rb_ec_thread_ptr(ec);
+
+    return &th->native_thread_data.sleep_cond;
+}
+
+void
+rb_sleep_cond_put(rb_nativethread_cond_t *cond)
+{
+    /* no-op */
+}
+#endif /* USE_NATIVE_SLEEP_COND */
+
 #endif /* THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION */
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 6/8] Revert "test_process.rb: skip tests for Bug 14867"
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
                   ` (4 preceding siblings ...)
  2018-06-25 23:50 ` [PATCH 5/8] handle SIGCHLD in both the timer-thread and main thread Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 7/8] Revert "spec: skip Process wait specs on MJIT" Eric Wong
  2018-06-25 23:50 ` [PATCH 8/8] wip testing Eric Wong
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

This reverts r63740 (commit 042395a7f7262464265ce70cdffbe1812aa145d3).
---
 test/ruby/test_process.rb | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb
index 8b7d8f0c57..a9052b4706 100644
--- a/test/ruby/test_process.rb
+++ b/test/ruby/test_process.rb
@@ -1426,7 +1426,6 @@ def test_status_quit
   end
 
   def test_wait_without_arg
-    skip "[Bug #14867]" if RubyVM::MJIT.enabled?
     with_tmpchdir do
       write_file("foo", "sleep 0.1")
       pid = spawn(RUBY, "foo")
@@ -1435,7 +1434,6 @@ def test_wait_without_arg
   end
 
   def test_wait2
-    skip "[Bug #14867]" if RubyVM::MJIT.enabled?
     with_tmpchdir do
       write_file("foo", "sleep 0.1")
       pid = spawn(RUBY, "foo")
@@ -1444,7 +1442,6 @@ def test_wait2
   end
 
   def test_waitall
-    skip "[Bug #14867]" if RubyVM::MJIT.enabled?
     with_tmpchdir do
       write_file("foo", "sleep 0.1")
       ps = (0...3).map { spawn(RUBY, "foo") }.sort
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 7/8] Revert "spec: skip Process wait specs on MJIT"
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
                   ` (5 preceding siblings ...)
  2018-06-25 23:50 ` [PATCH 6/8] Revert "test_process.rb: skip tests for Bug 14867" Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  2018-06-25 23:50 ` [PATCH 8/8] wip testing Eric Wong
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

This reverts r63731 (commit 359dd59db2512d801bb983f98bede4fc598f139a).
---
 spec/mspec/lib/mspec/guards/feature.rb |   6 --
 spec/ruby/core/process/wait2_spec.rb   |  26 ++++---
 spec/ruby/core/process/wait_spec.rb    | 122 ++++++++++++++++-----------------
 spec/ruby/core/process/waitall_spec.rb |  66 +++++++++---------
 4 files changed, 104 insertions(+), 116 deletions(-)

diff --git a/spec/mspec/lib/mspec/guards/feature.rb b/spec/mspec/lib/mspec/guards/feature.rb
index 4f1df1cabe..30984e0cc5 100644
--- a/spec/mspec/lib/mspec/guards/feature.rb
+++ b/spec/mspec/lib/mspec/guards/feature.rb
@@ -39,9 +39,3 @@ def match?
 def with_feature(*features, &block)
   FeatureGuard.new(*features).run_if(:with_feature, &block)
 end
-
-MSpecEnv.class_eval do
-  def without_feature(*features, &block)
-    FeatureGuard.new(*features).run_unless(:without_feature, &block)
-  end
-end
diff --git a/spec/ruby/core/process/wait2_spec.rb b/spec/ruby/core/process/wait2_spec.rb
index 3f5aa3c7e2..cb082541f9 100644
--- a/spec/ruby/core/process/wait2_spec.rb
+++ b/spec/ruby/core/process/wait2_spec.rb
@@ -14,21 +14,19 @@
     end
   end
 
-  without_feature :mjit do # [Bug #14867]
-    platform_is_not :windows do
-      it "returns the pid and status of child process" do
-        pidf = Process.fork { Process.exit! 99 }
-        results = Process.wait2
-        results.size.should == 2
-        pidw, status = results
-        pidf.should == pidw
-        status.exitstatus.should == 99
-      end
+  platform_is_not :windows do
+    it "returns the pid and status of child process" do
+      pidf = Process.fork { Process.exit! 99 }
+      results = Process.wait2
+      results.size.should == 2
+      pidw, status = results
+      pidf.should == pidw
+      status.exitstatus.should == 99
     end
+  end
 
-    it "raises a StandardError if no child processes exist" do
-      lambda { Process.wait2 }.should raise_error(Errno::ECHILD)
-      lambda { Process.wait2 }.should raise_error(StandardError)
-    end
+  it "raises a StandardError if no child processes exist" do
+    lambda { Process.wait2 }.should raise_error(Errno::ECHILD)
+    lambda { Process.wait2 }.should raise_error(StandardError)
   end
 end
diff --git a/spec/ruby/core/process/wait_spec.rb b/spec/ruby/core/process/wait_spec.rb
index 447c62f42a..f11b079c16 100644
--- a/spec/ruby/core/process/wait_spec.rb
+++ b/spec/ruby/core/process/wait_spec.rb
@@ -12,81 +12,79 @@
     end
   end
 
-  without_feature :mjit do # [Bug #14867]
-    it "raises an Errno::ECHILD if there are no child processes" do
-      lambda { Process.wait }.should raise_error(Errno::ECHILD)
+  it "raises an Errno::ECHILD if there are no child processes" do
+    lambda { Process.wait }.should raise_error(Errno::ECHILD)
+  end
+
+  platform_is_not :windows do
+    it "returns its childs pid" do
+      pid = Process.spawn(ruby_cmd('exit'))
+      Process.wait.should == pid
     end
 
-    platform_is_not :windows do
-      it "returns its childs pid" do
-        pid = Process.spawn(ruby_cmd('exit'))
-        Process.wait.should == pid
-      end
+    it "sets $? to a Process::Status" do
+      pid = Process.spawn(ruby_cmd('exit'))
+      Process.wait
+      $?.should be_kind_of(Process::Status)
+      $?.pid.should == pid
+    end
 
-      it "sets $? to a Process::Status" do
-        pid = Process.spawn(ruby_cmd('exit'))
-        Process.wait
-        $?.should be_kind_of(Process::Status)
-        $?.pid.should == pid
-      end
+    it "waits for any child process if no pid is given" do
+      pid = Process.spawn(ruby_cmd('exit'))
+      Process.wait.should == pid
+      lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+    end
 
-      it "waits for any child process if no pid is given" do
-        pid = Process.spawn(ruby_cmd('exit'))
-        Process.wait.should == pid
-        lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
-      end
+    it "waits for a specific child if a pid is given" do
+      pid1 = Process.spawn(ruby_cmd('exit'))
+      pid2 = Process.spawn(ruby_cmd('exit'))
+      Process.wait(pid2).should == pid2
+      Process.wait(pid1).should == pid1
+      lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+      lambda { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH)
+    end
 
-      it "waits for a specific child if a pid is given" do
-        pid1 = Process.spawn(ruby_cmd('exit'))
-        pid2 = Process.spawn(ruby_cmd('exit'))
-        Process.wait(pid2).should == pid2
-        Process.wait(pid1).should == pid1
-        lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
-        lambda { Process.kill(0, pid2) }.should raise_error(Errno::ESRCH)
-      end
+    it "coerces the pid to an Integer" do
+      pid1 = Process.spawn(ruby_cmd('exit'))
+      Process.wait(mock_int(pid1)).should == pid1
+      lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
+    end
 
-      it "coerces the pid to an Integer" do
-        pid1 = Process.spawn(ruby_cmd('exit'))
-        Process.wait(mock_int(pid1)).should == pid1
-        lambda { Process.kill(0, pid1) }.should raise_error(Errno::ESRCH)
-      end
+    # This spec is probably system-dependent.
+    it "waits for a child whose process group ID is that of the calling process" do
+      pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true)
+      pid2 = Process.spawn(ruby_cmd('exit'))
 
-      # This spec is probably system-dependent.
-      it "waits for a child whose process group ID is that of the calling process" do
-        pid1 = Process.spawn(ruby_cmd('exit'), pgroup: true)
-        pid2 = Process.spawn(ruby_cmd('exit'))
+      Process.wait(0).should == pid2
+      Process.wait.should == pid1
+    end
 
-        Process.wait(0).should == pid2
-        Process.wait.should == pid1
+    # This spec is probably system-dependent.
+    it "doesn't block if no child is available when WNOHANG is used" do
+      read, write = IO.pipe
+      pid = Process.fork do
+        read.close
+        Signal.trap("TERM") { Process.exit! }
+        write << 1
+        write.close
+        sleep
       end
 
-      # This spec is probably system-dependent.
-      it "doesn't block if no child is available when WNOHANG is used" do
-        read, write = IO.pipe
-        pid = Process.fork do
-          read.close
-          Signal.trap("TERM") { Process.exit! }
-          write << 1
-          write.close
-          sleep
-        end
-
-        Process.wait(pid, Process::WNOHANG).should be_nil
+      Process.wait(pid, Process::WNOHANG).should be_nil
 
-        # wait for the child to setup its TERM handler
-        write.close
-        read.read(1)
-        read.close
+      # wait for the child to setup its TERM handler
+      write.close
+      read.read(1)
+      read.close
 
-        Process.kill("TERM", pid)
-        Process.wait.should == pid
-      end
+      Process.kill("TERM", pid)
+      Process.wait.should == pid
+    end
 
-      it "always accepts flags=0" do
-        pid = Process.spawn(ruby_cmd('exit'))
-        Process.wait(-1, 0).should == pid
-        lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
-      end
+    it "always accepts flags=0" do
+      pid = Process.spawn(ruby_cmd('exit'))
+      Process.wait(-1, 0).should == pid
+      lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
     end
   end
 end
diff --git a/spec/ruby/core/process/waitall_spec.rb b/spec/ruby/core/process/waitall_spec.rb
index ff06ae21f9..bdc1ea7490 100644
--- a/spec/ruby/core/process/waitall_spec.rb
+++ b/spec/ruby/core/process/waitall_spec.rb
@@ -8,43 +8,41 @@
     end
   end
 
-  without_feature :mjit do # [Bug #14867]
-    it "returns an empty array when there are no children" do
-      Process.waitall.should == []
-    end
+  it "returns an empty array when there are no children" do
+    Process.waitall.should == []
+  end
 
-    it "takes no arguments" do
-      lambda { Process.waitall(0) }.should raise_error(ArgumentError)
-    end
+  it "takes no arguments" do
+    lambda { Process.waitall(0) }.should raise_error(ArgumentError)
+  end
 
-    platform_is_not :windows do
-      it "waits for all children" do
-        pids = []
-        pids << Process.fork { Process.exit! 2 }
-        pids << Process.fork { Process.exit! 1 }
-        pids << Process.fork { Process.exit! 0 }
-        Process.waitall
-        pids.each { |pid|
-          lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
-        }
-      end
+  platform_is_not :windows do
+    it "waits for all children" do
+      pids = []
+      pids << Process.fork { Process.exit! 2 }
+      pids << Process.fork { Process.exit! 1 }
+      pids << Process.fork { Process.exit! 0 }
+      Process.waitall
+      pids.each { |pid|
+        lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
+      }
+    end
 
-      it "returns an array of pid/status pairs" do
-        pids = []
-        pids << Process.fork { Process.exit! 2 }
-        pids << Process.fork { Process.exit! 1 }
-        pids << Process.fork { Process.exit! 0 }
-        a = Process.waitall
-        a.should be_kind_of(Array)
-        a.size.should == 3
-        pids.each { |pid|
-          pid_status = a.assoc(pid)
-          pid_status.should be_kind_of(Array)
-          pid_status.size.should == 2
-          pid_status.first.should == pid
-          pid_status.last.should be_kind_of(Process::Status)
-        }
-      end
+    it "returns an array of pid/status pairs" do
+      pids = []
+      pids << Process.fork { Process.exit! 2 }
+      pids << Process.fork { Process.exit! 1 }
+      pids << Process.fork { Process.exit! 0 }
+      a = Process.waitall
+      a.should be_kind_of(Array)
+      a.size.should == 3
+      pids.each { |pid|
+        pid_status = a.assoc(pid)
+        pid_status.should be_kind_of(Array)
+        pid_status.size.should == 2
+        pid_status.first.should == pid
+        pid_status.last.should be_kind_of(Process::Status)
+      }
     end
   end
 end
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 8/8] wip testing
  2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
                   ` (6 preceding siblings ...)
  2018-06-25 23:50 ` [PATCH 7/8] Revert "spec: skip Process wait specs on MJIT" Eric Wong
@ 2018-06-25 23:50 ` Eric Wong
  7 siblings, 0 replies; 9+ messages in thread
From: Eric Wong @ 2018-06-25 23:50 UTC (permalink / raw)
  To: spew; +Cc: Eric Wong

---
 mjit.c    |  5 +++--
 process.c | 12 +++++++++---
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/mjit.c b/mjit.c
index a550d1aabd..2e11395851 100644
--- a/mjit.c
+++ b/mjit.c
@@ -421,8 +421,9 @@ exec_process(const char *path, char *const argv[])
         pid_t r = vm ? ruby_waitpid_locked(vm, pid, &stat, 0, &cond)
                      : waitpid(pid, &stat, 0);
         if (r == -1) {
-            if (errno == EINTR) continue; /* should never happen */
-            fprintf(stderr, "waitpid: %s\n", strerror(errno));
+            if (errno == EINTR) continue;
+            fprintf(stderr, "[%d] waitpid(%d): %s\n",
+                    getpid(), pid, strerror(errno));
             break;
         }
         else if (r == pid) {
diff --git a/process.c b/process.c
index 0b91b30075..ce1445fbaa 100644
--- a/process.c
+++ b/process.c
@@ -919,7 +919,6 @@ static void
 waitpid_notify(struct waitpid_state *w, pid_t ret)
 {
     w->ret = ret;
-    if (w->ret == -1) w->errnum = errno;
     list_del_init(&w->wnode);
     rb_native_cond_signal(w->cond);
 }
@@ -933,7 +932,9 @@ ruby_waitpid_all(rb_vm_t *vm)
     rb_native_mutex_lock(&vm->waitpid_lock);
     list_for_each_safe(&vm->waiting_pids, w, next, wnode) {
         pid_t ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG);
+
         if (!ret) continue;
+        if (ret == -1) w->errnum = errno;
 
         if (w->ec) { /* rb_waitpid */
             rb_thread_t *th = rb_ec_thread_ptr(w->ec);
@@ -985,7 +986,7 @@ ruby_waitpid_locked(rb_vm_t *vm, rb_pid_t pid, int *status, int options,
     if (status) {
         *status = w.status;
     }
-    errno = w.errnum;
+    if (w.ret == -1) errno = w.errnum;
     return w.ret;
 }
 
@@ -1061,7 +1062,11 @@ waitpid_wait(struct waitpid_state *w)
     }
     else {
         w->cond = rb_sleep_cond_get(w->ec);
-        list_add(&vm->waiting_pids, &w->wnode);
+        /* order matters, favor specified PIDs rather than -1 or 0 */
+        if (w->pid > 0)
+          list_add(&vm->waiting_pids, &w->wnode);
+        else
+          list_add_tail(&vm->waiting_pids, &w->wnode);
     }
 
     rb_native_mutex_unlock(&vm->waitpid_lock);
@@ -1086,6 +1091,7 @@ rb_waitpid(rb_pid_t pid, int *st, int flags)
     if (result > 0) {
 	rb_last_status_set(*st, result);
     }
+    if (w.ret == -1) errno = w.errnum;
     return result;
 }
 
-- 
EW


^ permalink raw reply related	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2018-06-25 23:51 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-25 23:50 [PATCHv3 0/8] mjit SIGCHLD hijack series Eric Wong
2018-06-25 23:50 ` [PATCH 1/8] hijack SIGCHLD handler for internal use Eric Wong
2018-06-25 23:50 ` [PATCH 2/8] fix SIGCHLD hijacking race conditions Eric Wong
2018-06-25 23:50 ` [PATCH 3/8] mjit.c: allow working on platforms without SIGCHLD Eric Wong
2018-06-25 23:50 ` [PATCH 4/8] cleanups Eric Wong
2018-06-25 23:50 ` [PATCH 5/8] handle SIGCHLD in both the timer-thread and main thread Eric Wong
2018-06-25 23:50 ` [PATCH 6/8] Revert "test_process.rb: skip tests for Bug 14867" Eric Wong
2018-06-25 23:50 ` [PATCH 7/8] Revert "spec: skip Process wait specs on MJIT" Eric Wong
2018-06-25 23:50 ` [PATCH 8/8] wip testing Eric Wong

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).