From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS60729 185.220.102.0/24 X-Spam-Status: No, score=-1.8 required=3.0 tests=BAYES_00,RCVD_IN_XBL, RDNS_NONE,SPF_FAIL,SPF_HELO_FAIL,TO_EQ_FM_DOM_SPF_FAIL shortcircuit=no autolearn=no autolearn_force=no version=3.4.1 Received: from 80x24.org (unknown [185.220.102.7]) by dcvr.yhbt.net (Postfix) with ESMTP id B45B81F954 for ; Thu, 23 Aug 2018 10:35:04 +0000 (UTC) From: Eric Wong To: spew@80x24.org Subject: [PATCH v3] thread.c: use rb_hrtime_t scalar for high-resolution time operations Date: Thu, 23 Aug 2018 10:35:03 +0000 Message-Id: <20180823103503.29516-1-e@80x24.org> List-Id: Relying on "struct timespec" was too annoying API-wise and used more stack space. "double" was a bit wacky w.r.t rounding in the past, so now we'll switch to using a 64-bit type. Unsigned 64-bit integer is able to give us over nearly 585 years of range with nanoseconds. This range is good enough for the Linux kernel internal time representation, so it ought to be good enough for us. This reduces the stack usage of functions while GVL is held (and thus subject to marking) on x86-64 Linux (with ppoll): rb_wait_for_single_fd 120 => 104 do_select 120 => 88 --- common.mk | 1 + hrtime.h | 168 ++++++++++++++++++++++++++++++ thread.c | 259 +++++++++++++++++------------------------------ thread_pthread.c | 110 ++++++++++---------- thread_sync.c | 18 ++-- thread_win32.c | 11 +- 6 files changed, 332 insertions(+), 235 deletions(-) create mode 100644 hrtime.h diff --git a/common.mk b/common.mk index 5feca8ade0..5cdbe9e651 100644 --- a/common.mk +++ b/common.mk @@ -2847,6 +2847,7 @@ thread.$(OBJEXT): {$(VPATH)}defines.h thread.$(OBJEXT): {$(VPATH)}encoding.h thread.$(OBJEXT): {$(VPATH)}eval_intern.h thread.$(OBJEXT): {$(VPATH)}gc.h +thread.$(OBJEXT): {$(VPATH)}hrtime.h thread.$(OBJEXT): {$(VPATH)}id.h thread.$(OBJEXT): {$(VPATH)}intern.h thread.$(OBJEXT): {$(VPATH)}internal.h diff --git a/hrtime.h b/hrtime.h new file mode 100644 index 0000000000..c3dfe776a2 --- /dev/null +++ b/hrtime.h @@ -0,0 +1,168 @@ +#ifndef RB_HRTIME_H +#define RB_HRTIME_H +#include "ruby/ruby.h" +#include +#if defined(HAVE_SYS_TIME_H) +# include +#endif + +/* + * Hi-res monotonic clock. It is currently nsec resolution, which has over + * 500 years of range (unsigned). + * + * TBD: Is nsec even necessary? usec resolution seems enough for userspace + * and it'll be suitable for use with devices lasting over 500,000 years + * (maybe some devices designed for long-term space travel) + * + * Current API: + * + * * rb_hrtime_now - current clock value (monotonic if available) + * * rb_hrtime_mul - multiply with overflow check + * * rb_hrtime_add - add with overflow check + * * rb_timeval2hrtime - convert from timeval + * * rb_timespec2hrtime - convert from timespec + * * rb_msec2hrtime - convert from millisecond + * * rb_sec2hrtime - convert from time_t (seconds) + * * rb_hrtime2timeval - convert to timeval + * * rb_hrtime2timespec - convert to timespec + * + * Note: no conversion to milliseconds is provided here because different + * functions have different limits (e.g. epoll_wait vs w32_wait_events). + * So we provide RB_HRTIME_PER_MSEC and similar macros for implementing + * this for each use case. + */ +#define RB_HRTIME_PER_USEC ((rb_hrtime_t)1000) +#define RB_HRTIME_PER_MSEC (RB_HRTIME_PER_USEC * (rb_hrtime_t)1000) +#define RB_HRTIME_PER_SEC (RB_HRTIME_PER_MSEC * (rb_hrtime_t)1000) + +#define RB_HRTIME_MAX UINT64_MAX + +/* + * Lets try to support time travelers. Lets assume anybody with a time machine + * also has access to a modern gcc or clang with 128-bit int support + */ +#ifdef MY_RUBY_BUILD_MAY_TIME_TRAVEL +typedef int128_t rb_hrtime_t; +#else +typedef uint64_t rb_hrtime_t; +#endif + +/* thread.c */ +/* returns the value of the monotonic clock (if available) */ +rb_hrtime_t rb_hrtime_now(void); + +/* + * multiply @a and @b with overflow check and return the + * (clamped to RB_HRTIME_MAX) result. + */ +static inline rb_hrtime_t +rb_hrtime_mul(rb_hrtime_t a, rb_hrtime_t b) +{ + rb_hrtime_t c; + +#ifdef HAVE_BUILTIN___BUILTIN_MUL_OVERFLOW + if (__builtin_mul_overflow(a, b, &c)) + return RB_HRTIME_MAX; +#else + if (b != 0 && b > RB_HRTIME_MAX / b) /* overflow */ + return RB_HRTIME_MAX; + c = a * b; +#endif + return c; +} + +/* + * add @a and @b with overflow check and return the + * (clamped to RB_HRTIME_MAX) result. + */ +static inline rb_hrtime_t +rb_hrtime_add(rb_hrtime_t a, rb_hrtime_t b) +{ + rb_hrtime_t c; + +#ifdef HAVE_BUILTIN___BUILTIN_ADD_OVERFLOW + if (__builtin_add_overflow(a, b, &c)) + return RB_HRTIME_MAX; +#else + c = a + b; + if (c < a) /* overflow */ + return RB_HRTIME_MAX; +#endif + return c; +} + +/* + * convert a timeval struct to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_timeval2hrtime(const struct timeval *tv) +{ + rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)tv->tv_sec, RB_HRTIME_PER_SEC); + rb_hrtime_t u = rb_hrtime_mul((rb_hrtime_t)tv->tv_usec, RB_HRTIME_PER_USEC); + + return rb_hrtime_add(s, u); +} + +/* + * convert a timespec struct to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_timespec2hrtime(const struct timespec *ts) +{ + rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)ts->tv_sec, RB_HRTIME_PER_SEC); + + return rb_hrtime_add(s, (rb_hrtime_t)ts->tv_nsec); +} + +/* + * convert a millisecond value to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_msec2hrtime(unsigned long msec) +{ + return rb_hrtime_mul((rb_hrtime_t)msec, RB_HRTIME_PER_MSEC); +} + +/* + * convert a time_t value to rb_hrtime_t, clamping at RB_HRTIME_MAX + * Negative values will be clamped at 0. + */ +static inline rb_hrtime_t +rb_sec2hrtime(time_t sec) +{ + if (sec <= 0) return 0; + + return rb_hrtime_mul((rb_hrtime_t)sec, RB_HRTIME_PER_SEC); +} + +/* + * convert a rb_hrtime_t value to a timespec, suitable for calling + * functions like ppoll(2) or kevent(2) + */ +static inline struct timespec * +rb_hrtime2timespec(struct timespec *ts, const rb_hrtime_t *hrt) +{ + if (hrt) { + ts->tv_sec = *hrt / RB_HRTIME_PER_SEC; + ts->tv_nsec = (int32_t)(*hrt % RB_HRTIME_PER_SEC); + return ts; + } + return 0; +} + +/* + * convert a rb_hrtime_t value to a timeval, suitable for calling + * functions like select(2) + */ +static inline struct timeval * +rb_hrtime2timeval(struct timeval *tv, const rb_hrtime_t *hrt) +{ + if (hrt) { + tv->tv_sec = *hrt / RB_HRTIME_PER_SEC; + tv->tv_usec = (int32_t)((*hrt % RB_HRTIME_PER_SEC)/RB_HRTIME_PER_USEC); + + return tv; + } + return 0; +} +#endif /* RB_HRTIME_H */ diff --git a/thread.c b/thread.c index 9757f9c0e9..612a14f569 100644 --- a/thread.c +++ b/thread.c @@ -74,6 +74,7 @@ #include "internal.h" #include "iseq.h" #include "vm_core.h" +#include "hrtime.h" #ifndef USE_NATIVE_THREAD_PRIORITY #define USE_NATIVE_THREAD_PRIORITY 0 @@ -97,18 +98,14 @@ enum SLEEP_FLAGS { SLEEP_SPURIOUS_CHECK = 0x2 }; -static void sleep_timespec(rb_thread_t *, struct timespec, unsigned int fl); +static void sleep_hrtime(rb_thread_t *, rb_hrtime_t, unsigned int fl); static void sleep_forever(rb_thread_t *th, unsigned int fl); static void rb_thread_sleep_deadly_allow_spurious_wakeup(void); static int rb_threadptr_dead(rb_thread_t *th); static void rb_check_deadlock(rb_vm_t *vm); static int rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th); static const char *thread_status_name(rb_thread_t *th, int detail); -static void timespec_add(struct timespec *, const struct timespec *); -static void timespec_sub(struct timespec *, const struct timespec *); -static int timespec_cmp(const struct timespec *a, const struct timespec *b); -static int timespec_update_expire(struct timespec *, const struct timespec *); -static void getclockofday(struct timespec *); +static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t); NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd)); static int consume_communication_pipe(int fd); static int check_signals_nogvl(rb_thread_t *, int sigwait_fd); @@ -229,40 +226,17 @@ vm_living_thread_num(const rb_vm_t *vm) # endif #endif -static struct timespec * -timespec_for(struct timespec *ts, const struct timeval *tv) -{ - if (tv) { - ts->tv_sec = tv->tv_sec; - ts->tv_nsec = tv->tv_usec * 1000; - return ts; - } - return 0; -} - -static struct timeval * -timeval_for(struct timeval *tv, const struct timespec *ts) -{ - if (tv && ts) { - tv->tv_sec = ts->tv_sec; - tv->tv_usec = (int32_t)(ts->tv_nsec / 1000); /* 10**6 < 2**(32-1) */ - return tv; - } - return 0; -} - static void -timeout_prepare(struct timespec **tsp, - struct timespec *ts, struct timespec *end, - const struct timeval *timeout) +timeout_prepare(rb_hrtime_t **to, rb_hrtime_t *rel, rb_hrtime_t *end, + const struct timeval *timeout) { if (timeout) { - getclockofday(end); - timespec_add(end, timespec_for(ts, timeout)); - *tsp = ts; + *rel = rb_timeval2hrtime(timeout); + *end = rb_hrtime_add(rb_hrtime_now(), *rel); + *to = rel; } else { - *tsp = 0; + *to = 0; } } @@ -592,13 +566,13 @@ rb_thread_terminate_all(void) terminate_all(vm, th); while (vm_living_thread_num(vm) > 1) { - struct timespec ts = { 1, 0 }; + rb_hrtime_t rel = RB_HRTIME_PER_SEC; /* * Thread exiting routine in thread_start_func_2 notify * me when the last sub-thread exit. */ sleeping = 1; - native_sleep(th, &ts); + native_sleep(th, &rel); RUBY_VM_CHECK_INTS_BLOCKING(ec); sleeping = 0; } @@ -927,7 +901,7 @@ rb_thread_create(VALUE (*fn)(ANYARGS), void *arg) struct join_arg { rb_thread_t *target, *waiting; - struct timespec *limit; + rb_hrtime_t *limit; }; static VALUE @@ -956,11 +930,10 @@ thread_join_sleep(VALUE arg) { struct join_arg *p = (struct join_arg *)arg; rb_thread_t *target_th = p->target, *th = p->waiting; - struct timespec end; + rb_hrtime_t end; if (p->limit) { - getclockofday(&end); - timespec_add(&end, p->limit); + end = rb_hrtime_add(*p->limit, rb_hrtime_now()); } while (target_th->status != THREAD_KILLED) { @@ -972,7 +945,7 @@ thread_join_sleep(VALUE arg) th->vm->sleeper--; } else { - if (timespec_update_expire(p->limit, &end)) { + if (hrtime_update_expire(p->limit, end)) { thread_debug("thread_join: timeout (thid: %"PRI_THREAD_ID")\n", thread_id_str(target_th)); return Qfalse; @@ -989,7 +962,7 @@ thread_join_sleep(VALUE arg) } static VALUE -thread_join(rb_thread_t *target_th, struct timespec *ts) +thread_join(rb_thread_t *target_th, rb_hrtime_t *rel) { rb_thread_t *th = GET_THREAD(); struct join_arg arg; @@ -1003,7 +976,7 @@ thread_join(rb_thread_t *target_th, struct timespec *ts) arg.target = target_th; arg.waiting = th; - arg.limit = ts; + arg.limit = rel; thread_debug("thread_join (thid: %"PRI_THREAD_ID", status: %s)\n", thread_id_str(target_th), thread_status_name(target_th, TRUE)); @@ -1048,7 +1021,7 @@ thread_join(rb_thread_t *target_th, struct timespec *ts) return target_th->self; } -static struct timespec *double2timespec(struct timespec *, double); +static rb_hrtime_t *double2hrtime(rb_hrtime_t *, double); /* * call-seq: @@ -1093,8 +1066,7 @@ static VALUE thread_join_m(int argc, VALUE *argv, VALUE self) { VALUE limit; - struct timespec timespec; - struct timespec *ts = 0; + rb_hrtime_t rel, *to = 0; rb_scan_args(argc, argv, "01", &limit); @@ -1105,17 +1077,14 @@ thread_join_m(int argc, VALUE *argv, VALUE self) switch (TYPE(limit)) { case T_NIL: break; case T_FIXNUM: - timespec.tv_sec = NUM2TIMET(limit); - if (timespec.tv_sec < 0) - timespec.tv_sec = 0; - timespec.tv_nsec = 0; - ts = ×pec; + rel = rb_sec2hrtime(NUM2TIMET(limit)); + to = &rel; break; default: - ts = double2timespec(×pec, rb_num2dbl(limit)); + to = double2hrtime(&rel, rb_num2dbl(limit)); } - return thread_join(rb_thread_ptr(self), ts); + return thread_join(rb_thread_ptr(self), to); } /* @@ -1154,8 +1123,8 @@ thread_value(VALUE self) #define TIMESPEC_SEC_MAX TIMET_MAX #define TIMESPEC_SEC_MIN TIMET_MIN -static struct timespec * -double2timespec(struct timespec *ts, double d) +static rb_hrtime_t * +double2hrtime(rb_hrtime_t *hrt, double d) { /* assume timespec.tv_sec has same signedness as time_t */ const double TIMESPEC_SEC_MAX_PLUS_ONE = TIMET_MAX_PLUS_ONE; @@ -1164,18 +1133,36 @@ double2timespec(struct timespec *ts, double d) return NULL; } else if (d <= 0) { - ts->tv_sec = 0; - ts->tv_nsec = 0; + *hrt = 0; } else { - ts->tv_sec = (time_t)d; - ts->tv_nsec = (long)((d - (time_t)d) * 1e9); - if (ts->tv_nsec < 0) { - ts->tv_nsec += (long)1e9; - ts->tv_sec -= 1; - } + *hrt = (rb_hrtime_t)(d * (double)RB_HRTIME_PER_SEC); } - return ts; + return hrt; +} + +static void +getclockofday(struct timespec *ts) +{ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) + return; +#endif + rb_timespec_now(ts); +} + +/* + * Don't inline this, since library call is already time consuming + * and we don't want "struct timespec" on stack too long for GC + */ +NOINLINE(rb_hrtime_t rb_hrtime_now(void)); +rb_hrtime_t +rb_hrtime_now(void) +{ + struct timespec ts; + + getclockofday(&ts); + return rb_timespec2hrtime(&ts); } static void @@ -1204,102 +1191,39 @@ sleep_forever(rb_thread_t *th, unsigned int fl) th->status = prev_status; } -static void -getclockofday(struct timespec *ts) -{ -#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) - if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) - return; -#endif - rb_timespec_now(ts); -} - -static void -timespec_add(struct timespec *dst, const struct timespec *ts) -{ - if (TIMESPEC_SEC_MAX - ts->tv_sec < dst->tv_sec) - dst->tv_sec = TIMESPEC_SEC_MAX; - else - dst->tv_sec += ts->tv_sec; - if ((dst->tv_nsec += ts->tv_nsec) >= 1000000000) { - if (dst->tv_sec == TIMESPEC_SEC_MAX) { - dst->tv_nsec = 999999999; - } - else { - dst->tv_sec++; - dst->tv_nsec -= 1000000000; - } - } -} - -static void -timespec_sub(struct timespec *dst, const struct timespec *tv) -{ - dst->tv_sec -= tv->tv_sec; - if ((dst->tv_nsec -= tv->tv_nsec) < 0) { - --dst->tv_sec; - dst->tv_nsec += 1000000000; - } -} - -static int -timespec_cmp(const struct timespec *a, const struct timespec *b) -{ - if (a->tv_sec > b->tv_sec) { - return 1; - } - else if (a->tv_sec < b->tv_sec) { - return -1; - } - else { - if (a->tv_nsec > b->tv_nsec) { - return 1; - } - else if (a->tv_nsec < b->tv_nsec) { - return -1; - } - return 0; - } -} - /* * @end is the absolute time when @ts is set to expire * Returns true if @end has past * Updates @ts and returns false otherwise */ static int -timespec_update_expire(struct timespec *ts, const struct timespec *end) -{ - struct timespec now; - - getclockofday(&now); - if (timespec_cmp(&now, end) >= 0) return 1; - thread_debug("timespec_update_expire: " - "%"PRI_TIMET_PREFIX"d.%.6ld > %"PRI_TIMET_PREFIX"d.%.6ld\n", - (time_t)end->tv_sec, (long)end->tv_nsec, - (time_t)now.tv_sec, (long)now.tv_nsec); - *ts = *end; - timespec_sub(ts, &now); +hrtime_update_expire(rb_hrtime_t *timeout, const rb_hrtime_t end) +{ + rb_hrtime_t now = rb_hrtime_now(); + + if (now > end) return 1; + thread_debug("hrtime_update_expire: " + "%"PRI_64_PREFIX"u > %"PRI_64_PREFIX"u\n", + end, now); + *timeout = end - now; return 0; } static void -sleep_timespec(rb_thread_t *th, struct timespec ts, unsigned int fl) +sleep_hrtime(rb_thread_t *th, rb_hrtime_t rel, unsigned int fl) { - struct timespec end; enum rb_thread_status prev_status = th->status; int woke; + rb_hrtime_t end = rb_hrtime_add(rb_hrtime_now(), rel); - getclockofday(&end); - timespec_add(&end, &ts); th->status = THREAD_STOPPED; RUBY_VM_CHECK_INTS_BLOCKING(th->ec); while (th->status == THREAD_STOPPED) { - native_sleep(th, &ts); + native_sleep(th, &rel); woke = vm_check_ints_blocking(th->ec); if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) break; - if (timespec_update_expire(&ts, &end)) + if (hrtime_update_expire(&rel, end)) break; } th->status = prev_status; @@ -1330,10 +1254,8 @@ void rb_thread_wait_for(struct timeval time) { rb_thread_t *th = GET_THREAD(); - struct timespec ts; - timespec_for(&ts, &time); - sleep_timespec(th, ts, SLEEP_SPURIOUS_CHECK); + sleep_hrtime(th, rb_timeval2hrtime(&time), SLEEP_SPURIOUS_CHECK); } /* @@ -3844,8 +3766,7 @@ rb_fd_set(int fd, rb_fdset_t *set) #endif static int -wait_retryable(int *result, int errnum, struct timespec *timeout, - const struct timespec *end) +wait_retryable(int *result, int errnum, rb_hrtime_t *rel, rb_hrtime_t end) { if (*result < 0) { switch (errnum) { @@ -3854,9 +3775,8 @@ wait_retryable(int *result, int errnum, struct timespec *timeout, case ERESTART: #endif *result = 0; - if (timeout && timespec_update_expire(timeout, end)) { - timeout->tv_sec = 0; - timeout->tv_nsec = 0; + if (rel && hrtime_update_expire(rel, end)) { + *rel = 0; } return TRUE; } @@ -3864,8 +3784,8 @@ wait_retryable(int *result, int errnum, struct timespec *timeout, } else if (*result == 0) { /* check for spurious wakeup */ - if (timeout) { - return !timespec_update_expire(timeout, end); + if (rel) { + return !hrtime_update_expire(rel, end); } return TRUE; } @@ -3902,15 +3822,15 @@ select_set_free(VALUE p) return Qfalse; } -static const struct timespec * -sigwait_timeout(rb_thread_t *th, int sigwait_fd, const struct timespec *orig, +static const rb_hrtime_t * +sigwait_timeout(rb_thread_t *th, int sigwait_fd, const rb_hrtime_t *orig, int *drained_p) { - static const struct timespec quantum = { 0, TIME_QUANTUM_USEC * 1000 }; + static const rb_hrtime_t quantum = TIME_QUANTUM_USEC * 1000; if (sigwait_fd >= 0 && (!ubf_threads_empty() || BUSY_WAIT_SIGNALS)) { *drained_p = check_signals_nogvl(th, sigwait_fd); - if (!orig || timespec_cmp(orig, &quantum) > 0) + if (!orig || *orig > quantum) return &quantum; } @@ -3923,11 +3843,9 @@ do_select(VALUE p) struct select_set *set = (struct select_set *)p; int MAYBE_UNUSED(result); int lerrno; - struct timespec ts, end, *tsp; - const struct timespec *to; - struct timeval tv; + rb_hrtime_t *to, rel, end; - timeout_prepare(&tsp, &ts, &end, set->timeout); + timeout_prepare(&to, &rel, &end, set->timeout); #define restore_fdset(dst, src) \ ((dst) ? rb_fd_dup(dst, src) : (void)0) #define do_select_update() \ @@ -3941,9 +3859,12 @@ do_select(VALUE p) lerrno = 0; BLOCKING_REGION(set->th, { - to = sigwait_timeout(set->th, set->sigwait_fd, tsp, &drained); + const rb_hrtime_t *sto; + struct timeval tv; + + sto = sigwait_timeout(set->th, set->sigwait_fd, to, &drained); result = native_fd_select(set->max, set->rset, set->wset, set->eset, - timeval_for(&tv, to), set->th); + rb_hrtime2timeval(&tv, sto), set->th); if (result < 0) lerrno = errno; }, set->sigwait_fd >= 0 ? ubf_sigwait : ubf_select, set->th, FALSE); @@ -3954,7 +3875,7 @@ do_select(VALUE p) } RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); /* may raise */ - } while (wait_retryable(&result, lerrno, tsp, &end) && do_select_update()); + } while (wait_retryable(&result, lerrno, to, end) && do_select_update()); if (result < 0) { errno = lerrno; @@ -4078,14 +3999,13 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout) { struct pollfd fds[2]; int result = 0, lerrno; - struct timespec ts, end, *tsp; - const struct timespec *to; + rb_hrtime_t *to, rel, end; int drained; rb_thread_t *th = GET_THREAD(); nfds_t nfds; rb_unblock_function_t *ubf; - timeout_prepare(&tsp, &ts, &end, timeout); + timeout_prepare(&to, &rel, &end, timeout); fds[0].fd = fd; fds[0].events = (short)events; do { @@ -4105,8 +4025,11 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout) lerrno = 0; BLOCKING_REGION(th, { - to = sigwait_timeout(th, fds[1].fd, tsp, &drained); - result = ppoll(fds, nfds, to, NULL); + const rb_hrtime_t *sto; + struct timespec ts; + + sto = sigwait_timeout(th, fds[1].fd, to, &drained); + result = ppoll(fds, nfds, rb_hrtime2timespec(&ts, sto), NULL); if (result < 0) lerrno = errno; }, ubf, th, FALSE); @@ -4120,7 +4043,7 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout) rb_sigwait_fd_migrate(th->vm); } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); - } while (wait_retryable(&result, lerrno, tsp, &end)); + } while (wait_retryable(&result, lerrno, to, end)); if (result < 0) { errno = lerrno; diff --git a/thread_pthread.c b/thread_pthread.c index 2923c1514c..56f5f75cd4 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -116,9 +116,9 @@ static void clear_thread_cache_altstack(void); static void ubf_wakeup_all_threads(void); static int ubf_threads_empty(void); static int native_cond_timedwait(rb_nativethread_cond_t *, pthread_mutex_t *, - const struct timespec *); -static const struct timespec *sigwait_timeout(rb_thread_t *, int sigwait_fd, - const struct timespec *, + const rb_hrtime_t *abs); +static const rb_hrtime_t *sigwait_timeout(rb_thread_t *, int sigwait_fd, + const rb_hrtime_t *, int *drained_p); static void ubf_timer_disarm(void); static void threadptr_trap_interrupt(rb_thread_t *); @@ -152,8 +152,7 @@ static const void *const condattr_monotonic = NULL; #define TIME_QUANTUM_USEC (TIME_QUANTUM_MSEC * 1000) #define TIME_QUANTUM_NSEC (TIME_QUANTUM_USEC * 1000) -static struct timespec native_cond_timeout(rb_nativethread_cond_t *, - struct timespec rel); +static rb_hrtime_t native_cond_timeout(rb_nativethread_cond_t *, rb_hrtime_t); /* * Designate the next gvl.timer thread, favor the last thread in @@ -179,19 +178,17 @@ designate_timer_thread(rb_vm_t *vm) static void do_gvl_timer(rb_vm_t *vm, rb_thread_t *th) { - static struct timespec ts; + static rb_hrtime_t abs; native_thread_data_t *nd = &th->native_thread_data; /* take over wakeups from UBF_TIMER */ ubf_timer_disarm(); if (vm->gvl.timer_err == ETIMEDOUT) { - ts.tv_sec = 0; - ts.tv_nsec = TIME_QUANTUM_NSEC; - ts = native_cond_timeout(&nd->cond.gvlq, ts); + abs = native_cond_timeout(&nd->cond.gvlq, TIME_QUANTUM_NSEC); } vm->gvl.timer = th; - vm->gvl.timer_err = native_cond_timedwait(&nd->cond.gvlq, &vm->gvl.lock, &ts); + vm->gvl.timer_err = native_cond_timedwait(&nd->cond.gvlq, &vm->gvl.lock, &abs); vm->gvl.timer = 0; ubf_wakeup_all_threads(); @@ -492,9 +489,11 @@ rb_native_cond_wait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex) } static int -native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *ts) +native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, + const rb_hrtime_t *abs) { int r; + struct timespec ts; /* * An old Linux may return EINTR. Even though POSIX says @@ -503,7 +502,7 @@ native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, cons * Let's hide it from arch generic code. */ do { - r = pthread_cond_timedwait(cond, mutex, ts); + r = pthread_cond_timedwait(cond, mutex, rb_hrtime2timespec(&ts, abs)); } while (r == EINTR); if (r != 0 && r != ETIMEDOUT) { @@ -513,20 +512,18 @@ native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, cons return r; } -static struct timespec -native_cond_timeout(rb_nativethread_cond_t *cond, struct timespec timeout_rel) +static rb_hrtime_t +native_cond_timeout(rb_nativethread_cond_t *cond, const rb_hrtime_t rel) { - struct timespec abs; - if (condattr_monotonic) { - getclockofday(&abs); + return rb_hrtime_add(rb_hrtime_now(), rel); } else { - rb_timespec_now(&abs); - } - timespec_add(&abs, &timeout_rel); + struct timespec ts; - return abs; + rb_timespec_now(&ts); + return rb_hrtime_add(rb_timespec2hrtime(&ts), rel); + } } #define native_cleanup_push pthread_cleanup_push @@ -1049,13 +1046,13 @@ thread_cache_reset(void) * worst case network latency across the globe) without wasting memory */ #ifndef THREAD_CACHE_TIME -# define THREAD_CACHE_TIME 3 +# define THREAD_CACHE_TIME ((rb_hrtime_t)3 * RB_HRTIME_PER_SEC) #endif static rb_thread_t * register_cached_thread_and_wait(void *altstack) { - struct timespec end = { THREAD_CACHE_TIME, 0 }; + rb_hrtime_t end = THREAD_CACHE_TIME; struct cached_thread_entry entry; rb_native_cond_initialize(&entry.cond); @@ -1211,28 +1208,20 @@ ubf_pthread_cond_signal(void *ptr) } static void -native_cond_sleep(rb_thread_t *th, struct timespec *timeout_rel) +native_cond_sleep(rb_thread_t *th, rb_hrtime_t *rel) { - struct timespec timeout; rb_nativethread_lock_t *lock = &th->interrupt_lock; rb_nativethread_cond_t *cond = &th->native_thread_data.cond.intr; - if (timeout_rel) { - /* Solaris cond_timedwait() return EINVAL if an argument is greater than - * current_time + 100,000,000. So cut up to 100,000,000. This is - * considered as a kind of spurious wakeup. The caller to native_sleep - * should care about spurious wakeup. - * - * See also [Bug #1341] [ruby-core:29702] - * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html - */ - if (timeout_rel->tv_sec > 100000000) { - timeout_rel->tv_sec = 100000000; - timeout_rel->tv_nsec = 0; - } - - timeout = native_cond_timeout(cond, *timeout_rel); - } + /* Solaris cond_timedwait() return EINVAL if an argument is greater than + * current_time + 100,000,000. So cut up to 100,000,000. This is + * considered as a kind of spurious wakeup. The caller to native_sleep + * should care about spurious wakeup. + * + * See also [Bug #1341] [ruby-core:29702] + * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html + */ + const rb_hrtime_t max = (rb_hrtime_t)100000000 * RB_HRTIME_PER_SEC; GVL_UNLOCK_BEGIN(th); { @@ -1245,10 +1234,19 @@ native_cond_sleep(rb_thread_t *th, struct timespec *timeout_rel) thread_debug("native_sleep: interrupted before sleep\n"); } else { - if (!timeout_rel) + if (!rel) { rb_native_cond_wait(cond, lock); - else - native_cond_timedwait(cond, lock, &timeout); + } + else { + rb_hrtime_t end; + + if (*rel > max) { + *rel = max; + } + + end = native_cond_timeout(cond, *rel); + native_cond_timedwait(cond, lock, &end); + } } th->unblock.func = 0; @@ -1939,15 +1937,12 @@ rb_sigwait_sleep(rb_thread_t *th, int sigwait_fd, const struct timespec *ts) check_signals_nogvl(th, sigwait_fd); } else { - struct timespec end, diff; - const struct timespec *to; + rb_hrtime_t rel, end; int n = 0; if (ts) { - getclockofday(&end); - timespec_add(&end, ts); - diff = *ts; - ts = &diff; + rel = rb_timespec2hrtime(ts); + end = rb_hrtime_add(rb_hrtime_now(), rel); } /* * tricky: this needs to return on spurious wakeup (no auto-retry). @@ -1955,21 +1950,23 @@ rb_sigwait_sleep(rb_thread_t *th, int sigwait_fd, const struct timespec *ts) * wakeups, so we care about the result of consume_communication_pipe */ for (;;) { - to = sigwait_timeout(th, sigwait_fd, ts, &n); + const rb_hrtime_t *sto = sigwait_timeout(th, sigwait_fd, &rel, &n); + struct timespec tmp; + if (n) return; - n = ppoll(&pfd, 1, to, 0); + n = ppoll(&pfd, 1, rb_hrtime2timespec(&tmp, sto), 0); if (check_signals_nogvl(th, sigwait_fd)) return; if (n || (th && RUBY_VM_INTERRUPTED(th->ec))) return; - if (ts && timespec_update_expire(&diff, &end)) + if (ts && hrtime_update_expire(&rel, end)) return; } } } static void -native_sleep(rb_thread_t *th, struct timespec *timeout_rel) +native_sleep(rb_thread_t *th, rb_hrtime_t *rel) { int sigwait_fd = rb_sigwait_fd_get(th); @@ -1981,7 +1978,8 @@ native_sleep(rb_thread_t *th, struct timespec *timeout_rel) GVL_UNLOCK_BEGIN(th); if (!RUBY_VM_INTERRUPTED(th->ec)) { - rb_sigwait_sleep(th, sigwait_fd, timeout_rel); + struct timespec ts; + rb_sigwait_sleep(th, sigwait_fd, rb_hrtime2timespec(&ts, rel)); } else { check_signals_nogvl(th, sigwait_fd); @@ -1992,7 +1990,7 @@ native_sleep(rb_thread_t *th, struct timespec *timeout_rel) rb_sigwait_fd_migrate(th->vm); } else { - native_cond_sleep(th, timeout_rel); + native_cond_sleep(th, rel); } } diff --git a/thread_sync.c b/thread_sync.c index 76632ebe5f..4ed559ad67 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -248,8 +248,8 @@ do_mutex_lock(VALUE self, int interruptible_p) while (mutex->th != th) { enum rb_thread_status prev_status = th->status; - struct timespec *timeout = 0; - struct timespec ts = { 0, 100000000 }; /* 100ms */ + rb_hrtime_t *timeout = 0; + rb_hrtime_t rel = rb_msec2hrtime(100); th->status = THREAD_STOPPED_FOREVER; th->locking_mutex = self; @@ -261,7 +261,7 @@ do_mutex_lock(VALUE self, int interruptible_p) */ if ((vm_living_thread_num(th->vm) == th->vm->sleeper) && !patrol_thread) { - timeout = &ts; + timeout = &rel; patrol_thread = th; } @@ -458,8 +458,9 @@ rb_mutex_sleep_forever(VALUE time) static VALUE rb_mutex_wait_for(VALUE time) { - struct timespec *t = (struct timespec*)time; - sleep_timespec(GET_THREAD(), *t, 0); /* permit spurious check */ + rb_hrtime_t *rel = (rb_hrtime_t *)time; + /* permit spurious check */ + sleep_hrtime(GET_THREAD(), *rel, 0); return Qnil; } @@ -472,16 +473,17 @@ rb_mutex_sleep(VALUE self, VALUE timeout) if (!NIL_P(timeout)) { t = rb_time_interval(timeout); } + rb_mutex_unlock(self); beg = time(0); if (NIL_P(timeout)) { rb_ensure(rb_mutex_sleep_forever, Qnil, mutex_lock_uninterruptible, self); } else { - struct timespec ts; - VALUE tsp = (VALUE)timespec_for(&ts, &t); + rb_hrtime_t rel = rb_timeval2hrtime(&t); - rb_ensure(rb_mutex_wait_for, tsp, mutex_lock_uninterruptible, self); + rb_ensure(rb_mutex_wait_for, (VALUE)&rel, + mutex_lock_uninterruptible, self); } RUBY_VM_CHECK_INTS_BLOCKING(GET_EC()); end = time(0) - beg; diff --git a/thread_win32.c b/thread_win32.c index af9d3517fa..83896d81ff 100644 --- a/thread_win32.c +++ b/thread_win32.c @@ -276,11 +276,16 @@ rb_w32_Sleep(unsigned long msec) return ret; } +static DWORD +hrtime2msec(rb_hrtime_t hrt) +{ + return (DWORD)hrt / (DWORD)RB_HRTIME_PER_MSEC; +} + static void -native_sleep(rb_thread_t *th, struct timespec *ts) +native_sleep(rb_thread_t *th, rb_hrtime_t *rel) { - const volatile DWORD msec = (ts) ? - (DWORD)(ts->tv_sec * 1000 + ts->tv_nsec / 1000000) : INFINITE; + const volatile DWORD msec = rel ? hrtime2msec(*rel) : INFINITE; GVL_UNLOCK_BEGIN(th); { -- EW