ruby-changes:64031
From: Samuel <ko1@a...>
Date: Wed, 9 Dec 2020 04:56:05 +0900 (JST)
Subject: [ruby-changes:64031] 2553c5f94a (master): Add support for non-blocking `Process.wait`.
https://git.ruby-lang.org/ruby.git/commit/?id=2553c5f94a From 2553c5f94a5d51c2c5876b31e4c1521ad9be12f6 Mon Sep 17 00:00:00 2001 From: Samuel Williams <samuel.williams@o...> Date: Tue, 8 Dec 2020 09:29:09 +1300 Subject: Add support for non-blocking `Process.wait`. diff --git a/common.mk b/common.mk index 158828a..c2d6a72 100644 --- a/common.mk +++ b/common.mk @@ -10002,6 +10002,7 @@ process.$(OBJEXT): $(top_srcdir)/internal/thread.h https://github.com/ruby/ruby/blob/trunk/common.mk#L10002 process.$(OBJEXT): $(top_srcdir)/internal/variable.h process.$(OBJEXT): $(top_srcdir)/internal/vm.h process.$(OBJEXT): $(top_srcdir)/internal/warnings.h +process.$(OBJEXT): {$(VPATH)}$(COROUTINE_H) process.$(OBJEXT): {$(VPATH)}assert.h process.$(OBJEXT): {$(VPATH)}backward/2/assume.h process.$(OBJEXT): {$(VPATH)}backward/2/attributes.h diff --git a/doc/scheduler.md b/doc/scheduler.md index 9994831..a6e2d78 100644 --- a/doc/scheduler.md +++ b/doc/scheduler.md @@ -12,6 +12,17 @@ This is the interface you need to implement. https://github.com/ruby/ruby/blob/trunk/doc/scheduler.md#L12 ~~~ ruby class Scheduler + # Wait for the specified process ID to exit. + # This hook is optional. + # @parameter pid [Integer] The process ID to wait for. + # @parameter flags [Integer] A bit-mask of flags suitable for `Process::Status.wait`. + # @returns [Process::Status] A process status instance. + def process_wait(pid, flags) + Thread.new do + Process::Status.wait(pid, flags) + end.value + end + # Wait for the given file descriptor to match the specified events within # the specified timeout. # @parameter event [Integer] A bit mask of `IO::READABLE`, diff --git a/include/ruby/internal/intern/process.h b/include/ruby/internal/intern/process.h index 2b1005a..bcb5a7e 100644 --- a/include/ruby/internal/intern/process.h +++ b/include/ruby/internal/intern/process.h @@ -28,7 +28,9 @@ https://github.com/ruby/ruby/blob/trunk/include/ruby/internal/intern/process.h#L28 RBIMPL_SYMBOL_EXPORT_BEGIN() /* process.c */ -void rb_last_status_set(int status, rb_pid_t pid); +RUBY_EXTERN void (* rb_socket_before_fork_func)(); + +void rb_last_status_set(rb_pid_t pid, int status, int error); VALUE rb_last_status_get(void); int rb_proc_exec(const char*); diff --git a/internal/scheduler.h b/internal/scheduler.h index 472edc6..8314020 100644 --- a/internal/scheduler.h +++ b/internal/scheduler.h @@ -25,6 +25,9 @@ VALUE rb_scheduler_close(VALUE scheduler); https://github.com/ruby/ruby/blob/trunk/internal/scheduler.h#L25 VALUE rb_scheduler_kernel_sleep(VALUE scheduler, VALUE duration); VALUE rb_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv); +int rb_scheduler_supports_process_wait(VALUE scheduler); +VALUE rb_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags); + VALUE rb_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout); VALUE rb_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber); diff --git a/io.c b/io.c index 0182042..b5553b5 100644 --- a/io.c +++ b/io.c @@ -4913,9 +4913,9 @@ fptr_waitpid(rb_io_t *fptr, int nohang) https://github.com/ruby/ruby/blob/trunk/io.c#L4913 { int status; if (fptr->pid) { - rb_last_status_clear(); - rb_waitpid(fptr->pid, &status, nohang ? WNOHANG : 0); - fptr->pid = 0; + rb_last_status_clear(); + rb_waitpid(fptr->pid, &status, nohang ? WNOHANG : 0); + fptr->pid = 0; } } @@ -6433,11 +6433,11 @@ pipe_finalize(rb_io_t *fptr, int noraise) https://github.com/ruby/ruby/blob/trunk/io.c#L6433 #if !defined(HAVE_WORKING_FORK) && !defined(_WIN32) int status = 0; if (fptr->stdio_file) { - status = pclose(fptr->stdio_file); + status = pclose(fptr->stdio_file); } fptr->fd = -1; fptr->stdio_file = 0; - rb_last_status_set(status, fptr->pid); + rb_last_status_set(fptr->pid, status, 0); #else fptr_finalize(fptr, noraise); #endif diff --git a/process.c b/process.c index 8ad555f..acc9de1 100644 --- a/process.c +++ b/process.c @@ -14,6 +14,7 @@ https://github.com/ruby/ruby/blob/trunk/process.c#L14 #include "ruby/internal/config.h" #include "internal/scheduler.h" +#include "coroutine/Stack.h" #include <ctype.h> #include <errno.h> @@ -568,6 +569,27 @@ proc_get_ppid(VALUE _) https://github.com/ruby/ruby/blob/trunk/process.c#L569 static VALUE rb_cProcessStatus; +struct rb_process_status { + rb_pid_t pid; + int status; + int error; +}; + +static const rb_data_type_t rb_process_status_type = { + .wrap_struct_name = "Process::Status", + .function = { + .dfree = RUBY_DEFAULT_FREE, + }, + .data = NULL, + .flags = RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static VALUE rb_process_status_allocate(VALUE klass) { + struct rb_process_status *data = NULL; + + return TypedData_Make_Struct(klass, struct rb_process_status, &rb_process_status_type, data); +} + VALUE rb_last_status_get(void) { @@ -596,13 +618,20 @@ proc_s_last_status(VALUE mod) https://github.com/ruby/ruby/blob/trunk/process.c#L618 } void -rb_last_status_set(int status, rb_pid_t pid) +rb_last_status_set(rb_pid_t pid, int status, int error) { rb_thread_t *th = GET_THREAD(); - th->last_status = rb_obj_alloc(rb_cProcessStatus); - rb_ivar_set(th->last_status, id_status, INT2FIX(status)); - rb_ivar_set(th->last_status, id_pid, PIDT2NUM(pid)); - rb_obj_freeze(th->last_status); + + VALUE last_status = rb_process_status_allocate(rb_cProcessStatus); + + struct rb_process_status *data = RTYPEDDATA_DATA(last_status); + data->pid = pid; + data->status = status; + data->error = error; + + rb_obj_freeze(last_status); + + th->last_status = last_status; } void @@ -624,9 +653,11 @@ rb_last_status_clear(void) https://github.com/ruby/ruby/blob/trunk/process.c#L653 */ static VALUE -pst_to_i(VALUE st) +pst_to_i(VALUE self) { - return rb_ivar_get(st, id_status); + struct rb_process_status *data = RTYPEDDATA_DATA(self); + + return RB_INT2NUM(data->status); } #define PST2INT(st) NUM2INT(pst_to_i(st)) @@ -643,9 +674,11 @@ pst_to_i(VALUE st) https://github.com/ruby/ruby/blob/trunk/process.c#L674 */ static VALUE -pst_pid(VALUE st) +pst_pid(VALUE self) { - return rb_attr_get(st, id_pid); + struct rb_process_status *data = RTYPEDDATA_DATA(self); + + return PIDT2NUM(data->pid); } static VALUE pst_message_status(VALUE str, int status); @@ -1104,6 +1137,7 @@ waitpid_state_init(struct waitpid_state *w, rb_pid_t pid, int options) https://github.com/ruby/ruby/blob/trunk/process.c#L1137 w->ret = 0; w->pid = pid; w->options = options; + w->errnum = 0; } static const rb_hrtime_t * @@ -1214,8 +1248,10 @@ waitpid_wait(struct waitpid_state *w) https://github.com/ruby/ruby/blob/trunk/process.c#L1248 */ rb_native_mutex_lock(&vm->waitpid_lock); - if (w->pid > 0 || list_empty(&vm->waiting_pids)) + if (w->pid > 0 || list_empty(&vm->waiting_pids)) { w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG); + } + if (w->ret) { if (w->ret == -1) w->errnum = errno; } @@ -1264,35 +1300,125 @@ waitpid_no_SIGCHLD(struct waitpid_state *w) https://github.com/ruby/ruby/blob/trunk/process.c#L1300 w->errnum = errno; } -rb_pid_t -rb_waitpid(rb_pid_t pid, int *st, int flags) +/* + * call-seq: + * Process::Status.wait(pid=-1, flags=0) -> Process::Status + * + * Waits for a child process to exit and returns a Process::Status object + * containing information on that process. Which child it waits on + * depends on the value of _pid_: + * + * > 0:: Waits for the child whose process ID equals _pid_. + * + * 0:: Waits for any child whose process group ID equals that of the + * calling process. + * + * -1:: Waits for any child process (the default if no _pid_ is + * given). + * + * < -1:: Waits for any child whose process group ID equals the absolute + * value of _pid_. + * + * The _flags_ argument may be a logical or of the flag values + * Process::WNOHANG (do not block if no child available) + * or Process::WUNTRACED (return stopped children that + * haven't been reported). Not all flags are available on all + * platforms, but a flag value of zero will work on all platforms. + * + * Calling this method raises a SystemCallError if there are no child + * processes. Not available on all platforms. + * + * May invoke the scheduler hook _process_wait_. + * + * fork { exit 99 } #=> 27429 + * Process::Status.wait #=> pid 27429 exit 99 + * $? #=> nil + * + * pid = fork { sleep 3 } #=> 27440 + * Time.now #=> 2008-03-08 19:56:16 +0900 + * Process::Status.wait(pid, Process::WNOHANG) #=> nil + * Time.now #=> 2008-03-08 19:56:16 +0900 + * Process::Status.wait(pid, 0) #=> pid 27440 exit 99 + * Time.now #=> 2008-03-08 19:56:19 +0900 + */ +VALUE rb_process_status_wait(rb_pid_t pid, int flags) { - struct waitpid_state w; + // We only enter the scheduler if we are "blocking": + if (!(flags & WNOHANG)) { + VALUE scheduler = rb_scheduler_current(); + if (rb_scheduler_supports_process_wait(scheduler)) { + return rb_scheduler_process_wait(scheduler, pid, flags); + } + } - waitpid_state_init(&w, pid, flags); - w.ec = GET_EC(); + COROUTINE_STACK_LOCAL(struct waitpid_state, w); + + waitpid_state_init(w, pid, flags); + w->ec = GET_EC(); if (WAITPID_USE_SIGCHLD) { - waitpid_wait(&w); + waitpid_wait(w); } else { - waitpid_no_SIGCHLD(&w); + waitpid_no_SIGCHLD(w); } - if (st) *st = w.status; - if (w.ret == -1) { - errno = w.errnum; - } - else if (w.ret > 0) { + if (w->ret (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/