thread.c: use rb_hrtime_t scalar for high-resolution time operations

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

[ruby-core:88582] [Misc #15014]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64533 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2018-08-25 06:58:35 +00:00
parent 7c31c2738c
commit b0253a7569
6 changed files with 278 additions and 234 deletions

View File

@ -2847,6 +2847,7 @@ thread.$(OBJEXT): {$(VPATH)}defines.h
thread.$(OBJEXT): {$(VPATH)}encoding.h thread.$(OBJEXT): {$(VPATH)}encoding.h
thread.$(OBJEXT): {$(VPATH)}eval_intern.h thread.$(OBJEXT): {$(VPATH)}eval_intern.h
thread.$(OBJEXT): {$(VPATH)}gc.h thread.$(OBJEXT): {$(VPATH)}gc.h
thread.$(OBJEXT): {$(VPATH)}hrtime.h
thread.$(OBJEXT): {$(VPATH)}id.h thread.$(OBJEXT): {$(VPATH)}id.h
thread.$(OBJEXT): {$(VPATH)}intern.h thread.$(OBJEXT): {$(VPATH)}intern.h
thread.$(OBJEXT): {$(VPATH)}internal.h thread.$(OBJEXT): {$(VPATH)}internal.h

120
hrtime.h Normal file
View File

@ -0,0 +1,120 @@
#ifndef RB_HRTIME_H
#define RB_HRTIME_H
#include "ruby/ruby.h"
#include <time.h>
#if defined(HAVE_SYS_TIME_H)
# include <sys/time.h>
#endif
/*
* Hi-res monotonic clock. It is currently nsec resolution, which has over
* 500 years of range.
*
* 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)
*/
#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 */
rb_hrtime_t rb_hrtime_now(void);
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;
}
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;
}
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);
}
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);
}
static inline rb_hrtime_t
rb_msec2hrtime(unsigned long msec)
{
return rb_hrtime_mul((rb_hrtime_t)msec, RB_HRTIME_PER_MSEC);
}
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);
}
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;
}
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 */

246
thread.c
View File

@ -74,6 +74,7 @@
#include "internal.h" #include "internal.h"
#include "iseq.h" #include "iseq.h"
#include "vm_core.h" #include "vm_core.h"
#include "hrtime.h"
#ifndef USE_NATIVE_THREAD_PRIORITY #ifndef USE_NATIVE_THREAD_PRIORITY
#define USE_NATIVE_THREAD_PRIORITY 0 #define USE_NATIVE_THREAD_PRIORITY 0
@ -97,18 +98,14 @@ enum SLEEP_FLAGS {
SLEEP_SPURIOUS_CHECK = 0x2 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 sleep_forever(rb_thread_t *th, unsigned int fl);
static void rb_thread_sleep_deadly_allow_spurious_wakeup(void); static void rb_thread_sleep_deadly_allow_spurious_wakeup(void);
static int rb_threadptr_dead(rb_thread_t *th); static int rb_threadptr_dead(rb_thread_t *th);
static void rb_check_deadlock(rb_vm_t *vm); static void rb_check_deadlock(rb_vm_t *vm);
static int rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th); 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 const char *thread_status_name(rb_thread_t *th, int detail);
static void timespec_add(struct timespec *, const struct timespec *); static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t);
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 *);
NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd)); NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd));
static int consume_communication_pipe(int fd); static int consume_communication_pipe(int fd);
static int check_signals_nogvl(rb_thread_t *, int sigwait_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
#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 static void
timeout_prepare(struct timespec **tsp, timeout_prepare(rb_hrtime_t **to, rb_hrtime_t *rel, rb_hrtime_t *end,
struct timespec *ts, struct timespec *end,
const struct timeval *timeout) const struct timeval *timeout)
{ {
if (timeout) { if (timeout) {
getclockofday(end); *rel = rb_timeval2hrtime(timeout);
timespec_add(end, timespec_for(ts, timeout)); *end = rb_hrtime_add(rb_hrtime_now(), *rel);
*tsp = ts; *to = rel;
} }
else { else {
*tsp = 0; *to = 0;
} }
} }
@ -596,13 +570,13 @@ rb_thread_terminate_all(void)
terminate_all(vm, th); terminate_all(vm, th);
while (vm_living_thread_num(vm) > 1) { 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 * Thread exiting routine in thread_start_func_2 notify
* me when the last sub-thread exit. * me when the last sub-thread exit.
*/ */
sleeping = 1; sleeping = 1;
native_sleep(th, &ts); native_sleep(th, &rel);
RUBY_VM_CHECK_INTS_BLOCKING(ec); RUBY_VM_CHECK_INTS_BLOCKING(ec);
sleeping = 0; sleeping = 0;
} }
@ -931,7 +905,7 @@ rb_thread_create(VALUE (*fn)(ANYARGS), void *arg)
struct join_arg { struct join_arg {
rb_thread_t *target, *waiting; rb_thread_t *target, *waiting;
struct timespec *limit; rb_hrtime_t *limit;
}; };
static VALUE static VALUE
@ -960,11 +934,10 @@ thread_join_sleep(VALUE arg)
{ {
struct join_arg *p = (struct join_arg *)arg; struct join_arg *p = (struct join_arg *)arg;
rb_thread_t *target_th = p->target, *th = p->waiting; rb_thread_t *target_th = p->target, *th = p->waiting;
struct timespec end; rb_hrtime_t end;
if (p->limit) { if (p->limit) {
getclockofday(&end); end = rb_hrtime_add(*p->limit, rb_hrtime_now());
timespec_add(&end, p->limit);
} }
while (target_th->status != THREAD_KILLED) { while (target_th->status != THREAD_KILLED) {
@ -976,7 +949,7 @@ thread_join_sleep(VALUE arg)
th->vm->sleeper--; th->vm->sleeper--;
} }
else { 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_debug("thread_join: timeout (thid: %"PRI_THREAD_ID")\n",
thread_id_str(target_th)); thread_id_str(target_th));
return Qfalse; return Qfalse;
@ -993,7 +966,7 @@ thread_join_sleep(VALUE arg)
} }
static VALUE 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(); rb_thread_t *th = GET_THREAD();
struct join_arg arg; struct join_arg arg;
@ -1007,7 +980,7 @@ thread_join(rb_thread_t *target_th, struct timespec *ts)
arg.target = target_th; arg.target = target_th;
arg.waiting = th; arg.waiting = th;
arg.limit = ts; arg.limit = rel;
thread_debug("thread_join (thid: %"PRI_THREAD_ID", status: %s)\n", thread_debug("thread_join (thid: %"PRI_THREAD_ID", status: %s)\n",
thread_id_str(target_th), thread_status_name(target_th, TRUE)); thread_id_str(target_th), thread_status_name(target_th, TRUE));
@ -1052,7 +1025,7 @@ thread_join(rb_thread_t *target_th, struct timespec *ts)
return target_th->self; return target_th->self;
} }
static struct timespec *double2timespec(struct timespec *, double); static rb_hrtime_t *double2hrtime(rb_hrtime_t *, double);
/* /*
* call-seq: * call-seq:
@ -1097,8 +1070,7 @@ static VALUE
thread_join_m(int argc, VALUE *argv, VALUE self) thread_join_m(int argc, VALUE *argv, VALUE self)
{ {
VALUE limit; VALUE limit;
struct timespec timespec; rb_hrtime_t rel, *to = 0;
struct timespec *ts = 0;
rb_scan_args(argc, argv, "01", &limit); rb_scan_args(argc, argv, "01", &limit);
@ -1109,17 +1081,14 @@ thread_join_m(int argc, VALUE *argv, VALUE self)
switch (TYPE(limit)) { switch (TYPE(limit)) {
case T_NIL: break; case T_NIL: break;
case T_FIXNUM: case T_FIXNUM:
timespec.tv_sec = NUM2TIMET(limit); rel = rb_sec2hrtime(NUM2TIMET(limit));
if (timespec.tv_sec < 0) to = &rel;
timespec.tv_sec = 0;
timespec.tv_nsec = 0;
ts = &timespec;
break; break;
default: default:
ts = double2timespec(&timespec, 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);
} }
/* /*
@ -1158,8 +1127,8 @@ thread_value(VALUE self)
#define TIMESPEC_SEC_MAX TIMET_MAX #define TIMESPEC_SEC_MAX TIMET_MAX
#define TIMESPEC_SEC_MIN TIMET_MIN #define TIMESPEC_SEC_MIN TIMET_MIN
static struct timespec * static rb_hrtime_t *
double2timespec(struct timespec *ts, double d) double2hrtime(rb_hrtime_t *hrt, double d)
{ {
/* assume timespec.tv_sec has same signedness as time_t */ /* assume timespec.tv_sec has same signedness as time_t */
const double TIMESPEC_SEC_MAX_PLUS_ONE = TIMET_MAX_PLUS_ONE; const double TIMESPEC_SEC_MAX_PLUS_ONE = TIMET_MAX_PLUS_ONE;
@ -1168,18 +1137,31 @@ double2timespec(struct timespec *ts, double d)
return NULL; return NULL;
} }
else if (d <= 0) { else if (d <= 0) {
ts->tv_sec = 0; *hrt = 0;
ts->tv_nsec = 0;
} }
else { else {
ts->tv_sec = (time_t)d; *hrt = (rb_hrtime_t)(d * (double)RB_HRTIME_PER_SEC);
ts->tv_nsec = (long)((d - (time_t)d) * 1e9);
if (ts->tv_nsec < 0) {
ts->tv_nsec += (long)1e9;
ts->tv_sec -= 1;
} }
return hrt;
} }
return ts;
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);
}
rb_hrtime_t
rb_hrtime_now(void)
{
struct timespec ts;
getclockofday(&ts);
return rb_timespec2hrtime(&ts);
} }
static void static void
@ -1208,102 +1190,39 @@ sleep_forever(rb_thread_t *th, unsigned int fl)
th->status = prev_status; 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 * @end is the absolute time when @ts is set to expire
* Returns true if @end has past * Returns true if @end has past
* Updates @ts and returns false otherwise * Updates @ts and returns false otherwise
*/ */
static int static int
timespec_update_expire(struct timespec *ts, const struct timespec *end) hrtime_update_expire(rb_hrtime_t *timeout, const rb_hrtime_t end)
{ {
struct timespec now; rb_hrtime_t now = rb_hrtime_now();
getclockofday(&now); if (now > end) return 1;
if (timespec_cmp(&now, end) >= 0) return 1; thread_debug("hrtime_update_expire: "
thread_debug("timespec_update_expire: " "%"PRI_64_PREFIX"u > %"PRI_64_PREFIX"u\n",
"%"PRI_TIMET_PREFIX"d.%.6ld > %"PRI_TIMET_PREFIX"d.%.6ld\n", end, now);
(time_t)end->tv_sec, (long)end->tv_nsec, *timeout = end - now;
(time_t)now.tv_sec, (long)now.tv_nsec);
*ts = *end;
timespec_sub(ts, &now);
return 0; return 0;
} }
static void 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; enum rb_thread_status prev_status = th->status;
int woke; int woke;
rb_hrtime_t end = rb_hrtime_add(rb_hrtime_now(), rel);
getclockofday(&end);
timespec_add(&end, &ts);
th->status = THREAD_STOPPED; th->status = THREAD_STOPPED;
RUBY_VM_CHECK_INTS_BLOCKING(th->ec); RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
while (th->status == THREAD_STOPPED) { while (th->status == THREAD_STOPPED) {
native_sleep(th, &ts); native_sleep(th, &rel);
woke = vm_check_ints_blocking(th->ec); woke = vm_check_ints_blocking(th->ec);
if (woke && !(fl & SLEEP_SPURIOUS_CHECK)) if (woke && !(fl & SLEEP_SPURIOUS_CHECK))
break; break;
if (timespec_update_expire(&ts, &end)) if (hrtime_update_expire(&rel, end))
break; break;
} }
th->status = prev_status; th->status = prev_status;
@ -1334,10 +1253,8 @@ void
rb_thread_wait_for(struct timeval time) rb_thread_wait_for(struct timeval time)
{ {
rb_thread_t *th = GET_THREAD(); rb_thread_t *th = GET_THREAD();
struct timespec ts;
timespec_for(&ts, &time); sleep_hrtime(th, rb_timeval2hrtime(&time), SLEEP_SPURIOUS_CHECK);
sleep_timespec(th, ts, SLEEP_SPURIOUS_CHECK);
} }
/* /*
@ -3848,8 +3765,7 @@ rb_fd_set(int fd, rb_fdset_t *set)
#endif #endif
static int static int
wait_retryable(int *result, int errnum, struct timespec *timeout, wait_retryable(int *result, int errnum, rb_hrtime_t *rel, rb_hrtime_t end)
const struct timespec *end)
{ {
if (*result < 0) { if (*result < 0) {
switch (errnum) { switch (errnum) {
@ -3858,9 +3774,8 @@ wait_retryable(int *result, int errnum, struct timespec *timeout,
case ERESTART: case ERESTART:
#endif #endif
*result = 0; *result = 0;
if (timeout && timespec_update_expire(timeout, end)) { if (rel && hrtime_update_expire(rel, end)) {
timeout->tv_sec = 0; *rel = 0;
timeout->tv_nsec = 0;
} }
return TRUE; return TRUE;
} }
@ -3868,8 +3783,8 @@ wait_retryable(int *result, int errnum, struct timespec *timeout,
} }
else if (*result == 0) { else if (*result == 0) {
/* check for spurious wakeup */ /* check for spurious wakeup */
if (timeout) { if (rel) {
return !timespec_update_expire(timeout, end); return !hrtime_update_expire(rel, end);
} }
return TRUE; return TRUE;
} }
@ -3906,15 +3821,15 @@ select_set_free(VALUE p)
return Qfalse; return Qfalse;
} }
static const struct timespec * static const rb_hrtime_t *
sigwait_timeout(rb_thread_t *th, int sigwait_fd, const struct timespec *orig, sigwait_timeout(rb_thread_t *th, int sigwait_fd, const rb_hrtime_t *orig,
int *drained_p) 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)) { if (sigwait_fd >= 0 && (!ubf_threads_empty() || BUSY_WAIT_SIGNALS)) {
*drained_p = check_signals_nogvl(th, sigwait_fd); *drained_p = check_signals_nogvl(th, sigwait_fd);
if (!orig || timespec_cmp(orig, &quantum) > 0) if (!orig || *orig > quantum)
return &quantum; return &quantum;
} }
@ -3927,11 +3842,9 @@ do_select(VALUE p)
struct select_set *set = (struct select_set *)p; struct select_set *set = (struct select_set *)p;
int MAYBE_UNUSED(result); int MAYBE_UNUSED(result);
int lerrno; int lerrno;
struct timespec ts, end, *tsp; rb_hrtime_t *to, rel, end;
const struct timespec *to;
struct timeval tv;
timeout_prepare(&tsp, &ts, &end, set->timeout); timeout_prepare(&to, &rel, &end, set->timeout);
#define restore_fdset(dst, src) \ #define restore_fdset(dst, src) \
((dst) ? rb_fd_dup(dst, src) : (void)0) ((dst) ? rb_fd_dup(dst, src) : (void)0)
#define do_select_update() \ #define do_select_update() \
@ -3945,9 +3858,12 @@ do_select(VALUE p)
lerrno = 0; lerrno = 0;
BLOCKING_REGION(set->th, { 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, 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; if (result < 0) lerrno = errno;
}, set->sigwait_fd >= 0 ? ubf_sigwait : ubf_select, set->th, FALSE); }, set->sigwait_fd >= 0 ? ubf_sigwait : ubf_select, set->th, FALSE);
@ -3958,7 +3874,7 @@ do_select(VALUE p)
} }
RUBY_VM_CHECK_INTS_BLOCKING(set->th->ec); /* may raise */ 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) { if (result < 0) {
errno = lerrno; errno = lerrno;
@ -4082,14 +3998,13 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)
{ {
struct pollfd fds[2]; struct pollfd fds[2];
int result = 0, lerrno; int result = 0, lerrno;
struct timespec ts, end, *tsp; rb_hrtime_t *to, rel, end;
const struct timespec *to;
int drained; int drained;
rb_thread_t *th = GET_THREAD(); rb_thread_t *th = GET_THREAD();
nfds_t nfds; nfds_t nfds;
rb_unblock_function_t *ubf; rb_unblock_function_t *ubf;
timeout_prepare(&tsp, &ts, &end, timeout); timeout_prepare(&to, &rel, &end, timeout);
fds[0].fd = fd; fds[0].fd = fd;
fds[0].events = (short)events; fds[0].events = (short)events;
do { do {
@ -4109,8 +4024,11 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)
lerrno = 0; lerrno = 0;
BLOCKING_REGION(th, { BLOCKING_REGION(th, {
to = sigwait_timeout(th, fds[1].fd, tsp, &drained); const rb_hrtime_t *sto;
result = ppoll(fds, nfds, to, NULL); 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; if (result < 0) lerrno = errno;
}, ubf, th, FALSE); }, ubf, th, FALSE);
@ -4124,7 +4042,7 @@ rb_wait_for_single_fd(int fd, int events, struct timeval *timeout)
rb_sigwait_fd_migrate(th->vm); rb_sigwait_fd_migrate(th->vm);
} }
RUBY_VM_CHECK_INTS_BLOCKING(th->ec); RUBY_VM_CHECK_INTS_BLOCKING(th->ec);
} while (wait_retryable(&result, lerrno, tsp, &end)); } while (wait_retryable(&result, lerrno, to, end));
if (result < 0) { if (result < 0) {
errno = lerrno; errno = lerrno;

View File

@ -123,9 +123,9 @@ static void clear_thread_cache_altstack(void);
static void ubf_wakeup_all_threads(void); static void ubf_wakeup_all_threads(void);
static int ubf_threads_empty(void); static int ubf_threads_empty(void);
static int native_cond_timedwait(rb_nativethread_cond_t *, pthread_mutex_t *, static int native_cond_timedwait(rb_nativethread_cond_t *, pthread_mutex_t *,
const struct timespec *); const rb_hrtime_t *abs);
static const struct timespec *sigwait_timeout(rb_thread_t *, int sigwait_fd, static const rb_hrtime_t *sigwait_timeout(rb_thread_t *, int sigwait_fd,
const struct timespec *, const rb_hrtime_t *,
int *drained_p); int *drained_p);
static void ubf_timer_disarm(void); static void ubf_timer_disarm(void);
static void threadptr_trap_interrupt(rb_thread_t *); static void threadptr_trap_interrupt(rb_thread_t *);
@ -159,8 +159,7 @@ static const void *const condattr_monotonic = NULL;
#define TIME_QUANTUM_USEC (TIME_QUANTUM_MSEC * 1000) #define TIME_QUANTUM_USEC (TIME_QUANTUM_MSEC * 1000)
#define TIME_QUANTUM_NSEC (TIME_QUANTUM_USEC * 1000) #define TIME_QUANTUM_NSEC (TIME_QUANTUM_USEC * 1000)
static struct timespec native_cond_timeout(rb_nativethread_cond_t *, static rb_hrtime_t native_cond_timeout(rb_nativethread_cond_t *, rb_hrtime_t);
struct timespec rel);
/* /*
* Designate the next gvl.timer thread, favor the last thread in * Designate the next gvl.timer thread, favor the last thread in
@ -186,19 +185,17 @@ designate_timer_thread(rb_vm_t *vm)
static void static void
do_gvl_timer(rb_vm_t *vm, rb_thread_t *th) 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; native_thread_data_t *nd = &th->native_thread_data;
/* take over wakeups from UBF_TIMER */ /* take over wakeups from UBF_TIMER */
ubf_timer_disarm(); ubf_timer_disarm();
if (vm->gvl.timer_err == ETIMEDOUT) { if (vm->gvl.timer_err == ETIMEDOUT) {
ts.tv_sec = 0; abs = native_cond_timeout(&nd->cond.gvlq, TIME_QUANTUM_NSEC);
ts.tv_nsec = TIME_QUANTUM_NSEC;
ts = native_cond_timeout(&nd->cond.gvlq, ts);
} }
vm->gvl.timer = th; 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; vm->gvl.timer = 0;
ubf_wakeup_all_threads(); ubf_wakeup_all_threads();
@ -499,9 +496,11 @@ rb_native_cond_wait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex)
} }
static int 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; int r;
struct timespec ts;
/* /*
* An old Linux may return EINTR. Even though POSIX says * An old Linux may return EINTR. Even though POSIX says
@ -510,7 +509,7 @@ native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, cons
* Let's hide it from arch generic code. * Let's hide it from arch generic code.
*/ */
do { do {
r = pthread_cond_timedwait(cond, mutex, ts); r = pthread_cond_timedwait(cond, mutex, rb_hrtime2timespec(&ts, abs));
} while (r == EINTR); } while (r == EINTR);
if (r != 0 && r != ETIMEDOUT) { if (r != 0 && r != ETIMEDOUT) {
@ -520,20 +519,18 @@ native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, cons
return r; return r;
} }
static struct timespec static rb_hrtime_t
native_cond_timeout(rb_nativethread_cond_t *cond, struct timespec timeout_rel) native_cond_timeout(rb_nativethread_cond_t *cond, const rb_hrtime_t rel)
{ {
struct timespec abs;
if (condattr_monotonic) { if (condattr_monotonic) {
getclockofday(&abs); return rb_hrtime_add(rb_hrtime_now(), rel);
} }
else { else {
rb_timespec_now(&abs); struct timespec ts;
}
timespec_add(&abs, &timeout_rel);
return abs; rb_timespec_now(&ts);
return rb_hrtime_add(rb_timespec2hrtime(&ts), rel);
}
} }
#define native_cleanup_push pthread_cleanup_push #define native_cleanup_push pthread_cleanup_push
@ -1056,13 +1053,13 @@ thread_cache_reset(void)
* worst case network latency across the globe) without wasting memory * worst case network latency across the globe) without wasting memory
*/ */
#ifndef THREAD_CACHE_TIME #ifndef THREAD_CACHE_TIME
# define THREAD_CACHE_TIME 3 # define THREAD_CACHE_TIME ((rb_hrtime_t)3 * RB_HRTIME_PER_SEC)
#endif #endif
static rb_thread_t * static rb_thread_t *
register_cached_thread_and_wait(void *altstack) 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; struct cached_thread_entry entry;
rb_native_cond_initialize(&entry.cond); rb_native_cond_initialize(&entry.cond);
@ -1218,13 +1215,11 @@ ubf_pthread_cond_signal(void *ptr)
} }
static void 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_lock_t *lock = &th->interrupt_lock;
rb_nativethread_cond_t *cond = &th->native_thread_data.cond.intr; 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 /* 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 * 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 * considered as a kind of spurious wakeup. The caller to native_sleep
@ -1233,13 +1228,7 @@ native_cond_sleep(rb_thread_t *th, struct timespec *timeout_rel)
* See also [Bug #1341] [ruby-core:29702] * See also [Bug #1341] [ruby-core:29702]
* http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html
*/ */
if (timeout_rel->tv_sec > 100000000) { const rb_hrtime_t max = (rb_hrtime_t)100000000 * RB_HRTIME_PER_SEC;
timeout_rel->tv_sec = 100000000;
timeout_rel->tv_nsec = 0;
}
timeout = native_cond_timeout(cond, *timeout_rel);
}
GVL_UNLOCK_BEGIN(th); GVL_UNLOCK_BEGIN(th);
{ {
@ -1252,10 +1241,19 @@ native_cond_sleep(rb_thread_t *th, struct timespec *timeout_rel)
thread_debug("native_sleep: interrupted before sleep\n"); thread_debug("native_sleep: interrupted before sleep\n");
} }
else { else {
if (!timeout_rel) if (!rel) {
rb_native_cond_wait(cond, lock); 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; th->unblock.func = 0;
@ -1970,15 +1968,12 @@ rb_sigwait_sleep(rb_thread_t *th, int sigwait_fd, const struct timespec *ts)
check_signals_nogvl(th, sigwait_fd); check_signals_nogvl(th, sigwait_fd);
} }
else { else {
struct timespec end, diff; rb_hrtime_t rel, end;
const struct timespec *to;
int n = 0; int n = 0;
if (ts) { if (ts) {
getclockofday(&end); rel = rb_timespec2hrtime(ts);
timespec_add(&end, ts); end = rb_hrtime_add(rb_hrtime_now(), rel);
diff = *ts;
ts = &diff;
} }
/* /*
* tricky: this needs to return on spurious wakeup (no auto-retry). * tricky: this needs to return on spurious wakeup (no auto-retry).
@ -1986,21 +1981,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 * wakeups, so we care about the result of consume_communication_pipe
*/ */
for (;;) { 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; 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)) if (check_signals_nogvl(th, sigwait_fd))
return; return;
if (n || (th && RUBY_VM_INTERRUPTED(th->ec))) if (n || (th && RUBY_VM_INTERRUPTED(th->ec)))
return; return;
if (ts && timespec_update_expire(&diff, &end)) if (ts && hrtime_update_expire(&rel, end))
return; return;
} }
} }
} }
static void 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); int sigwait_fd = rb_sigwait_fd_get(th);
@ -2012,7 +2009,8 @@ native_sleep(rb_thread_t *th, struct timespec *timeout_rel)
GVL_UNLOCK_BEGIN(th); GVL_UNLOCK_BEGIN(th);
if (!RUBY_VM_INTERRUPTED(th->ec)) { 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 { else {
check_signals_nogvl(th, sigwait_fd); check_signals_nogvl(th, sigwait_fd);
@ -2023,7 +2021,7 @@ native_sleep(rb_thread_t *th, struct timespec *timeout_rel)
rb_sigwait_fd_migrate(th->vm); rb_sigwait_fd_migrate(th->vm);
} }
else { else {
native_cond_sleep(th, timeout_rel); native_cond_sleep(th, rel);
} }
} }

View File

@ -248,8 +248,8 @@ do_mutex_lock(VALUE self, int interruptible_p)
while (mutex->th != th) { while (mutex->th != th) {
enum rb_thread_status prev_status = th->status; enum rb_thread_status prev_status = th->status;
struct timespec *timeout = 0; rb_hrtime_t *timeout = 0;
struct timespec ts = { 0, 100000000 }; /* 100ms */ rb_hrtime_t rel = rb_msec2hrtime(100);
th->status = THREAD_STOPPED_FOREVER; th->status = THREAD_STOPPED_FOREVER;
th->locking_mutex = self; 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) && if ((vm_living_thread_num(th->vm) == th->vm->sleeper) &&
!patrol_thread) { !patrol_thread) {
timeout = &ts; timeout = &rel;
patrol_thread = th; patrol_thread = th;
} }
@ -458,8 +458,9 @@ rb_mutex_sleep_forever(VALUE time)
static VALUE static VALUE
rb_mutex_wait_for(VALUE time) rb_mutex_wait_for(VALUE time)
{ {
struct timespec *t = (struct timespec*)time; rb_hrtime_t *rel = (rb_hrtime_t *)time;
sleep_timespec(GET_THREAD(), *t, 0); /* permit spurious check */ /* permit spurious check */
sleep_hrtime(GET_THREAD(), *rel, 0);
return Qnil; return Qnil;
} }
@ -472,16 +473,17 @@ rb_mutex_sleep(VALUE self, VALUE timeout)
if (!NIL_P(timeout)) { if (!NIL_P(timeout)) {
t = rb_time_interval(timeout); t = rb_time_interval(timeout);
} }
rb_mutex_unlock(self); rb_mutex_unlock(self);
beg = time(0); beg = time(0);
if (NIL_P(timeout)) { if (NIL_P(timeout)) {
rb_ensure(rb_mutex_sleep_forever, Qnil, mutex_lock_uninterruptible, self); rb_ensure(rb_mutex_sleep_forever, Qnil, mutex_lock_uninterruptible, self);
} }
else { else {
struct timespec ts; rb_hrtime_t rel = rb_timeval2hrtime(&t);
VALUE tsp = (VALUE)timespec_for(&ts, &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()); RUBY_VM_CHECK_INTS_BLOCKING(GET_EC());
end = time(0) - beg; end = time(0) - beg;

View File

@ -276,11 +276,16 @@ rb_w32_Sleep(unsigned long msec)
return ret; return ret;
} }
static void static DWORD
native_sleep(rb_thread_t *th, struct timespec *ts) hrtime2msec(rb_hrtime_t hrt)
{ {
const volatile DWORD msec = (ts) ? return (DWORD)hrt / (DWORD)RB_HRTIME_PER_MSEC;
(DWORD)(ts->tv_sec * 1000 + ts->tv_nsec / 1000000) : INFINITE; }
static void
native_sleep(rb_thread_t *th, rb_hrtime_t *rel)
{
const volatile DWORD msec = rel ? hrtime2msec(*rel) : INFINITE;
GVL_UNLOCK_BEGIN(th); GVL_UNLOCK_BEGIN(th);
{ {