From 44fc3d08eb3cb52df5bdd91a0d9723718654b349 Mon Sep 17 00:00:00 2001 From: normal Date: Thu, 5 Jul 2018 03:02:33 +0000 Subject: [PATCH] unrevert r63852 but keep SIGCHLD path disabled for win32 Reading win32/win32.c waitpid implementation, maybe waitpid(-1, ...) on that platform will never conflict with mjit use of waitpid. In any case, I've added WAITPID_USE_SIGCHLD macro to vm_core.h so it can be easy for Linux/BSD users to test (hopefully!) win32-compatible code. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@63855 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- configure.ac | 2 + ext/pty/pty.c | 22 +- internal.h | 3 + mjit.c | 51 +++-- process.c | 287 +++++++++++++++++++++---- signal.c | 125 ++++++++--- spec/ruby/core/process/wait2_spec.rb | 38 ++-- spec/ruby/core/process/wait_spec.rb | 126 +++++------ spec/ruby/core/process/waitall_spec.rb | 66 +++--- test/ruby/test_optimization.rb | 2 +- test/ruby/test_process.rb | 12 +- test/ruby/test_rubyoptions.rb | 17 +- thread.c | 34 +++ thread_pthread.c | 47 +++- vm_core.h | 23 ++ win32/Makefile.sub | 2 +- 16 files changed, 627 insertions(+), 230 deletions(-) diff --git a/configure.ac b/configure.ac index b433863d64e194..074a3fd394f255 100644 --- a/configure.ac +++ b/configure.ac @@ -766,6 +766,7 @@ AS_CASE(["$target_os"], AS_IF([test $gcc_major -lt 4 -o \( $gcc_major -eq 4 -a $gcc_minor -lt 3 \)], [ ac_cv_func___builtin_setjmp=no ]) + with_setjmp_type=sigsetjmp # to hijack SIGCHLD handler AC_CACHE_CHECK(for broken crypt with 8bit chars, rb_cv_broken_crypt, [AC_TRY_RUN([ #include @@ -1782,6 +1783,7 @@ AC_CHECK_FUNCS(getsid) AC_CHECK_FUNCS(gettimeofday) # for making ac_cv_func_gettimeofday AC_CHECK_FUNCS(getuidx) AC_CHECK_FUNCS(gmtime_r) +AC_CHECK_FUNCS(grantpt) AC_CHECK_FUNCS(initgroups) AC_CHECK_FUNCS(ioctl) AC_CHECK_FUNCS(isfinite) diff --git a/ext/pty/pty.c b/ext/pty/pty.c index b3c753532108e7..1ac89061c1fefd 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -246,19 +246,13 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, /* Unix98 PTY */ int masterfd = -1, slavefd = -1; char *slavedevice; - struct sigaction dfl, old; - - dfl.sa_handler = SIG_DFL; - dfl.sa_flags = 0; - sigemptyset(&dfl.sa_mask); #if defined(__sun) || (defined(__FreeBSD__) && __FreeBSD_version < 902000) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ /* FreeBSD 9.2 or later supports O_CLOEXEC * http://www.freebsd.org/cgi/query-pr.cgi?pr=162374 */ if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error; - if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; - if (grantpt(masterfd) == -1) goto grantpt_error; + if (rb_grantpt(masterfd) == -1) goto error; rb_fd_fix_cloexec(masterfd); #else { @@ -272,10 +266,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if ((masterfd = posix_openpt(flags)) == -1) goto error; } rb_fd_fix_cloexec(masterfd); - if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; - if (grantpt(masterfd) == -1) goto grantpt_error; + if (rb_grantpt(masterfd) == -1) goto error; #endif - if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; if ((slavedevice = ptsname(masterfd)) == NULL) goto error; if (no_mesg(slavedevice, nomesg) == -1) goto error; @@ -293,8 +285,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; - grantpt_error: - sigaction(SIGCHLD, &old, NULL); error: if (slavefd != -1) close(slavefd); if (masterfd != -1) close(masterfd); @@ -346,21 +336,17 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, extern char *ptsname(int); extern int unlockpt(int); - extern int grantpt(int); #if defined(__sun) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; - s = signal(SIGCHLD, SIG_DFL); - if(grantpt(masterfd) == -1) goto error; + if(rb_grantpt(masterfd) == -1) goto error; rb_fd_fix_cloexec(masterfd); #else if((masterfd = rb_cloexec_open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; rb_update_max_fd(masterfd); - s = signal(SIGCHLD, SIG_DFL); - if(grantpt(masterfd) == -1) goto error; + if(rb_grantpt(masterfd) == -1) goto error; #endif - signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; if((slavedevice = ptsname(masterfd)) == NULL) goto error; if (no_mesg(slavedevice, nomesg) == -1) goto error; diff --git a/internal.h b/internal.h index 59e855c8507ad1..b555bbab06394d 100644 --- a/internal.h +++ b/internal.h @@ -2042,6 +2042,9 @@ VALUE rb_gcd_normal(VALUE self, VALUE other); VALUE rb_gcd_gmp(VALUE x, VALUE y); #endif +/* signal.c (export) */ +int rb_grantpt(int fd); + /* string.c (export) */ #ifdef RUBY_ENCODING_H /* internal use */ diff --git a/mjit.c b/mjit.c index 07e9328aff9707..ade1e2e6a94bbe 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,10 @@ 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 */ +rb_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 #ifdef _WIN32 @@ -263,7 +268,7 @@ real_ms_time(void) static int sprint_uniq_filename(char *str, size_t size, unsigned long id, const char *prefix, const char *suffix) { - return snprintf(str, size, "%s/%sp%luu%lu%s", tmp_dir, prefix, (unsigned long) getpid(), id, suffix); + return snprintf(str, size, "%s/%sp%"PRI_PIDT_PREFIX"uu%lu%s", tmp_dir, prefix, getpid(), id, suffix); } /* Return an unique file name in /tmp with PREFIX and SUFFIX and @@ -401,22 +406,41 @@ 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 = WAITPID_USE_SIGCHLD ? GET_VM() : 0; + rb_nativethread_cond_t cond; - pid = start_process(path, argv); - if (pid <= 0) - return -2; + if (vm) { + rb_native_cond_initialize(&cond); + rb_native_mutex_lock(&vm->waitpid_lock); + } - for (;;) { - waitpid(pid, &stat, 0); - if (WIFEXITED(stat)) { - exit_code = WEXITSTATUS(stat); - break; - } else if (WIFSIGNALED(stat)) { - exit_code = -1; + 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; + fprintf(stderr, "[%"PRI_PIDT_PREFIX"d] waitpid(%"PRI_PIDT_PREFIX"d): %s (SIGCHLD=%d,%u)\n", + getpid(), pid, strerror(errno), + RUBY_SIGCHLD, SIGCHLD_LOSSY); break; } + else if (r == pid) { + if (WIFEXITED(stat)) { + exit_code = WEXITSTATUS(stat); + break; + } else if (WIFSIGNALED(stat)) { + exit_code = -1; + break; + } + } + } + + if (vm) { + rb_native_mutex_unlock(&vm->waitpid_lock); + rb_native_cond_destroy(&cond); } return exit_code; } @@ -1491,12 +1515,15 @@ mjit_init(struct mjit_options *opts) static void stop_worker(void) { + rb_execution_context_t *ec = GET_EC(); + stop_worker_p = TRUE; while (!worker_stopped) { verbose(3, "Sending cancel signal to worker"); 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(ec); } } diff --git a/process.c b/process.c index 12ea6eac86de15..12cceba934d329 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,45 +897,263 @@ do_waitpid(rb_pid_t pid, int *st, int flags) #endif } +struct waitpid_state { + struct list_node wnode; + rb_execution_context_t *ec; + rb_nativethread_cond_t *cond; + rb_pid_t ret; + rb_pid_t pid; + int status; + int options; + int errnum; +}; + +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 *); + +static void +waitpid_notify(struct waitpid_state *w, rb_pid_t ret) +{ + w->ret = ret; + list_del_init(&w->wnode); + rb_native_cond_signal(w->cond); +} + +#ifdef _WIN32 /* for spawnvp result from mjit.c */ +# define waitpid_sys(pid,status,options) \ + (WaitForSingleObject((HANDLE)(pid), 0),\ + GetExitCodeProcess((HANDLE)(pid), (LPDWORD)(status))) +#else +# define waitpid_sys(pid,status,options) do_waitpid((pid),(status),(options)) +#endif + +/* called by timer thread */ +static void +waitpid_each(struct list_head *head) +{ + struct waitpid_state *w = 0, *next; + + list_for_each_safe(head, w, next, wnode) { + rb_pid_t ret; + + if (w->ec) + ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG); + else + ret = waitpid_sys(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); + + rb_native_mutex_lock(&th->interrupt_lock); + waitpid_notify(w, ret); + rb_native_mutex_unlock(&th->interrupt_lock); + } + else { /* ruby_waitpid_locked */ + waitpid_notify(w, ret); + } + } +} + +void +ruby_waitpid_all(rb_vm_t *vm) +{ + rb_native_mutex_lock(&vm->waitpid_lock); + waitpid_each(&vm->waiting_pids); + if (list_empty(&vm->waiting_pids)) { + waitpid_each(&vm->waiting_grps); + } + rb_native_mutex_unlock(&vm->waitpid_lock); +} + +static void +waitpid_state_init(struct waitpid_state *w, rb_pid_t pid, int options) +{ + w->ret = 0; + w->pid = pid; + w->options = options; +} + +/* + * must be called with vm->waitpid_lock held, this is not interruptible + */ +rb_pid_t +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, pid, options); + 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; + } + else { + w.cond = cond; + w.ec = 0; + list_add(w.pid > 0 ? &vm->waiting_pids : &vm->waiting_grps, &w.wnode); + do { + rb_native_cond_wait(w.cond, &vm->waitpid_lock); + } while (!w.ret); + list_del(&w.wnode); + } + if (status) { + *status = w.status; + } + if (w.ret == -1) errno = w.errnum; + return w.ret; +} + +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 * -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; + rb_thread_t *th = rb_ec_thread_ptr(w->ec); + + rb_native_mutex_lock(&th->interrupt_lock); + /* + * We must check again before waiting, timer-thread may change w->ret + * by the time we enter this. And we may also be interrupted. + */ + if (!w->ret && !RUBY_VM_INTERRUPTED_ANY(w->ec)) { + if (SIGCHLD_LOSSY) { + rb_thread_wakeup_timer_thread(); + } + rb_native_cond_wait(w->cond, &th->interrupt_lock); + } + rb_native_mutex_unlock(&th->interrupt_lock); + + return 0; } -static rb_pid_t -do_waitpid_nonblocking(rb_pid_t pid, int *st, int flags) +static VALUE +waitpid_sleep(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; + + while (!w->ret) { + rb_thread_call_without_gvl(waitpid_nogvl, w, waitpid_wake, w); + } + + return Qfalse; +} + +static VALUE +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->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->ec); + + /* + * 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); + + if (w->pid > 0 || list_empty(&vm->waiting_pids)) + w->ret = do_waitpid(w->pid, &w->status, w->options | WNOHANG); + if (w->ret) { + w->cond = 0; + if (w->ret == -1) w->errnum = errno; + } + else if (w->options & WNOHANG) { + w->cond = 0; + } + else { + w->cond = rb_sleep_cond_get(w->ec); + /* order matters, favor specified PIDs rather than -1 or 0 */ + list_add(w->pid > 0 ? &vm->waiting_pids : &vm->waiting_grps, &w->wnode); + } + + rb_native_mutex_unlock(&vm->waitpid_lock); + + if (w->cond) { + rb_ensure(waitpid_sleep, (VALUE)w, waitpid_cleanup, (VALUE)w); + } +} + +static void * +waitpid_blocking_no_SIGCHLD(void *x) +{ + struct waitpid_state *w = x; + + w->ret = do_waitpid(w->pid, &w->status, w->options); + + return 0; +} + +static void +waitpid_no_SIGCHLD(struct waitpid_state *w) +{ + if (w->options & WNOHANG) { + w->ret = do_waitpid(w->pid, &w->status, w->options); + } + else { + do { + rb_thread_call_without_gvl(waitpid_blocking_no_SIGCHLD, w, + RUBY_UBF_PROCESS, 0); + } while (w->ret < 0 && errno == EINTR && (RUBY_VM_CHECK_INTS(w->ec),1)); + } + if (w->ret == -1) + w->errnum = errno; } 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); + waitpid_state_init(&w, pid, flags); + w.ec = GET_EC(); + + if (WAITPID_USE_SIGCHLD) { + waitpid_wait(&w); } else { - while ((result = do_waitpid_nonblocking(pid, st, flags)) < 0 && - (errno == EINTR)) { - RUBY_VM_CHECK_INTS(GET_EC()); - } + waitpid_no_SIGCHLD(&w); } - if (result > 0) { - rb_last_status_set(*st, result); + + if (st) *st = w.status; + if (w.ret > 0) { + rb_last_status_set(w.status, w.ret); } - return result; + if (w.ret == -1) errno = w.errnum; + return w.ret; } @@ -3595,6 +3807,8 @@ disable_child_handler_fork_child(struct child_handler_disabler_state *old, char } } + /* non-Ruby child process, ensure cmake can see SIGCHLD */ + sigemptyset(&old->sigmask); ret = sigprocmask(SIG_SETMASK, &old->sigmask, NULL); /* async-signal-safe */ if (ret != 0) { ERRMSG("sigprocmask"); @@ -4086,16 +4300,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 +4309,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 d2231fe5ca4750..e9f0708510fd37 100644 --- a/signal.c +++ b/signal.c @@ -62,10 +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 - #define FOREACH_SIGNAL(sig, offset) \ for (sig = siglist + (offset); sig < siglist + numberof(siglist); ++sig) enum { LONGEST_SIGNAME = 7 }; /* MIGRATE and RETRACT */ @@ -132,15 +128,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}, @@ -708,12 +698,29 @@ signal_enque(int sig) ATOMIC_INC(signal_buff.size); } +static rb_atomic_t sigchld_hit; + +/* Prevent compiler from reordering access */ +#define ACCESS_ONCE(type,x) (*((volatile type *)&(x))) + static RETSIGTYPE sighandler(int sig) { int old_errnum = errno; - signal_enque(sig); + /* the VM always needs to handle SIGCHLD for rb_waitpid */ + if (sig == RUBY_SIGCHLD) { + rb_vm_t *vm = GET_VM(); + ATOMIC_EXCHANGE(sigchld_hit, 1); + + /* avoid spurious wakeup in main thread iff nobody uses trap(:CHLD) */ + if (vm && ACCESS_ONCE(VALUE, vm->trap_list.cmd[sig])) { + signal_enque(sig); + } + } + else { + signal_enque(sig); + } rb_thread_wakeup_timer_thread(); #if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) ruby_signal(sig, sighandler); @@ -748,6 +755,7 @@ rb_enable_interrupt(void) #ifdef HAVE_PTHREAD_SIGMASK sigset_t mask; sigemptyset(&mask); + sigaddset(&mask, RUBY_SIGCHLD); /* timer-thread handles this */ pthread_sigmask(SIG_SETMASK, &mask, NULL); #endif } @@ -1058,6 +1066,17 @@ rb_trap_exit(void) } } +void ruby_waitpid_all(rb_vm_t *); /* process.c */ + +/* only runs in the timer-thread */ +void +ruby_sigchld_handler(rb_vm_t *vm) +{ + if (SIGCHLD_LOSSY || ATOMIC_EXCHANGE(sigchld_hit, 0)) { + ruby_waitpid_all(vm); + } +} + void rb_signal_exec(rb_thread_t *th, int sig) { @@ -1123,6 +1142,9 @@ default_handler(int sig) #endif #ifdef SIGUSR2 case SIGUSR2: +#endif +#if RUBY_SIGCHLD + case RUBY_SIGCHLD: #endif func = sighandler; break; @@ -1161,6 +1183,9 @@ trap_handler(VALUE *cmd, int sig) VALUE command; if (NIL_P(*cmd)) { + if (sig == RUBY_SIGCHLD) { + goto sig_dfl; + } func = SIG_IGN; } else { @@ -1181,6 +1206,9 @@ trap_handler(VALUE *cmd, int sig) break; case 14: if (memcmp(cptr, "SYSTEM_DEFAULT", 14) == 0) { + if (sig == RUBY_SIGCHLD) { + goto sig_dfl; + } func = SIG_DFL; *cmd = 0; } @@ -1188,6 +1216,9 @@ trap_handler(VALUE *cmd, int sig) case 7: if (memcmp(cptr, "SIG_IGN", 7) == 0) { sig_ign: + if (sig == RUBY_SIGCHLD) { + goto sig_dfl; + } func = SIG_IGN; *cmd = Qtrue; } @@ -1274,7 +1305,7 @@ trap(int sig, sighandler_t func, VALUE command) break; } - vm->trap_list.cmd[sig] = command; + ACCESS_ONCE(VALUE, vm->trap_list.cmd[sig]) = command; vm->trap_list.safe[sig] = rb_safe_level(); return oldcmd; @@ -1419,20 +1450,18 @@ 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) { 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); + ACCESS_ONCE(VALUE, GET_VM()->trap_list.cmd[sig]) = 0; + return 0; } @@ -1548,11 +1577,55 @@ 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(); } + +#if defined(HAVE_GRANTPT) +extern int grantpt(int); +#else +static int +fake_grantfd(int masterfd) +{ + errno = ENOSYS; + return -1; +} +#define grantpt(fd) fake_grantfd(fd) +#endif + +int +rb_grantpt(int masterfd) +{ + if (RUBY_SIGCHLD) { + rb_vm_t *vm = GET_VM(); + int ret, e; + + /* + * Prevent waitpid calls from Ruby by taking waitpid_lock. + * Pedantically, grantpt(3) is undefined if a non-default + * SIGCHLD handler is defined, but preventing conflicting + * waitpid calls ought to be sufficient. + * + * We could install the default sighandler temporarily, but that + * could cause SIGCHLD to be missed by other threads. Blocking + * SIGCHLD won't work here, either, unless we stop and restart + * timer-thread (as only timer-thread sees SIGCHLD), but that + * seems like overkill. + */ + rb_nativethread_lock_lock(&vm->waitpid_lock); + { + ret = grantpt(masterfd); /* may spawn `pt_chown' and wait on it */ + if (ret < 0) e = errno; + } + rb_nativethread_lock_unlock(&vm->waitpid_lock); + + if (ret < 0) errno = e; + return ret; + } + else { + return grantpt(masterfd); + } +} diff --git a/spec/ruby/core/process/wait2_spec.rb b/spec/ruby/core/process/wait2_spec.rb index 3f5aa3c7e2c4e2..d0163f80afa672 100644 --- a/spec/ruby/core/process/wait2_spec.rb +++ b/spec/ruby/core/process/wait2_spec.rb @@ -4,31 +4,37 @@ before :all do # HACK: this kludge is temporarily necessary because some # misbehaving spec somewhere else does not clear processes + # Note: background processes are unavoidable with MJIT, + # but we shouldn't reap them from Ruby-space begin Process.wait(-1, Process::WNOHANG) - $stderr.puts "Leaked process before wait2 specs! Waiting for it" + without_feature :mjit do + $stderr.puts "Leaked process before wait2 specs! Waiting for it" + end leaked = Process.waitall - $stderr.puts "leaked before wait2 specs: #{leaked}" + $stderr.puts "leaked before wait2 specs: #{leaked}" unless leaked.empty? + with_feature :mjit do + # Ruby-space should not see PIDs used by mjit + leaked.should be_empty + end rescue Errno::ECHILD # No child processes rescue NotImplementedError 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 447c62f42a9244..5130bb4391a6a7 100644 --- a/spec/ruby/core/process/wait_spec.rb +++ b/spec/ruby/core/process/wait_spec.rb @@ -8,85 +8,87 @@ begin leaked = Process.waitall puts "leaked before wait specs: #{leaked}" unless leaked.empty? + with_feature :mjit do + # Ruby-space should not see PIDs used by mjit + leaked.should be_empty + end rescue NotImplementedError 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 ff06ae21f9d941..bdc1ea749033da 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 diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 6bfaf11d7fe052..32bdcef13dbe0b 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -707,7 +707,7 @@ def test_peephole_optimization_without_trace end def test_clear_unreachable_keyword_args - assert_separately [], <<-END, timeout: 20 + assert_separately [], <<-END, timeout: 30 script = <<-EOS if true else diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 36ae94908aa9e3..6a32702b03a3dd 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 @@ -1459,7 +1456,9 @@ def test_waitall def test_wait_exception bug11340 = '[ruby-dev:49176] [Bug #11340]' t0 = t1 = nil - IO.popen([RUBY, '-e', 'puts;STDOUT.flush;Thread.start{gets;exit};sleep(3)'], 'r+') do |f| + sec = 3 + code = "puts;STDOUT.flush;Thread.start{gets;exit};sleep(#{sec})" + IO.popen([RUBY, '-e', code], 'r+') do |f| pid = f.pid f.gets t0 = Time.now @@ -1473,10 +1472,11 @@ def test_wait_exception th.kill.join end t1 = Time.now + diff = t1 - t0 + assert_operator(diff, :<, sec, + ->{"#{bug11340}: #{diff} seconds to interrupt Process.wait"}) f.puts end - assert_operator(t1 - t0, :<, 3, - ->{"#{bug11340}: #{t1-t0} seconds to interrupt Process.wait"}) end def test_abort diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index d67cdcb98440ab..edd44ca0ae0fbb 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -1,6 +1,7 @@ # -*- coding: us-ascii -*- require 'test/unit' +require 'timeout' require 'tmpdir' require 'tempfile' require_relative '../lib/jit_support' @@ -590,14 +591,18 @@ def test_set_program_name pid = spawn(EnvUtil.rubybin, "test-script") ps = nil + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 begin sleep 0.1 ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps - end until Process.wait(pid, Process::WNOHANG) + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop assert_match(/hello world/, ps) + assert_operator now, :<, stop Process.kill :KILL, pid - Process.wait(pid) + Timeout.timeout(5) { Process.wait(pid) } end end @@ -616,14 +621,18 @@ def test_setproctitle pid = spawn(EnvUtil.rubybin, "test-script") ps = nil + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + stop = now + 30 begin sleep 0.1 ps = `#{PSCMD.join(' ')} #{pid}` break if /hello world/ =~ ps - end until Process.wait(pid, Process::WNOHANG) + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + end until Process.wait(pid, Process::WNOHANG) || now > stop assert_match(/hello world/, ps) + assert_operator now, :<, stop Process.kill :KILL, pid - Process.wait(pid) + Timeout.timeout(5) { Process.wait(pid) } end end diff --git a/thread.c b/thread.c index 5a5e32379dd5ac..c58d3e0b9d075b 100644 --- a/thread.c +++ b/thread.c @@ -413,6 +413,10 @@ rb_vm_gvl_destroy(rb_vm_t *vm) gvl_release(vm); gvl_destroy(vm); rb_native_mutex_destroy(&vm->thread_destruct_lock); + if (0) { + /* may be held by running threads */ + rb_native_mutex_destroy(&vm->waitpid_lock); + } } void @@ -4131,6 +4135,9 @@ rb_gc_set_stack_end(VALUE **stack_end_p) #endif +/* signal.c */ +void ruby_sigchld_handler(rb_vm_t *); + /* * */ @@ -4163,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 @@ -4247,6 +4255,9 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r } rb_vm_living_threads_init(vm); rb_vm_living_threads_insert(vm, th); + + /* may be held by MJIT threads in parent */ + rb_native_mutex_initialize(&vm->waitpid_lock); vm->fork_gen++; vm->sleeper = 0; @@ -4999,6 +5010,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); @@ -5302,3 +5314,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 8a4825548bbdb9..722ce4448790e8 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -1369,7 +1369,7 @@ setup_communication_pipe(void) * @pre the calling context is in the timer thread. */ static inline void -timer_thread_sleep(rb_global_vm_lock_t* gvl) +timer_thread_sleep(rb_vm_t *vm) { int result; int need_polling; @@ -1382,7 +1382,15 @@ timer_thread_sleep(rb_global_vm_lock_t* gvl) need_polling = !ubf_threads_empty(); - if (gvl->waiting > 0 || need_polling) { + if (SIGCHLD_LOSSY && !need_polling) { + rb_native_mutex_lock(&vm->waitpid_lock); + if (!list_empty(&vm->waiting_pids) || !list_empty(&vm->waiting_grps)) { + need_polling = 1; + } + rb_native_mutex_unlock(&vm->waitpid_lock); + } + + if (vm->gvl.waiting > 0 || need_polling) { /* polling (TIME_QUANTUM_USEC usec) */ result = poll(pollfds, 1, TIME_QUANTUM_USEC/1000); } @@ -1421,7 +1429,7 @@ static rb_nativethread_lock_t timer_thread_lock; static rb_nativethread_cond_t timer_thread_cond; static inline void -timer_thread_sleep(rb_global_vm_lock_t* unused) +timer_thread_sleep(rb_vm_t *unused) { struct timespec ts; ts.tv_sec = 0; @@ -1485,7 +1493,14 @@ native_set_another_thread_name(rb_nativethread_id_t thread_id, VALUE name) static void * thread_timer(void *p) { - rb_global_vm_lock_t *gvl = (rb_global_vm_lock_t *)p; + rb_vm_t *vm = p; +#ifdef HAVE_PTHREAD_SIGMASK /* mainly to enable SIGCHLD */ + { + sigset_t mask; + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + } +#endif if (TT_DEBUG) WRITE_CONST(2, "start timer thread\n"); @@ -1507,7 +1522,7 @@ thread_timer(void *p) if (TT_DEBUG) WRITE_CONST(2, "tick\n"); /* wait */ - timer_thread_sleep(gvl); + timer_thread_sleep(vm); } #if USE_SLEEPY_TIMER_THREAD CLOSE_INVALIDATE(normal[0]); @@ -1579,7 +1594,7 @@ rb_thread_create_timer_thread(void) if (timer_thread.created) { rb_bug("rb_thread_create_timer_thread: Timer thread was already created\n"); } - err = pthread_create(&timer_thread.id, &attr, thread_timer, &vm->gvl); + err = pthread_create(&timer_thread.id, &attr, thread_timer, vm); pthread_attr_destroy(&attr); if (err == EINVAL) { @@ -1590,7 +1605,7 @@ rb_thread_create_timer_thread(void) * default stack size is enough for them: */ stack_size = 0; - err = pthread_create(&timer_thread.id, NULL, thread_timer, &vm->gvl); + err = pthread_create(&timer_thread.id, NULL, thread_timer, vm); } if (err != 0) { rb_warn("pthread_create failed for timer: %s, scheduling broken", @@ -1771,4 +1786,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 */ diff --git a/vm_core.h b/vm_core.h index ee151195d5bf06..10a697f8c50aaf 100644 --- a/vm_core.h +++ b/vm_core.h @@ -92,6 +92,24 @@ #define RUBY_NSIG NSIG +#if defined(SIGCLD) +# define RUBY_SIGCHLD (SIGCLD) +#elif defined(SIGCHLD) +# define RUBY_SIGCHLD (SIGCHLD) +#else +# define RUBY_SIGCHLD (0) +#endif + +/* platforms with broken or non-existent SIGCHLD work by polling */ +#if defined(__APPLE__) +# define SIGCHLD_LOSSY (1) +#else +# define SIGCHLD_LOSSY (0) +#endif + +/* define to 0 to test old code path */ +#define WAITPID_USE_SIGCHLD (RUBY_SIGCHLD || SIGCHLD_LOSSY) + #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start((a),(b)) @@ -553,6 +571,9 @@ typedef struct rb_vm_struct { #endif rb_serial_t fork_gen; + rb_nativethread_lock_t waitpid_lock; + struct list_head waiting_pids; /* PID > 0: <=> struct waitpid_state */ + struct list_head waiting_grps; /* PID <= 0: <=> struct waitpid_state */ struct list_head waiting_fds; /* <=> struct waiting_fd */ struct list_head living_threads; VALUE thgroup_default; @@ -1561,6 +1582,8 @@ 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->waiting_grps); list_head_init(&vm->living_threads); vm->living_thread_num = 0; } diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 20b47a581a40e0..fd852c21b766fd 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -760,7 +760,7 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define ssize_t int !endif #define PRI_LL_PREFIX "I64" -#define PRI_PIDT_PREFIX PRI_INT_PREFIX +#define PRI_PIDT_PREFIX "I" #define GETGROUPS_T int #define RETSIGTYPE void #define TYPEOF_TIMEVAL_TV_SEC long