Feature #14859 ยป 0001-implement-Timeout-in-VM.patch
benchmark/bm_timeout_mt_nested.rb | ||
---|---|---|
require 'timeout'
|
||
total = 10000
|
||
nthr = 8
|
||
nr = total / nthr
|
||
def nest_timeout(n, nthr)
|
||
n -= 1
|
||
if n > 0
|
||
Timeout.timeout(n) { nest_timeout(n, nthr) }
|
||
else
|
||
nthr.times { Thread.pass }
|
||
end
|
||
end
|
||
nthr.times.map do
|
||
Thread.new do
|
||
nr.times { nest_timeout(10, nthr) }
|
||
end
|
||
end.map(&:join)
|
benchmark/bm_timeout_mt_same.rb | ||
---|---|---|
require 'timeout'
|
||
total = 100000
|
||
nthr = 8
|
||
nr = total / nthr
|
||
nthr.times.map do
|
||
Thread.new do
|
||
nr.times { Timeout.timeout(5) { Thread.pass } }
|
||
end
|
||
end.map(&:join)
|
benchmark/bm_timeout_mt_ugly.rb | ||
---|---|---|
# unrealistic: this is the worst-case of insertion-sort-based timeout
|
||
require 'timeout'
|
||
total = 100000
|
||
nthr = 8
|
||
nr = total / nthr
|
||
nthr.times.map do
|
||
Thread.new do
|
||
i = nr
|
||
while (i -= 1) >= 0
|
||
Timeout.timeout(i + 1) { nthr.times { Thread.pass } }
|
||
end
|
||
end
|
||
end.map(&:join)
|
benchmark/bm_timeout_nested.rb | ||
---|---|---|
require 'timeout'
|
||
def nest_timeout(n)
|
||
n -= 1
|
||
if n > 0
|
||
Timeout.timeout(n) { nest_timeout(n) }
|
||
end
|
||
end
|
||
100000.times do
|
||
nest_timeout(10)
|
||
end
|
benchmark/bm_timeout_same.rb | ||
---|---|---|
require 'timeout'
|
||
100000.times { Timeout.timeout(5) {} }
|
benchmark/bm_timeout_zero.rb | ||
---|---|---|
require 'timeout'
|
||
100000.times { Timeout.timeout(0) {} }
|
common.mk | ||
---|---|---|
symbol.$(OBJEXT) \
|
||
thread.$(OBJEXT) \
|
||
time.$(OBJEXT) \
|
||
timeout.$(OBJEXT) \
|
||
transcode.$(OBJEXT) \
|
||
util.$(OBJEXT) \
|
||
variable.$(OBJEXT) \
|
||
... | ... | |
time.$(OBJEXT): {$(VPATH)}subst.h
|
||
time.$(OBJEXT): {$(VPATH)}time.c
|
||
time.$(OBJEXT): {$(VPATH)}timev.h
|
||
timeout.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
|
||
timeout.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
|
||
timeout.$(OBJEXT): $(CCAN_DIR)/list/list.h
|
||
timeout.$(OBJEXT): $(CCAN_DIR)/str/str.h
|
||
timeout.$(OBJEXT): $(hdrdir)/ruby/ruby.h
|
||
timeout.$(OBJEXT): $(top_srcdir)/include/ruby.h
|
||
timeout.$(OBJEXT): {$(VPATH)}config.h
|
||
timeout.$(OBJEXT): {$(VPATH)}defines.h
|
||
timeout.$(OBJEXT): {$(VPATH)}id.h
|
||
timeout.$(OBJEXT): {$(VPATH)}intern.h
|
||
timeout.$(OBJEXT): {$(VPATH)}internal.h
|
||
timeout.$(OBJEXT): {$(VPATH)}method.h
|
||
timeout.$(OBJEXT): {$(VPATH)}missing.h
|
||
timeout.$(OBJEXT): {$(VPATH)}node.h
|
||
timeout.$(OBJEXT): {$(VPATH)}ruby_assert.h
|
||
timeout.$(OBJEXT): {$(VPATH)}ruby_atomic.h
|
||
timeout.$(OBJEXT): {$(VPATH)}st.h
|
||
timeout.$(OBJEXT): {$(VPATH)}subst.h
|
||
timeout.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
|
||
timeout.$(OBJEXT): {$(VPATH)}thread_native.h
|
||
timeout.$(OBJEXT): {$(VPATH)}timeout.c
|
||
timeout.$(OBJEXT): {$(VPATH)}vm_core.h
|
||
timeout.$(OBJEXT): {$(VPATH)}vm_opts.h
|
||
transcode.$(OBJEXT): $(hdrdir)/ruby/ruby.h
|
||
transcode.$(OBJEXT): $(top_srcdir)/include/ruby.h
|
||
transcode.$(OBJEXT): {$(VPATH)}config.h
|
inits.c | ||
---|---|---|
CALL(version);
|
||
CALL(vm_trace);
|
||
CALL(ast);
|
||
CALL(timeout);
|
||
}
|
||
#undef CALL
|
internal.h | ||
---|---|---|
/* time.c */
|
||
struct timeval rb_time_timeval(VALUE);
|
||
/* timeout.c */
|
||
typedef struct rb_vm_struct rb_vm_t;
|
||
typedef struct rb_execution_context_struct rb_execution_context_t;
|
||
struct timespec *rb_timeout_sleep_interval(rb_vm_t *, struct timespec *);
|
||
void rb_timeout_expire(const rb_execution_context_t *);
|
||
/* thread.c */
|
||
#define COVERAGE_INDEX_LINES 0
|
||
#define COVERAGE_INDEX_BRANCHES 1
|
||
... | ... | |
void rb_mutex_allow_trap(VALUE self, int val);
|
||
VALUE rb_uninterruptible(VALUE (*b_proc)(ANYARGS), VALUE data);
|
||
VALUE rb_mutex_owned_p(VALUE self);
|
||
void rb_getclockofday(struct timespec *);
|
||
/* thread_pthread.c, thread_win32.c */
|
||
int rb_divert_reserved_fd(int fd);
|
test/test_timeout.rb | ||
---|---|---|
# frozen_string_literal: false
|
||
require 'test/unit'
|
||
require 'timeout'
|
||
begin
|
||
require 'io/wait'
|
||
rescue LoadError
|
||
end
|
||
class TestTimeout < Test::Unit::TestCase
|
||
def test_queue
|
||
... | ... | |
}
|
||
assert(ok, bug11344)
|
||
end
|
||
def test_io
|
||
t = 0.001
|
||
IO.pipe do |r, w|
|
||
assert_raise(Timeout::Error) { Timeout.timeout(t) { r.read } }
|
||
if r.respond_to?(:wait)
|
||
assert_raise(Timeout::Error) { Timeout.timeout(t) { r.wait } }
|
||
assert_raise(Timeout::Error) { Timeout.timeout(t) { r.wait(9) } }
|
||
end
|
||
rset = [r, r.dup]
|
||
assert_raise(Timeout::Error) do
|
||
Timeout.timeout(t) { IO.select(rset, nil, nil, 9) }
|
||
end
|
||
assert_raise(Timeout::Error) { Timeout.timeout(t) { IO.select(rset) } }
|
||
rset.each(&:close)
|
||
end
|
||
end
|
||
def test_thread_join
|
||
th = Thread.new { sleep }
|
||
assert_raise(Timeout::Error) { Timeout.timeout(0.001) { th.join } }
|
||
ensure
|
||
th.kill
|
||
th.join
|
||
end
|
||
def test_mutex_lock
|
||
m = Mutex.new
|
||
m.lock
|
||
th = Thread.new { m.synchronize { :ok} }
|
||
assert_raise(Timeout::Error) { Timeout.timeout(0.001) { th.join } }
|
||
m.unlock
|
||
assert_equal :ok, th.value
|
||
end
|
||
def test_yield_and_return_value
|
||
r = Timeout.timeout(0) do |sec|
|
||
assert_equal 0, sec
|
||
sec
|
||
end
|
||
assert_equal 0, r
|
||
t = 123
|
||
r = Timeout.timeout(t) do |sec|
|
||
assert_same t, sec
|
||
sec
|
||
end
|
||
assert_same r, t
|
||
r = Timeout.timeout(t, RuntimeError) do |sec|
|
||
assert_same t, sec
|
||
sec
|
||
end
|
||
assert_same r, t
|
||
end
|
||
def test_timeout_thread
|
||
in_thread { sleep }
|
||
end
|
||
def test_timeout_loop
|
||
in_thread { loop {} }
|
||
end
|
||
def test_timeout_io_read
|
||
IO.pipe { |r, w| in_thread { r.read } }
|
||
end
|
||
def test_timeout_mutex
|
||
m = Mutex.new
|
||
m.synchronize { in_thread { m.synchronize {} } }
|
||
in_thread { m.synchronize { m.sleep } }
|
||
end
|
||
def in_thread(&blk)
|
||
th = Thread.new do
|
||
begin
|
||
Timeout.timeout(0.001) { blk.call }
|
||
rescue => e
|
||
e
|
||
end
|
||
end
|
||
assert_same th, th.join(0.3)
|
||
assert_kind_of Timeout::Error, th.value
|
||
end
|
||
end
|
thread.c | ||
---|---|---|
}
|
||
static void
|
||
rb_threadptr_interrupt_common(rb_thread_t *th, int trap)
|
||
rb_threadptr_interrupt_set(rb_thread_t *th, rb_atomic_t flag)
|
||
{
|
||
rb_native_mutex_lock(&th->interrupt_lock);
|
||
if (trap) {
|
||
RUBY_VM_SET_TRAP_INTERRUPT(th->ec);
|
||
}
|
||
else {
|
||
RUBY_VM_SET_INTERRUPT(th->ec);
|
||
}
|
||
ATOMIC_OR(th->ec->interrupt_flag, flag);
|
||
if (th->unblock.func != NULL) {
|
||
(th->unblock.func)(th->unblock.arg);
|
||
}
|
||
else {
|
||
/* none */
|
||
(th->unblock.func)(th->unblock.arg);
|
||
}
|
||
rb_native_mutex_unlock(&th->interrupt_lock);
|
||
}
|
||
... | ... | |
void
|
||
rb_threadptr_interrupt(rb_thread_t *th)
|
||
{
|
||
rb_threadptr_interrupt_common(th, 0);
|
||
rb_threadptr_interrupt_set(th, PENDING_INTERRUPT_MASK);
|
||
}
|
||
static void
|
||
threadptr_trap_interrupt(rb_thread_t *th)
|
||
{
|
||
rb_threadptr_interrupt_common(th, 1);
|
||
rb_threadptr_interrupt_set(th, TRAP_INTERRUPT_MASK);
|
||
}
|
||
static void
|
||
... | ... | |
rb_timespec_now(ts);
|
||
}
|
||
void
|
||
rb_getclockofday(struct timespec *ts)
|
||
{
|
||
getclockofday(ts);
|
||
}
|
||
static void
|
||
timespec_add(struct timespec *dst, const struct timespec *ts)
|
||
{
|
||
... | ... | |
int timer_interrupt;
|
||
int pending_interrupt;
|
||
int trap_interrupt;
|
||
int timeout_interrupt;
|
||
timer_interrupt = interrupt & TIMER_INTERRUPT_MASK;
|
||
pending_interrupt = interrupt & PENDING_INTERRUPT_MASK;
|
||
postponed_job_interrupt = interrupt & POSTPONED_JOB_INTERRUPT_MASK;
|
||
trap_interrupt = interrupt & TRAP_INTERRUPT_MASK;
|
||
timeout_interrupt = interrupt & TIMEOUT_INTERRUPT_MASK;
|
||
if (postponed_job_interrupt) {
|
||
rb_postponed_job_flush(th->vm);
|
||
... | ... | |
}
|
||
}
|
||
if (timeout_interrupt) {
|
||
rb_timeout_expire(th->ec);
|
||
}
|
||
if (timer_interrupt) {
|
||
uint32_t limits_us = TIME_QUANTUM_USEC;
|
||
... | ... | |
}
|
||
rb_native_mutex_unlock(&vm->thread_destruct_lock);
|
||
if (vm->timer_thread_timeout >= 0) {
|
||
rb_threadptr_interrupt_set(vm->main_thread, TIMEOUT_INTERRUPT_MASK);
|
||
}
|
||
/* check signal */
|
||
rb_threadptr_check_signal(vm->main_thread);
|
||
vm->timer_thread_timeout = ATOMIC_EXCHANGE(vm->next_timeout, -1);
|
||
#if 0
|
||
/* prove profiler */
|
||
if (vm->prove_profile.enable) {
|
||
... | ... | |
if (vm_living_thread_num(vm) > vm->sleeper) return;
|
||
if (vm_living_thread_num(vm) < vm->sleeper) rb_bug("sleeper must not be more than vm_living_thread_num(vm)");
|
||
if (patrol_thread && patrol_thread != GET_THREAD()) return;
|
||
if (rb_timeout_sleep_interval(vm, 0)) return;
|
||
list_for_each(&vm->living_threads, th, vmlt_node) {
|
||
if (th->status != THREAD_STOPPED_FOREVER || RUBY_VM_INTERRUPTED(th->ec)) {
|
thread_pthread.c | ||
---|---|---|
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex);
|
||
void rb_native_cond_initialize(rb_nativethread_cond_t *cond);
|
||
void rb_native_cond_destroy(rb_nativethread_cond_t *cond);
|
||
static void rb_thread_wakeup_timer_thread_low(void);
|
||
void rb_thread_wakeup_timer_thread_low(void);
|
||
static struct {
|
||
pthread_t id;
|
||
int created;
|
||
... | ... | |
}
|
||
}
|
||
static void
|
||
void
|
||
rb_thread_wakeup_timer_thread_low(void)
|
||
{
|
||
if (timer_thread_pipe.owner_process == getpid()) {
|
||
... | ... | |
* @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;
|
||
... | ... | |
need_polling = !ubf_threads_empty();
|
||
if (gvl->waiting > 0 || need_polling) {
|
||
if (vm->gvl.waiting > 0 || need_polling) {
|
||
/* polling (TIME_QUANTUM_USEC usec) */
|
||
result = poll(pollfds, 1, TIME_QUANTUM_USEC/1000);
|
||
}
|
||
else {
|
||
/* wait (infinite) */
|
||
result = poll(pollfds, numberof(pollfds), -1);
|
||
/* wait (infinite, or whatever timeout.c sets) */
|
||
result = poll(pollfds, numberof(pollfds), vm->timer_thread_timeout);
|
||
}
|
||
if (result == 0) {
|
||
... | ... | |
#else /* USE_SLEEPY_TIMER_THREAD */
|
||
# define PER_NANO 1000000000
|
||
void rb_thread_wakeup_timer_thread(void) {}
|
||
static void rb_thread_wakeup_timer_thread_low(void) {}
|
||
void rb_thread_wakeup_timer_thread_low(void) {}
|
||
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;
|
||
... | ... | |
static void *
|
||
thread_timer(void *p)
|
||
{
|
||
rb_global_vm_lock_t *gvl = (rb_global_vm_lock_t *)p;
|
||
rb_vm_t *vm = p;
|
||
if (TT_DEBUG) WRITE_CONST(2, "start timer thread\n");
|
||
... | ... | |
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]);
|
||
... | ... | |
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) {
|
||
... | ... | |
* 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",
|
thread_win32.c | ||
---|---|---|
/* do nothing */
|
||
}
|
||
void
|
||
rb_thread_wakeup_timer_thread_low(void)
|
||
{
|
||
/* do nothing */
|
||
}
|
||
static void
|
||
rb_thread_create_timer_thread(void)
|
||
{
|
timeout.c | ||
---|---|---|
#include "internal.h"
|
||
#include "vm_core.h"
|
||
/* match ccan/timer/timer.h, which we may support in the future: */
|
||
struct timer {
|
||
struct list_node list;
|
||
uint64_t time; /* usec */
|
||
};
|
||
struct timeout {
|
||
rb_execution_context_t *ec;
|
||
VALUE sec;
|
||
VALUE klass;
|
||
VALUE message;
|
||
struct timer t;
|
||
};
|
||
static VALUE eTimeoutError, mTimeout, eUncaughtThrow;
|
||
static ID id_thread;
|
||
static uint64_t
|
||
timespec2usec(const struct timespec *ts)
|
||
{
|
||
return (uint64_t)ts->tv_sec * 1000000 + (uint64_t)ts->tv_nsec / 1000;
|
||
}
|
||
static void
|
||
timers_ll_add(struct list_head *timers, struct timer *t,
|
||
uint64_t rel_usec, uint64_t now_usec)
|
||
{
|
||
struct timer *i = 0;
|
||
t->time = rel_usec + now_usec;
|
||
/*
|
||
* search backwards: assume typical projects have multiple objects
|
||
* sharing the same timeout values, so new timers will expire later
|
||
* than existing timers
|
||
*/
|
||
list_for_each_rev(timers, i, list) {
|
||
if (t->time >= i->time) {
|
||
list_add_after(timers, &i->list, &t->list);
|
||
return;
|
||
}
|
||
}
|
||
list_add(timers, &t->list);
|
||
}
|
||
static struct timer *
|
||
timers_ll_expire(struct list_head *timers, uint64_t now_usec)
|
||
{
|
||
struct timer *t = list_top(timers, struct timer, list);
|
||
if (t && now_usec >= t->time) {
|
||
list_del_init(&t->list);
|
||
return t;
|
||
}
|
||
return 0;
|
||
}
|
||
static struct timer *
|
||
timers_ll_earliest(const struct list_head *timers)
|
||
{
|
||
return list_top(timers, struct timer, list);
|
||
}
|
||
static VALUE
|
||
timeout_yield(VALUE tag, VALUE sec)
|
||
{
|
||
return rb_yield(sec);
|
||
}
|
||
static VALUE
|
||
timeout_run(VALUE x)
|
||
{
|
||
struct timeout *a = (struct timeout *)x;
|
||
if (RTEST(a->klass)) {
|
||
return rb_yield(a->sec);
|
||
}
|
||
/* for Timeout::Error#exception to throw */
|
||
a->message = rb_exc_new_str(eTimeoutError, a->message);
|
||
/* hide for rb_gc_force_recycle */
|
||
RBASIC_CLEAR_CLASS(a->message);
|
||
x = rb_catch_obj(a->message, timeout_yield, a->sec);
|
||
if (x == a->message) {
|
||
rb_attr_delete(x, id_thread);
|
||
rb_exc_raise(x);
|
||
}
|
||
/* common case, no timeout, so exc is still hidden and safe to recycle */
|
||
VM_ASSERT(!RBASIC_CLASS(a->message) && RB_TYPE_P(a->message, T_OBJECT));
|
||
if (FL_TEST(a->message, FL_EXIVAR)) {
|
||
rb_free_generic_ivar(a->message);
|
||
FL_UNSET(a->message, FL_EXIVAR);
|
||
}
|
||
rb_gc_force_recycle(a->message);
|
||
return x;
|
||
}
|
||
static VALUE
|
||
timeout_ensure(VALUE x)
|
||
{
|
||
struct timeout *a = (struct timeout *)x;
|
||
list_del_init(&a->t.list); /* inlined timer_del */
|
||
return Qfalse;
|
||
}
|
||
static struct timeout *
|
||
rb_timers_expire_one(rb_vm_t *vm, uint64_t now_usec)
|
||
{
|
||
struct timer *t = timers_ll_expire(&vm->timers, now_usec);
|
||
return t ? container_of(t, struct timeout, t) : 0;
|
||
}
|
||
static void
|
||
arm_timer(rb_vm_t *vm, uint64_t rel_usec)
|
||
{
|
||
int msec = rel_usec / 1000;
|
||
ATOMIC_EXCHANGE(vm->next_timeout, (rb_atomic_t)msec);
|
||
/* _low makes a difference in benchmark/bm_timeout_mt_nested.rb */
|
||
rb_thread_wakeup_timer_thread_low();
|
||
}
|
||
struct expire_args {
|
||
uint64_t now_usec;
|
||
rb_thread_t *current_th;
|
||
enum rb_thread_status prev_status;
|
||
};
|
||
static VALUE
|
||
do_expire(VALUE x)
|
||
{
|
||
struct expire_args *ea = (struct expire_args *)x;
|
||
rb_vm_t *vm = ea->current_th->vm;
|
||
struct timeout *a;
|
||
while ((a = rb_timers_expire_one(vm, ea->now_usec))) {
|
||
rb_thread_t *target_th = rb_ec_thread_ptr(a->ec);
|
||
VALUE exc;
|
||
if (RTEST(a->klass)) {
|
||
exc = rb_exc_new_str(a->klass, a->message);
|
||
}
|
||
else { /* default, pre-made Timeout::Error */
|
||
exc = a->message;
|
||
RBASIC_SET_CLASS_RAW(exc, eTimeoutError); /* reveal */
|
||
/* for Timeout::Error#exception to call `throw' */
|
||
rb_ivar_set(exc, id_thread, target_th->self);
|
||
}
|
||
if (ea->current_th == target_th) {
|
||
rb_threadptr_pending_interrupt_enque(target_th, exc);
|
||
rb_threadptr_interrupt(target_th);
|
||
}
|
||
else {
|
||
rb_funcall(target_th->self, rb_intern("raise"), 1, exc);
|
||
}
|
||
}
|
||
return Qfalse;
|
||
}
|
||
static VALUE
|
||
expire_ensure(VALUE p)
|
||
{
|
||
struct expire_args *ea = (struct expire_args *)p;
|
||
rb_vm_t *vm = ea->current_th->vm;
|
||
struct timer *t = timers_ll_earliest(&vm->timers);
|
||
if (t) {
|
||
arm_timer(vm, t->time > ea->now_usec ? t->time - ea->now_usec : 0);
|
||
}
|
||
ea->current_th->status = ea->prev_status;
|
||
return Qfalse;
|
||
}
|
||
void
|
||
rb_timeout_expire(const rb_execution_context_t *ec)
|
||
{
|
||
struct expire_args ea;
|
||
struct timespec ts;
|
||
rb_getclockofday(&ts);
|
||
ea.now_usec = timespec2usec(&ts);
|
||
ea.current_th = rb_ec_thread_ptr(ec);
|
||
ea.prev_status = ea.current_th->status;
|
||
ea.current_th->status = THREAD_RUNNABLE;
|
||
rb_ensure(do_expire, (VALUE)&ea, expire_ensure, (VALUE)&ea);
|
||
}
|
||
struct timespec *
|
||
rb_timeout_sleep_interval(rb_vm_t *vm, struct timespec *ts)
|
||
{
|
||
struct timer *t = timers_ll_earliest(&vm->timers);
|
||
if (t && !ts) {
|
||
return (struct timespec *)-1;
|
||
}
|
||
if (t) {
|
||
uint64_t now_usec;
|
||
rb_getclockofday(ts);
|
||
now_usec = timespec2usec(ts);
|
||
if (t->time >= now_usec) {
|
||
uint64_t rel_usec = t->time - now_usec;
|
||
ts->tv_sec = rel_usec / 1000000;
|
||
ts->tv_nsec = rel_usec % 1000000 * 1000;
|
||
}
|
||
else {
|
||
ts->tv_sec = 0;
|
||
ts->tv_nsec = 0;
|
||
}
|
||
return ts;
|
||
}
|
||
return 0;
|
||
}
|
||
static void
|
||
timeout_add(struct timeout *a)
|
||
{
|
||
rb_vm_t *vm = rb_ec_vm_ptr(a->ec);
|
||
struct timer *cur = timers_ll_earliest(&vm->timers);
|
||
uint64_t now_usec, rel_usec;
|
||
struct timeval tv = rb_time_interval(a->sec);
|
||
struct timespec ts;
|
||
ts.tv_sec = tv.tv_sec;
|
||
ts.tv_nsec = tv.tv_usec * 1000;
|
||
rel_usec = timespec2usec(&ts);
|
||
rb_getclockofday(&ts);
|
||
now_usec = timespec2usec(&ts);
|
||
timers_ll_add(&vm->timers, &a->t, rel_usec, now_usec);
|
||
if (!cur || timers_ll_earliest(&vm->timers) == &a->t) {
|
||
arm_timer(vm, rel_usec);
|
||
}
|
||
}
|
||
static VALUE
|
||
s_timeout(int argc, VALUE *argv, VALUE mod)
|
||
{
|
||
struct timeout a;
|
||
rb_scan_args(argc, argv, "12", &a.sec, &a.klass, &a.message);
|
||
if (NIL_P(a.sec) || rb_equal(a.sec, INT2FIX(0))) {
|
||
return rb_yield(a.sec);
|
||
}
|
||
if (!RTEST(a.message)) {
|
||
a.message = rb_fstring_cstr("execution expired");
|
||
}
|
||
a.ec = GET_EC();
|
||
timeout_add(&a);
|
||
return rb_ensure(timeout_run, (VALUE)&a, timeout_ensure, (VALUE)&a);
|
||
}
|
||
static VALUE
|
||
begin_throw(VALUE self)
|
||
{
|
||
rb_throw_obj(self, self);
|
||
return self;
|
||
}
|
||
static VALUE
|
||
rescue_throw(VALUE ignore, VALUE err)
|
||
{
|
||
return Qnil;
|
||
}
|
||
/*
|
||
* We don't want to generate a backtrace like the version
|
||
* in timeout.rb does. We also want to raise the same
|
||
* exception object so s_timeout (in core) can match
|
||
* against it without relying on an extra proc for:
|
||
*
|
||
* proc { |exception| return yield(sec) }
|
||
*/
|
||
static VALUE
|
||
timeout_error_exception(int argc, VALUE *argv, VALUE self)
|
||
{
|
||
if (rb_attr_get(self, id_thread) == rb_thread_current()) {
|
||
rb_rescue2(begin_throw, self, rescue_throw, Qfalse, eUncaughtThrow, 0);
|
||
}
|
||
return self;
|
||
}
|
||
static VALUE
|
||
timeout_compat(int argc, VALUE *argv, VALUE mod)
|
||
{
|
||
VALUE w[2];
|
||
w[0] = rb_funcall(mod, rb_intern("__method__"), 0);
|
||
w[0] = rb_sprintf("Object#%"PRIsVALUE
|
||
" is deprecated, use Timeout.timeout instead.", w[0]);
|
||
w[1] = rb_hash_new();
|
||
rb_hash_aset(w[1], ID2SYM(rb_intern("uplevel")), INT2FIX(1));
|
||
rb_funcallv(mod, rb_intern("warn"), 2, w);
|
||
return s_timeout(argc, argv, mTimeout);
|
||
}
|
||
void
|
||
Init_timeout(void)
|
||
{
|
||
#undef rb_intern
|
||
mTimeout = rb_define_module("Timeout");
|
||
eTimeoutError = rb_define_class_under(mTimeout, "Error", rb_eRuntimeError);
|
||
eUncaughtThrow = rb_const_get(rb_cObject, rb_intern("UncaughtThrowError"));
|
||
rb_define_method(mTimeout, "timeout", s_timeout, -1);
|
||
rb_define_singleton_method(mTimeout, "timeout", s_timeout, -1);
|
||
rb_define_method(eTimeoutError, "exception", timeout_error_exception, -1);
|
||
id_thread = rb_intern("@thread");
|
||
/* backwards compatibility */
|
||
rb_define_method(rb_mKernel, "timeout", timeout_compat, -1);
|
||
rb_const_set(rb_cObject, rb_intern("TimeoutError"), eTimeoutError);
|
||
rb_deprecate_constant(rb_cObject, "TimeoutError");
|
||
rb_provide("timeout.rb");
|
||
}
|
vm.c | ||
---|---|---|
{
|
||
MEMZERO(vm, rb_vm_t, 1);
|
||
rb_vm_living_threads_init(vm);
|
||
list_head_init(&vm->timers);
|
||
vm->thread_report_on_exception = 1;
|
||
vm->src_encoding_index = -1;
|
||
vm->next_timeout = (rb_atomic_t)-1;
|
||
vm->timer_thread_timeout = -1;
|
||
vm_default_params_setup(vm);
|
||
}
|
vm_core.h | ||
---|---|---|
rb_global_vm_lock_t gvl;
|
||
rb_nativethread_lock_t thread_destruct_lock;
|
||
struct list_head timers; /* TODO: consider moving to rb_thread_t */
|
||
rb_atomic_t next_timeout;
|
||
int timer_thread_timeout;
|
||
struct rb_thread_struct *main_thread;
|
||
struct rb_thread_struct *running_thread;
|
||
... | ... | |
void rb_thread_stop_timer_thread(void);
|
||
void rb_thread_reset_timer_thread(void);
|
||
void rb_thread_wakeup_timer_thread(void);
|
||
void rb_thread_wakeup_timer_thread_low(void);
|
||
static inline void
|
||
rb_vm_living_threads_init(rb_vm_t *vm)
|
||
... | ... | |
TIMER_INTERRUPT_MASK = 0x01,
|
||
PENDING_INTERRUPT_MASK = 0x02,
|
||
POSTPONED_JOB_INTERRUPT_MASK = 0x04,
|
||
TRAP_INTERRUPT_MASK = 0x08
|
||
TRAP_INTERRUPT_MASK = 0x08,
|
||
TIMEOUT_INTERRUPT_MASK = 0x10
|
||
};
|
||
#define RUBY_VM_SET_TIMER_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, TIMER_INTERRUPT_MASK)
|
||
#define RUBY_VM_SET_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, PENDING_INTERRUPT_MASK)
|
||
#define RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, POSTPONED_JOB_INTERRUPT_MASK)
|
||
#define RUBY_VM_SET_TRAP_INTERRUPT(ec) ATOMIC_OR((ec)->interrupt_flag, TRAP_INTERRUPT_MASK)
|
||
#define RUBY_VM_INTERRUPTED(ec) ((ec)->interrupt_flag & ~(ec)->interrupt_mask & \
|
||
(PENDING_INTERRUPT_MASK|TRAP_INTERRUPT_MASK))
|
||
#define RUBY_VM_INTERRUPTED(ec) ((ec)->interrupt_flag & \
|
||
~(ec)->interrupt_mask & \
|
||
(PENDING_INTERRUPT_MASK|\
|
||
TRAP_INTERRUPT_MASK|\
|
||
TIMEOUT_INTERRUPT_MASK))
|
||
#define RUBY_VM_INTERRUPTED_ANY(ec) ((ec)->interrupt_flag & ~(ec)->interrupt_mask)
|
||
VALUE rb_exc_set_backtrace(VALUE exc, VALUE bt);
|
||
-
|