mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
945ea61cd7
stack overflows in the signal handler, if sigaltstack is available. On stack overflow (and with sigaltstack), the signal handler is more likely to have room to create an exception object. [ruby-core:23813] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@23720 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
4058 lines
93 KiB
C
4058 lines
93 KiB
C
/**********************************************************************
|
|
|
|
thread.c -
|
|
|
|
$Author$
|
|
|
|
Copyright (C) 2004-2007 Koichi Sasada
|
|
|
|
**********************************************************************/
|
|
|
|
/*
|
|
YARV Thread Desgin
|
|
|
|
model 1: Userlevel Thread
|
|
Same as traditional ruby thread.
|
|
|
|
model 2: Native Thread with Global VM lock
|
|
Using pthread (or Windows thread) and Ruby threads run concurrent.
|
|
|
|
model 3: Native Thread with fine grain lock
|
|
Using pthread and Ruby threads run concurrent or parallel.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
model 2:
|
|
A thread has mutex (GVL: Global VM Lock or Giant VM Lock) can run.
|
|
When thread scheduling, running thread release GVL. If running thread
|
|
try blocking operation, this thread must release GVL and another
|
|
thread can continue this flow. After blocking operation, thread
|
|
must check interrupt (RUBY_VM_CHECK_INTS).
|
|
|
|
Every VM can run parallel.
|
|
|
|
Ruby threads are scheduled by OS thread scheduler.
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
model 3:
|
|
Every threads run concurrent or parallel and to access shared object
|
|
exclusive access control is needed. For example, to access String
|
|
object or Array object, fine grain lock must be locked every time.
|
|
*/
|
|
|
|
|
|
/* for model 2 */
|
|
|
|
#include "eval_intern.h"
|
|
#include "gc.h"
|
|
|
|
#ifndef USE_NATIVE_THREAD_PRIORITY
|
|
#define USE_NATIVE_THREAD_PRIORITY 0
|
|
#define RUBY_THREAD_PRIORITY_MAX 3
|
|
#define RUBY_THREAD_PRIORITY_MIN -3
|
|
#endif
|
|
|
|
#ifndef THREAD_DEBUG
|
|
#define THREAD_DEBUG 0
|
|
#endif
|
|
|
|
VALUE rb_cMutex;
|
|
VALUE rb_cBarrier;
|
|
|
|
static void sleep_timeval(rb_thread_t *th, struct timeval time);
|
|
static void sleep_wait_for_interrupt(rb_thread_t *th, double sleepsec);
|
|
static void sleep_forever(rb_thread_t *th, int nodeadlock);
|
|
static double timeofday(void);
|
|
struct timeval rb_time_interval(VALUE);
|
|
static int rb_threadptr_dead(rb_thread_t *th);
|
|
|
|
static void rb_check_deadlock(rb_vm_t *vm);
|
|
|
|
int rb_signal_buff_size(void);
|
|
void rb_signal_exec(rb_thread_t *th, int sig);
|
|
void rb_disable_interrupt(void);
|
|
void rb_thread_stop_timer_thread(void);
|
|
|
|
static const VALUE eKillSignal = INT2FIX(0);
|
|
static const VALUE eTerminateSignal = INT2FIX(1);
|
|
static volatile int system_working = 1;
|
|
|
|
inline static void
|
|
st_delete_wrap(st_table *table, st_data_t key)
|
|
{
|
|
st_delete(table, &key, 0);
|
|
}
|
|
|
|
/********************************************************************************/
|
|
|
|
#define THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION
|
|
|
|
struct rb_blocking_region_buffer {
|
|
enum rb_thread_status prev_status;
|
|
struct rb_unblock_callback oldubf;
|
|
};
|
|
|
|
static void set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
|
struct rb_unblock_callback *old);
|
|
static void reset_unblock_function(rb_thread_t *th, const struct rb_unblock_callback *old);
|
|
|
|
static inline void blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
|
|
rb_unblock_function_t *func, void *arg);
|
|
static inline void blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region);
|
|
|
|
#define GVL_UNLOCK_BEGIN() do { \
|
|
rb_thread_t *_th_stored = GET_THREAD(); \
|
|
rb_gc_save_machine_context(_th_stored); \
|
|
native_mutex_unlock(&_th_stored->vm->global_vm_lock)
|
|
|
|
#define GVL_UNLOCK_END() \
|
|
native_mutex_lock(&_th_stored->vm->global_vm_lock); \
|
|
rb_thread_set_current(_th_stored); \
|
|
} while(0)
|
|
|
|
#define BLOCKING_REGION_CORE(exec) do { \
|
|
GVL_UNLOCK_BEGIN(); {\
|
|
exec; \
|
|
} \
|
|
GVL_UNLOCK_END(); \
|
|
} while(0);
|
|
|
|
#define BLOCKING_REGION(exec, ubf, ubfarg) do { \
|
|
rb_thread_t *__th = GET_THREAD(); \
|
|
struct rb_blocking_region_buffer __region; \
|
|
blocking_region_begin(__th, &__region, ubf, ubfarg); \
|
|
exec; \
|
|
blocking_region_end(__th, &__region); \
|
|
RUBY_VM_CHECK_INTS(); \
|
|
} while(0)
|
|
|
|
#if THREAD_DEBUG
|
|
#ifdef HAVE_VA_ARGS_MACRO
|
|
void rb_thread_debug(const char *file, int line, const char *fmt, ...);
|
|
#define thread_debug(fmt, ...) rb_thread_debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
|
#define POSITION_FORMAT "%s:%d:"
|
|
#define POSITION_ARGS ,file, line
|
|
#else
|
|
void rb_thread_debug(const char *fmt, ...);
|
|
#define thread_debug rb_thread_debug
|
|
#define POSITION_FORMAT
|
|
#define POSITION_ARGS
|
|
#endif
|
|
|
|
# if THREAD_DEBUG < 0
|
|
static int rb_thread_debug_enabled;
|
|
|
|
static VALUE
|
|
rb_thread_s_debug(void)
|
|
{
|
|
return INT2NUM(rb_thread_debug_enabled);
|
|
}
|
|
|
|
static VALUE
|
|
rb_thread_s_debug_set(VALUE self, VALUE val)
|
|
{
|
|
rb_thread_debug_enabled = RTEST(val);
|
|
return val;
|
|
}
|
|
# else
|
|
# define rb_thread_debug_enabled THREAD_DEBUG
|
|
# endif
|
|
#else
|
|
#define thread_debug if(0)printf
|
|
#endif
|
|
|
|
#ifndef __ia64
|
|
#define thread_start_func_2(th, st, rst) thread_start_func_2(th, st)
|
|
#endif
|
|
NOINLINE(static int thread_start_func_2(rb_thread_t *th, VALUE *stack_start,
|
|
VALUE *register_stack_start));
|
|
static void timer_thread_function(void *);
|
|
|
|
#if defined(_WIN32)
|
|
#include "thread_win32.c"
|
|
|
|
#define DEBUG_OUT() \
|
|
WaitForSingleObject(&debug_mutex, INFINITE); \
|
|
printf(POSITION_FORMAT"%p - %s" POSITION_ARGS, GetCurrentThreadId(), buf); \
|
|
fflush(stdout); \
|
|
ReleaseMutex(&debug_mutex);
|
|
|
|
#elif defined(HAVE_PTHREAD_H)
|
|
#include "thread_pthread.c"
|
|
|
|
#define DEBUG_OUT() \
|
|
pthread_mutex_lock(&debug_mutex); \
|
|
printf(POSITION_FORMAT"%#"PRIxVALUE" - %s" POSITION_ARGS, (VALUE)pthread_self(), buf); \
|
|
fflush(stdout); \
|
|
pthread_mutex_unlock(&debug_mutex);
|
|
|
|
#else
|
|
#error "unsupported thread type"
|
|
#endif
|
|
|
|
#if THREAD_DEBUG
|
|
static int debug_mutex_initialized = 1;
|
|
static rb_thread_lock_t debug_mutex;
|
|
|
|
void
|
|
rb_thread_debug(
|
|
#ifdef HAVE_VA_ARGS_MACRO
|
|
const char *file, int line,
|
|
#endif
|
|
const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
char buf[BUFSIZ];
|
|
|
|
if (!rb_thread_debug_enabled) return;
|
|
|
|
if (debug_mutex_initialized == 1) {
|
|
debug_mutex_initialized = 0;
|
|
native_mutex_initialize(&debug_mutex);
|
|
}
|
|
|
|
va_start(args, fmt);
|
|
vsnprintf(buf, BUFSIZ, fmt, args);
|
|
va_end(args);
|
|
|
|
DEBUG_OUT();
|
|
}
|
|
#endif
|
|
|
|
|
|
static void
|
|
set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
|
|
struct rb_unblock_callback *old)
|
|
{
|
|
check_ints:
|
|
RUBY_VM_CHECK_INTS(); /* check signal or so */
|
|
native_mutex_lock(&th->interrupt_lock);
|
|
if (th->interrupt_flag) {
|
|
native_mutex_unlock(&th->interrupt_lock);
|
|
goto check_ints;
|
|
}
|
|
else {
|
|
if (old) *old = th->unblock;
|
|
th->unblock.func = func;
|
|
th->unblock.arg = arg;
|
|
}
|
|
native_mutex_unlock(&th->interrupt_lock);
|
|
}
|
|
|
|
static void
|
|
reset_unblock_function(rb_thread_t *th, const struct rb_unblock_callback *old)
|
|
{
|
|
native_mutex_lock(&th->interrupt_lock);
|
|
th->unblock = *old;
|
|
native_mutex_unlock(&th->interrupt_lock);
|
|
}
|
|
|
|
static void
|
|
rb_threadptr_interrupt(rb_thread_t *th)
|
|
{
|
|
native_mutex_lock(&th->interrupt_lock);
|
|
RUBY_VM_SET_INTERRUPT(th);
|
|
if (th->unblock.func) {
|
|
(th->unblock.func)(th->unblock.arg);
|
|
}
|
|
else {
|
|
/* none */
|
|
}
|
|
native_mutex_unlock(&th->interrupt_lock);
|
|
}
|
|
|
|
|
|
static int
|
|
terminate_i(st_data_t key, st_data_t val, rb_thread_t *main_thread)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
if (th != main_thread) {
|
|
thread_debug("terminate_i: %p\n", (void *)th);
|
|
rb_threadptr_interrupt(th);
|
|
th->thrown_errinfo = eTerminateSignal;
|
|
th->status = THREAD_TO_KILL;
|
|
}
|
|
else {
|
|
thread_debug("terminate_i: main thread (%p)\n", (void *)th);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
typedef struct rb_mutex_struct
|
|
{
|
|
rb_thread_lock_t lock;
|
|
rb_thread_cond_t cond;
|
|
struct rb_thread_struct volatile *th;
|
|
volatile int cond_waiting, cond_notified;
|
|
struct rb_mutex_struct *next_mutex;
|
|
} mutex_t;
|
|
|
|
static void rb_mutex_unlock_all(mutex_t *mutex, rb_thread_t *th);
|
|
|
|
void
|
|
rb_thread_terminate_all(void)
|
|
{
|
|
rb_thread_t *th = GET_THREAD(); /* main thread */
|
|
rb_vm_t *vm = th->vm;
|
|
if (vm->main_thread != th) {
|
|
rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)",
|
|
(void *)vm->main_thread, (void *)th);
|
|
}
|
|
|
|
/* unlock all locking mutexes */
|
|
if (th->keeping_mutexes) {
|
|
rb_mutex_unlock_all(th->keeping_mutexes, GET_THREAD());
|
|
}
|
|
|
|
thread_debug("rb_thread_terminate_all (main thread: %p)\n", (void *)th);
|
|
st_foreach(vm->living_threads, terminate_i, (st_data_t)th);
|
|
|
|
while (!rb_thread_alone()) {
|
|
PUSH_TAG();
|
|
if (EXEC_TAG() == 0) {
|
|
rb_thread_schedule();
|
|
}
|
|
else {
|
|
/* ignore exception */
|
|
}
|
|
POP_TAG();
|
|
}
|
|
rb_thread_stop_timer_thread();
|
|
}
|
|
|
|
static void
|
|
thread_cleanup_func_before_exec(void *th_ptr)
|
|
{
|
|
rb_thread_t *th = th_ptr;
|
|
th->status = THREAD_KILLED;
|
|
th->machine_stack_start = th->machine_stack_end = 0;
|
|
#ifdef __ia64
|
|
th->machine_register_stack_start = th->machine_register_stack_end = 0;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
thread_cleanup_func(void *th_ptr)
|
|
{
|
|
rb_thread_t *th = th_ptr;
|
|
|
|
/* unlock all locking mutexes */
|
|
if (th->keeping_mutexes) {
|
|
rb_mutex_unlock_all(th->keeping_mutexes, th);
|
|
th->keeping_mutexes = NULL;
|
|
}
|
|
thread_cleanup_func_before_exec(th_ptr);
|
|
native_thread_destroy(th);
|
|
}
|
|
|
|
extern void ruby_error_print(void);
|
|
static VALUE rb_threadptr_raise(rb_thread_t *, int, VALUE *);
|
|
void rb_thread_recycle_stack_release(VALUE *);
|
|
|
|
void
|
|
ruby_thread_init_stack(rb_thread_t *th)
|
|
{
|
|
native_thread_init_stack(th);
|
|
}
|
|
|
|
static int
|
|
thread_start_func_2(rb_thread_t *th, VALUE *stack_start, VALUE *register_stack_start)
|
|
{
|
|
int state;
|
|
VALUE args = th->first_args;
|
|
rb_proc_t *proc;
|
|
rb_thread_t *join_th;
|
|
rb_thread_t *main_th;
|
|
VALUE errinfo = Qnil;
|
|
|
|
ruby_thread_set_native(th);
|
|
|
|
th->machine_stack_start = stack_start;
|
|
#ifdef __ia64
|
|
th->machine_register_stack_start = register_stack_start;
|
|
#endif
|
|
thread_debug("thread start: %p\n", (void *)th);
|
|
|
|
native_mutex_lock(&th->vm->global_vm_lock);
|
|
{
|
|
thread_debug("thread start (get lock): %p\n", (void *)th);
|
|
rb_thread_set_current(th);
|
|
|
|
TH_PUSH_TAG(th);
|
|
if ((state = EXEC_TAG()) == 0) {
|
|
SAVE_ROOT_JMPBUF(th, {
|
|
if (th->first_proc) {
|
|
GetProcPtr(th->first_proc, proc);
|
|
th->errinfo = Qnil;
|
|
th->local_lfp = proc->block.lfp;
|
|
th->local_svar = Qnil;
|
|
th->value = rb_vm_invoke_proc(th, proc, proc->block.self,
|
|
(int)RARRAY_LEN(args), RARRAY_PTR(args), 0);
|
|
}
|
|
else {
|
|
th->value = (*th->first_func)((void *)th->first_args);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
errinfo = th->errinfo;
|
|
if (NIL_P(errinfo)) errinfo = rb_errinfo();
|
|
if (state == TAG_FATAL) {
|
|
/* fatal error within this thread, need to stop whole script */
|
|
}
|
|
else if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) {
|
|
if (th->safe_level >= 4) {
|
|
th->errinfo = rb_exc_new3(rb_eSecurityError,
|
|
rb_sprintf("Insecure exit at level %d", th->safe_level));
|
|
errinfo = Qnil;
|
|
}
|
|
}
|
|
else if (th->safe_level < 4 &&
|
|
(th->vm->thread_abort_on_exception ||
|
|
th->abort_on_exception || RTEST(ruby_debug))) {
|
|
/* exit on main_thread */
|
|
}
|
|
else {
|
|
errinfo = Qnil;
|
|
}
|
|
th->value = Qnil;
|
|
}
|
|
|
|
th->status = THREAD_KILLED;
|
|
thread_debug("thread end: %p\n", (void *)th);
|
|
|
|
main_th = th->vm->main_thread;
|
|
if (th != main_th) {
|
|
if (TYPE(errinfo) == T_OBJECT) {
|
|
/* treat with normal error object */
|
|
rb_threadptr_raise(main_th, 1, &errinfo);
|
|
}
|
|
}
|
|
TH_POP_TAG();
|
|
|
|
/* locking_mutex must be Qfalse */
|
|
if (th->locking_mutex != Qfalse) {
|
|
rb_bug("thread_start_func_2: locking_mutex must not be set (%p:%"PRIxVALUE")",
|
|
(void *)th, th->locking_mutex);
|
|
}
|
|
|
|
/* delete self other than main thread from living_threads */
|
|
if (th != main_th) {
|
|
st_delete_wrap(th->vm->living_threads, th->self);
|
|
}
|
|
|
|
/* wake up joinning threads */
|
|
join_th = th->join_list_head;
|
|
while (join_th) {
|
|
if (join_th == main_th) errinfo = Qnil;
|
|
rb_threadptr_interrupt(join_th);
|
|
switch (join_th->status) {
|
|
case THREAD_STOPPED: case THREAD_STOPPED_FOREVER:
|
|
join_th->status = THREAD_RUNNABLE;
|
|
default: break;
|
|
}
|
|
join_th = join_th->join_list_next;
|
|
}
|
|
|
|
if (!th->root_fiber) {
|
|
rb_thread_recycle_stack_release(th->stack);
|
|
th->stack = 0;
|
|
}
|
|
}
|
|
thread_cleanup_func(th);
|
|
if (th != main_th) rb_check_deadlock(th->vm);
|
|
if (th->vm->main_thread == th) {
|
|
ruby_cleanup(state);
|
|
}
|
|
native_mutex_unlock(&th->vm->global_vm_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static VALUE
|
|
thread_create_core(VALUE thval, VALUE args, VALUE (*fn)(ANYARGS))
|
|
{
|
|
rb_thread_t *th;
|
|
|
|
if (OBJ_FROZEN(GET_THREAD()->thgroup)) {
|
|
rb_raise(rb_eThreadError,
|
|
"can't start a new thread (frozen ThreadGroup)");
|
|
}
|
|
GetThreadPtr(thval, th);
|
|
|
|
/* setup thread environment */
|
|
th->first_func = fn;
|
|
th->first_proc = fn ? Qfalse : rb_block_proc();
|
|
th->first_args = args; /* GC: shouldn't put before above line */
|
|
|
|
th->priority = GET_THREAD()->priority;
|
|
th->thgroup = GET_THREAD()->thgroup;
|
|
|
|
native_mutex_initialize(&th->interrupt_lock);
|
|
/* kick thread */
|
|
st_insert(th->vm->living_threads, thval, (st_data_t) th->thread_id);
|
|
native_thread_create(th);
|
|
return thval;
|
|
}
|
|
|
|
static VALUE
|
|
thread_s_new(int argc, VALUE *argv, VALUE klass)
|
|
{
|
|
rb_thread_t *th;
|
|
VALUE thread = rb_thread_alloc(klass);
|
|
rb_obj_call_init(thread, argc, argv);
|
|
GetThreadPtr(thread, th);
|
|
if (!th->first_args) {
|
|
rb_raise(rb_eThreadError, "uninitialized thread - check `%s#initialize'",
|
|
rb_class2name(klass));
|
|
}
|
|
return thread;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.start([args]*) {|args| block } => thread
|
|
* Thread.fork([args]*) {|args| block } => thread
|
|
*
|
|
* Basically the same as <code>Thread::new</code>. However, if class
|
|
* <code>Thread</code> is subclassed, then calling <code>start</code> in that
|
|
* subclass will not invoke the subclass's <code>initialize</code> method.
|
|
*/
|
|
|
|
static VALUE
|
|
thread_start(VALUE klass, VALUE args)
|
|
{
|
|
return thread_create_core(rb_thread_alloc(klass), args, 0);
|
|
}
|
|
|
|
static VALUE
|
|
thread_initialize(VALUE thread, VALUE args)
|
|
{
|
|
rb_thread_t *th;
|
|
if (!rb_block_given_p()) {
|
|
rb_raise(rb_eThreadError, "must be called with a block");
|
|
}
|
|
GetThreadPtr(thread, th);
|
|
if (th->first_args) {
|
|
VALUE rb_proc_location(VALUE self);
|
|
VALUE proc = th->first_proc, line, loc;
|
|
const char *file;
|
|
if (!proc || !RTEST(loc = rb_proc_location(proc))) {
|
|
rb_raise(rb_eThreadError, "already initialized thread");
|
|
}
|
|
file = RSTRING_PTR(RARRAY_PTR(loc)[0]);
|
|
if (NIL_P(line = RARRAY_PTR(loc)[1])) {
|
|
rb_raise(rb_eThreadError, "already initialized thread - %s",
|
|
file);
|
|
}
|
|
rb_raise(rb_eThreadError, "already initialized thread - %s:%d",
|
|
file, NUM2INT(line));
|
|
}
|
|
return thread_create_core(thread, args, 0);
|
|
}
|
|
|
|
VALUE
|
|
rb_thread_create(VALUE (*fn)(ANYARGS), void *arg)
|
|
{
|
|
return thread_create_core(rb_thread_alloc(rb_cThread), (VALUE)arg, fn);
|
|
}
|
|
|
|
|
|
/* +infty, for this purpose */
|
|
#define DELAY_INFTY 1E30
|
|
|
|
struct join_arg {
|
|
rb_thread_t *target, *waiting;
|
|
double limit;
|
|
int forever;
|
|
};
|
|
|
|
static VALUE
|
|
remove_from_join_list(VALUE arg)
|
|
{
|
|
struct join_arg *p = (struct join_arg *)arg;
|
|
rb_thread_t *target_th = p->target, *th = p->waiting;
|
|
|
|
if (target_th->status != THREAD_KILLED) {
|
|
rb_thread_t **pth = &target_th->join_list_head;
|
|
|
|
while (*pth) {
|
|
if (*pth == th) {
|
|
*pth = th->join_list_next;
|
|
break;
|
|
}
|
|
pth = &(*pth)->join_list_next;
|
|
}
|
|
}
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
thread_join_sleep(VALUE arg)
|
|
{
|
|
struct join_arg *p = (struct join_arg *)arg;
|
|
rb_thread_t *target_th = p->target, *th = p->waiting;
|
|
double now, limit = p->limit;
|
|
|
|
while (target_th->status != THREAD_KILLED) {
|
|
if (p->forever) {
|
|
sleep_forever(th, 1);
|
|
}
|
|
else {
|
|
now = timeofday();
|
|
if (now > limit) {
|
|
thread_debug("thread_join: timeout (thid: %p)\n",
|
|
(void *)target_th->thread_id);
|
|
return Qfalse;
|
|
}
|
|
sleep_wait_for_interrupt(th, limit - now);
|
|
}
|
|
thread_debug("thread_join: interrupted (thid: %p)\n",
|
|
(void *)target_th->thread_id);
|
|
}
|
|
return Qtrue;
|
|
}
|
|
|
|
static VALUE
|
|
thread_join(rb_thread_t *target_th, double delay)
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
struct join_arg arg;
|
|
|
|
arg.target = target_th;
|
|
arg.waiting = th;
|
|
arg.limit = timeofday() + delay;
|
|
arg.forever = delay == DELAY_INFTY;
|
|
|
|
thread_debug("thread_join (thid: %p)\n", (void *)target_th->thread_id);
|
|
|
|
if (target_th->status != THREAD_KILLED) {
|
|
th->join_list_next = target_th->join_list_head;
|
|
target_th->join_list_head = th;
|
|
if (!rb_ensure(thread_join_sleep, (VALUE)&arg,
|
|
remove_from_join_list, (VALUE)&arg)) {
|
|
return Qnil;
|
|
}
|
|
}
|
|
|
|
thread_debug("thread_join: success (thid: %p)\n",
|
|
(void *)target_th->thread_id);
|
|
|
|
if (target_th->errinfo != Qnil) {
|
|
VALUE err = target_th->errinfo;
|
|
|
|
if (FIXNUM_P(err)) {
|
|
/* */
|
|
}
|
|
else if (TYPE(target_th->errinfo) == T_NODE) {
|
|
rb_exc_raise(rb_vm_make_jump_tag_but_local_jump(
|
|
GET_THROWOBJ_STATE(err), GET_THROWOBJ_VAL(err)));
|
|
}
|
|
else {
|
|
/* normal exception */
|
|
rb_exc_raise(err);
|
|
}
|
|
}
|
|
return target_th->self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.join => thr
|
|
* thr.join(limit) => thr
|
|
*
|
|
* The calling thread will suspend execution and run <i>thr</i>. Does not
|
|
* return until <i>thr</i> exits or until <i>limit</i> seconds have passed. If
|
|
* the time limit expires, <code>nil</code> will be returned, otherwise
|
|
* <i>thr</i> is returned.
|
|
*
|
|
* Any threads not joined will be killed when the main program exits. If
|
|
* <i>thr</i> had previously raised an exception and the
|
|
* <code>abort_on_exception</code> and <code>$DEBUG</code> flags are not set
|
|
* (so the exception has not yet been processed) it will be processed at this
|
|
* time.
|
|
*
|
|
* a = Thread.new { print "a"; sleep(10); print "b"; print "c" }
|
|
* x = Thread.new { print "x"; Thread.pass; print "y"; print "z" }
|
|
* x.join # Let x thread finish, a will be killed on exit.
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* axyz
|
|
*
|
|
* The following example illustrates the <i>limit</i> parameter.
|
|
*
|
|
* y = Thread.new { 4.times { sleep 0.1; puts 'tick... ' }}
|
|
* puts "Waiting" until y.join(0.15)
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* tick...
|
|
* Waiting
|
|
* tick...
|
|
* Waitingtick...
|
|
*
|
|
*
|
|
* tick...
|
|
*/
|
|
|
|
static VALUE
|
|
thread_join_m(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
rb_thread_t *target_th;
|
|
double delay = DELAY_INFTY;
|
|
VALUE limit;
|
|
|
|
GetThreadPtr(self, target_th);
|
|
|
|
rb_scan_args(argc, argv, "01", &limit);
|
|
if (!NIL_P(limit)) {
|
|
delay = rb_num2dbl(limit);
|
|
}
|
|
|
|
return thread_join(target_th, delay);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.value => obj
|
|
*
|
|
* Waits for <i>thr</i> to complete (via <code>Thread#join</code>) and returns
|
|
* its value.
|
|
*
|
|
* a = Thread.new { 2 + 2 }
|
|
* a.value #=> 4
|
|
*/
|
|
|
|
static VALUE
|
|
thread_value(VALUE self)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(self, th);
|
|
thread_join(th, DELAY_INFTY);
|
|
return th->value;
|
|
}
|
|
|
|
/*
|
|
* Thread Scheduling
|
|
*/
|
|
|
|
static struct timeval
|
|
double2timeval(double d)
|
|
{
|
|
struct timeval time;
|
|
|
|
time.tv_sec = (int)d;
|
|
time.tv_usec = (int)((d - (int)d) * 1e6);
|
|
if (time.tv_usec < 0) {
|
|
time.tv_usec += (int)1e6;
|
|
time.tv_sec -= 1;
|
|
}
|
|
return time;
|
|
}
|
|
|
|
static void
|
|
sleep_forever(rb_thread_t *th, int deadlockable)
|
|
{
|
|
enum rb_thread_status prev_status = th->status;
|
|
|
|
th->status = deadlockable ? THREAD_STOPPED_FOREVER : THREAD_STOPPED;
|
|
do {
|
|
if (deadlockable) {
|
|
th->vm->sleeper++;
|
|
rb_check_deadlock(th->vm);
|
|
}
|
|
native_sleep(th, 0);
|
|
if (deadlockable) {
|
|
th->vm->sleeper--;
|
|
}
|
|
RUBY_VM_CHECK_INTS();
|
|
} while (th->status == THREAD_STOPPED_FOREVER);
|
|
th->status = prev_status;
|
|
}
|
|
|
|
static void
|
|
getclockofday(struct timeval *tp)
|
|
{
|
|
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
|
|
struct timespec ts;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
|
tp->tv_sec = ts.tv_sec;
|
|
tp->tv_usec = ts.tv_nsec / 1000;
|
|
} else
|
|
#endif
|
|
{
|
|
gettimeofday(tp, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sleep_timeval(rb_thread_t *th, struct timeval tv)
|
|
{
|
|
struct timeval to, tvn;
|
|
enum rb_thread_status prev_status = th->status;
|
|
|
|
getclockofday(&to);
|
|
to.tv_sec += tv.tv_sec;
|
|
if ((to.tv_usec += tv.tv_usec) >= 1000000) {
|
|
to.tv_sec++;
|
|
to.tv_usec -= 1000000;
|
|
}
|
|
|
|
th->status = THREAD_STOPPED;
|
|
do {
|
|
native_sleep(th, &tv);
|
|
RUBY_VM_CHECK_INTS();
|
|
getclockofday(&tvn);
|
|
if (to.tv_sec < tvn.tv_sec) break;
|
|
if (to.tv_sec == tvn.tv_sec && to.tv_usec <= tvn.tv_usec) break;
|
|
thread_debug("sleep_timeval: %ld.%.6ld > %ld.%.6ld\n",
|
|
(long)to.tv_sec, (long)to.tv_usec,
|
|
(long)tvn.tv_sec, (long)tvn.tv_usec);
|
|
tv.tv_sec = to.tv_sec - tvn.tv_sec;
|
|
if ((tv.tv_usec = to.tv_usec - tvn.tv_usec) < 0) {
|
|
--tv.tv_sec;
|
|
tv.tv_usec += 1000000;
|
|
}
|
|
} while (th->status == THREAD_STOPPED);
|
|
th->status = prev_status;
|
|
}
|
|
|
|
void
|
|
rb_thread_sleep_forever(void)
|
|
{
|
|
thread_debug("rb_thread_sleep_forever\n");
|
|
sleep_forever(GET_THREAD(), 0);
|
|
}
|
|
|
|
static void
|
|
rb_thread_sleep_deadly(void)
|
|
{
|
|
thread_debug("rb_thread_sleep_deadly\n");
|
|
sleep_forever(GET_THREAD(), 1);
|
|
}
|
|
|
|
static double
|
|
timeofday(void)
|
|
{
|
|
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
|
|
struct timespec tp;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
|
|
return (double)tp.tv_sec + (double)tp.tv_nsec * 1e-9;
|
|
} else
|
|
#endif
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sleep_wait_for_interrupt(rb_thread_t *th, double sleepsec)
|
|
{
|
|
sleep_timeval(th, double2timeval(sleepsec));
|
|
}
|
|
|
|
static void
|
|
sleep_for_polling(rb_thread_t *th)
|
|
{
|
|
struct timeval time;
|
|
time.tv_sec = 0;
|
|
time.tv_usec = 100 * 1000; /* 0.1 sec */
|
|
sleep_timeval(th, time);
|
|
}
|
|
|
|
void
|
|
rb_thread_wait_for(struct timeval time)
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
sleep_timeval(th, time);
|
|
}
|
|
|
|
void
|
|
rb_thread_polling(void)
|
|
{
|
|
RUBY_VM_CHECK_INTS();
|
|
if (!rb_thread_alone()) {
|
|
rb_thread_t *th = GET_THREAD();
|
|
sleep_for_polling(th);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CAUTION: This function causes thread switching.
|
|
* rb_thread_check_ints() check ruby's interrupts.
|
|
* some interrupt needs thread switching/invoke handlers,
|
|
* and so on.
|
|
*/
|
|
|
|
void
|
|
rb_thread_check_ints(void)
|
|
{
|
|
RUBY_VM_CHECK_INTS();
|
|
}
|
|
|
|
/*
|
|
* Hidden API for tcl/tk wrapper.
|
|
* There is no guarantee to perpetuate it.
|
|
*/
|
|
int
|
|
rb_thread_check_trap_pending(void)
|
|
{
|
|
return GET_THREAD()->exec_signal != 0;
|
|
}
|
|
|
|
/* This function can be called in blocking region. */
|
|
int
|
|
rb_thread_interrupted(VALUE thval)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
return RUBY_VM_INTERRUPTED(th);
|
|
}
|
|
|
|
struct timeval rb_time_timeval(VALUE);
|
|
|
|
void
|
|
rb_thread_sleep(int sec)
|
|
{
|
|
rb_thread_wait_for(rb_time_timeval(INT2FIX(sec)));
|
|
}
|
|
|
|
void
|
|
rb_thread_schedule(void)
|
|
{
|
|
thread_debug("rb_thread_schedule\n");
|
|
if (!rb_thread_alone()) {
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
thread_debug("rb_thread_schedule/switch start\n");
|
|
|
|
rb_gc_save_machine_context(th);
|
|
native_mutex_unlock(&th->vm->global_vm_lock);
|
|
{
|
|
native_thread_yield();
|
|
}
|
|
native_mutex_lock(&th->vm->global_vm_lock);
|
|
|
|
rb_thread_set_current(th);
|
|
thread_debug("rb_thread_schedule/switch done\n");
|
|
|
|
RUBY_VM_CHECK_INTS();
|
|
}
|
|
}
|
|
|
|
/* blocking region */
|
|
static inline void
|
|
blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
|
|
rb_unblock_function_t *func, void *arg)
|
|
{
|
|
region->prev_status = th->status;
|
|
th->blocking_region_buffer = region;
|
|
set_unblock_function(th, func, arg, ®ion->oldubf);
|
|
th->status = THREAD_STOPPED;
|
|
thread_debug("enter blocking region (%p)\n", (void *)th);
|
|
rb_gc_save_machine_context(th);
|
|
native_mutex_unlock(&th->vm->global_vm_lock);
|
|
}
|
|
|
|
static inline void
|
|
blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region)
|
|
{
|
|
native_mutex_lock(&th->vm->global_vm_lock);
|
|
rb_thread_set_current(th);
|
|
thread_debug("leave blocking region (%p)\n", (void *)th);
|
|
remove_signal_thread_list(th);
|
|
th->blocking_region_buffer = 0;
|
|
reset_unblock_function(th, ®ion->oldubf);
|
|
if (th->status == THREAD_STOPPED) {
|
|
th->status = region->prev_status;
|
|
}
|
|
}
|
|
|
|
struct rb_blocking_region_buffer *
|
|
rb_thread_blocking_region_begin(void)
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
struct rb_blocking_region_buffer *region = ALLOC(struct rb_blocking_region_buffer);
|
|
blocking_region_begin(th, region, ubf_select, th);
|
|
return region;
|
|
}
|
|
|
|
void
|
|
rb_thread_blocking_region_end(struct rb_blocking_region_buffer *region)
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
blocking_region_end(th, region);
|
|
xfree(region);
|
|
RUBY_VM_CHECK_INTS();
|
|
}
|
|
|
|
/*
|
|
* rb_thread_blocking_region - permit concurrent/parallel execution.
|
|
*
|
|
* This function does:
|
|
* (1) release GVL.
|
|
* Other Ruby threads may run in parallel.
|
|
* (2) call func with data1.
|
|
* (3) aquire GVL.
|
|
* Other Ruby threads can not run in parallel any more.
|
|
*
|
|
* If another thread interrupts this thread (Thread#kill, signal deliverly,
|
|
* VM-shutdown request, and so on), `ubf()' is called (`ubf()' means
|
|
* "un-blocking function"). `ubf()' should interrupt `func()' execution.
|
|
*
|
|
* There are built-in ubfs and you can specify these ubfs.
|
|
* However, we can not guarantee our built-in ubfs interrupt
|
|
* your `func()' correctly. Be careful to use rb_thread_blocking_region().
|
|
*
|
|
* * RUBY_UBF_IO: ubf for IO operation
|
|
* * RUBY_UBF_PROCESS: ubf for process operation
|
|
*
|
|
* NOTE: You can not execute most of Ruby C API and touch Ruby objects
|
|
* in `func()' and `ubf()' because current thread doesn't acquire
|
|
* GVL (cause synchronization problem). If you need to do it,
|
|
* read source code of C APIs and confirm by yourself.
|
|
*
|
|
* NOTE: In short, this API is difficult to use safely. I recommend you
|
|
* use other ways if you have. We lack experiences to use this API.
|
|
* Please report your problem related on it.
|
|
*
|
|
* Safe C API:
|
|
* * rb_thread_interrupted() - check interrupt flag
|
|
* * ruby_xalloc(), ruby_xrealloc(), ruby_xfree() -
|
|
* if they called without GVL, acquire GVL automatically.
|
|
*/
|
|
VALUE
|
|
rb_thread_blocking_region(
|
|
rb_blocking_function_t *func, void *data1,
|
|
rb_unblock_function_t *ubf, void *data2)
|
|
{
|
|
VALUE val;
|
|
rb_thread_t *th = GET_THREAD();
|
|
|
|
if (ubf == RUBY_UBF_IO || ubf == RUBY_UBF_PROCESS) {
|
|
ubf = ubf_select;
|
|
data2 = th;
|
|
}
|
|
|
|
BLOCKING_REGION({
|
|
val = func(data1);
|
|
}, ubf, data2);
|
|
|
|
return val;
|
|
}
|
|
|
|
/* alias of rb_thread_blocking_region() */
|
|
|
|
VALUE
|
|
rb_thread_call_without_gvl(
|
|
rb_blocking_function_t *func, void *data1,
|
|
rb_unblock_function_t *ubf, void *data2)
|
|
{
|
|
return rb_thread_blocking_region(func, data1, ubf, data2);
|
|
}
|
|
|
|
/*
|
|
* rb_thread_call_with_gvl - re-enter into Ruby world while releasing GVL.
|
|
*
|
|
***
|
|
*** This API is EXPERIMENTAL!
|
|
*** We do not guarantee that this API remains in ruby 1.9.2 or later.
|
|
***
|
|
*
|
|
* While releasing GVL using rb_thread_blocking_region() or
|
|
* rb_thread_call_without_gvl(), you can not access Ruby values or invoke methods.
|
|
* If you need to access it, you must use this function rb_thread_call_with_gvl().
|
|
*
|
|
* This function rb_thread_call_with_gvl() does:
|
|
* (1) acquire GVL.
|
|
* (2) call passed function `func'.
|
|
* (3) release GVL.
|
|
* (4) return a value which is returned at (2).
|
|
*
|
|
* NOTE: You should not return Ruby object at (2) because such Object
|
|
* will not marked.
|
|
*
|
|
* NOTE: If an exception is raised in `func', this function "DOES NOT"
|
|
* protect (catch) the exception. If you have any resources
|
|
* which should free before throwing exception, you need use
|
|
* rb_protect() in `func' and return a value which represents
|
|
* exception is raised.
|
|
*
|
|
* NOTE: This functions should not be called by a thread which
|
|
* is not created as Ruby thread (created by Thread.new or so).
|
|
* In other words, this function *DOES NOT* associate
|
|
* NON-Ruby thread to Ruby thread.
|
|
*/
|
|
void *
|
|
rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
|
|
{
|
|
rb_thread_t *th = ruby_thread_from_native();
|
|
struct rb_blocking_region_buffer *brb;
|
|
struct rb_unblock_callback prev_unblock;
|
|
void *r;
|
|
|
|
if (th == 0) {
|
|
/* Error is occurred, but we can't use rb_bug()
|
|
* because this thread is not Ruby's thread.
|
|
* What should we do?
|
|
*/
|
|
|
|
fprintf(stderr, "[BUG] rb_thread_call_with_gvl() is called by non-ruby thread\n");
|
|
exit(1);
|
|
}
|
|
|
|
brb = (struct rb_blocking_region_buffer *)th->blocking_region_buffer;
|
|
prev_unblock = th->unblock;
|
|
|
|
if (brb == 0) {
|
|
rb_bug("rb_thread_call_with_gvl: called by a thread which has GVL.");
|
|
}
|
|
|
|
blocking_region_end(th, brb);
|
|
/* enter to Ruby world: You can access Ruby values, methods and so on. */
|
|
r = (*func)(data1);
|
|
/* levae from Ruby world: You can not access Ruby values, etc. */
|
|
blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg);
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* ruby_thread_has_gvl_p - check if current native thread has GVL.
|
|
*
|
|
***
|
|
*** This API is EXPERIMENTAL!
|
|
*** We do not guarantee that this API remains in ruby 1.9.2 or later.
|
|
***
|
|
*/
|
|
|
|
int
|
|
ruby_thread_has_gvl_p(void)
|
|
{
|
|
rb_thread_t *th = ruby_thread_from_native();
|
|
|
|
if (th && th->blocking_region_buffer == 0) {
|
|
return 1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.pass => nil
|
|
*
|
|
* Invokes the thread scheduler to pass execution to another thread.
|
|
*
|
|
* a = Thread.new { print "a"; Thread.pass;
|
|
* print "b"; Thread.pass;
|
|
* print "c" }
|
|
* b = Thread.new { print "x"; Thread.pass;
|
|
* print "y"; Thread.pass;
|
|
* print "z" }
|
|
* a.join
|
|
* b.join
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* axbycz
|
|
*/
|
|
|
|
static VALUE
|
|
thread_s_pass(VALUE klass)
|
|
{
|
|
rb_thread_schedule();
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
void
|
|
rb_threadptr_execute_interrupts(rb_thread_t *th)
|
|
{
|
|
if (GET_VM()->main_thread == th) {
|
|
while (rb_signal_buff_size() && !th->exec_signal) native_thread_yield();
|
|
}
|
|
|
|
if (th->raised_flag) return;
|
|
|
|
while (th->interrupt_flag) {
|
|
enum rb_thread_status status = th->status;
|
|
int timer_interrupt = th->interrupt_flag & 0x01;
|
|
int finalizer_interrupt = th->interrupt_flag & 0x04;
|
|
|
|
th->status = THREAD_RUNNABLE;
|
|
th->interrupt_flag = 0;
|
|
|
|
/* signal handling */
|
|
if (th->exec_signal) {
|
|
int sig = th->exec_signal;
|
|
th->exec_signal = 0;
|
|
rb_signal_exec(th, sig);
|
|
}
|
|
|
|
/* exception from another thread */
|
|
if (th->thrown_errinfo) {
|
|
VALUE err = th->thrown_errinfo;
|
|
th->thrown_errinfo = 0;
|
|
thread_debug("rb_thread_execute_interrupts: %ld\n", err);
|
|
|
|
if (err == eKillSignal || err == eTerminateSignal) {
|
|
th->errinfo = INT2FIX(TAG_FATAL);
|
|
TH_JUMP_TAG(th, TAG_FATAL);
|
|
}
|
|
else {
|
|
rb_exc_raise(err);
|
|
}
|
|
}
|
|
th->status = status;
|
|
|
|
if (finalizer_interrupt) {
|
|
rb_gc_finalize_deferred();
|
|
}
|
|
|
|
if (timer_interrupt) {
|
|
EXEC_EVENT_HOOK(th, RUBY_EVENT_SWITCH, th->cfp->self, 0, 0);
|
|
|
|
if (th->slice > 0) {
|
|
th->slice--;
|
|
}
|
|
else {
|
|
reschedule:
|
|
rb_thread_schedule();
|
|
if (th->slice < 0) {
|
|
th->slice++;
|
|
goto reschedule;
|
|
}
|
|
else {
|
|
th->slice = th->priority;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
rb_gc_mark_threads(void)
|
|
{
|
|
/* TODO: remove */
|
|
}
|
|
|
|
/*****************************************************/
|
|
|
|
static void
|
|
rb_threadptr_ready(rb_thread_t *th)
|
|
{
|
|
rb_threadptr_interrupt(th);
|
|
}
|
|
|
|
static VALUE
|
|
rb_threadptr_raise(rb_thread_t *th, int argc, VALUE *argv)
|
|
{
|
|
VALUE exc;
|
|
|
|
again:
|
|
if (rb_threadptr_dead(th)) {
|
|
return Qnil;
|
|
}
|
|
|
|
if (th->thrown_errinfo != 0 || th->raised_flag) {
|
|
rb_thread_schedule();
|
|
goto again;
|
|
}
|
|
|
|
exc = rb_make_exception(argc, argv);
|
|
th->thrown_errinfo = exc;
|
|
rb_threadptr_ready(th);
|
|
return Qnil;
|
|
}
|
|
|
|
void
|
|
rb_threadptr_signal_raise(rb_thread_t *th, int sig)
|
|
{
|
|
VALUE argv[2];
|
|
|
|
argv[0] = rb_eSignal;
|
|
argv[1] = INT2FIX(sig);
|
|
rb_threadptr_raise(th->vm->main_thread, 2, argv);
|
|
}
|
|
|
|
void
|
|
rb_threadptr_signal_exit(rb_thread_t *th)
|
|
{
|
|
VALUE argv[2];
|
|
|
|
argv[0] = rb_eSystemExit;
|
|
argv[1] = rb_str_new2("exit");
|
|
rb_threadptr_raise(th->vm->main_thread, 2, argv);
|
|
}
|
|
|
|
#if defined(POSIX_SIGNAL) && defined(SIGSEGV) && defined(HAVE_SIGALTSTACK)
|
|
#define USE_SIGALTSTACK
|
|
#endif
|
|
|
|
void
|
|
ruby_thread_stack_overflow(rb_thread_t *th)
|
|
{
|
|
th->raised_flag = 0;
|
|
#ifdef USE_SIGALTSTACK
|
|
th->raised_flag = 0;
|
|
rb_exc_raise(sysstack_error);
|
|
#else
|
|
th->errinfo = sysstack_error;
|
|
TH_JUMP_TAG(th, TAG_RAISE);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
rb_threadptr_set_raised(rb_thread_t *th)
|
|
{
|
|
if (th->raised_flag & RAISED_EXCEPTION) {
|
|
return 1;
|
|
}
|
|
th->raised_flag |= RAISED_EXCEPTION;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
rb_threadptr_reset_raised(rb_thread_t *th)
|
|
{
|
|
if (!(th->raised_flag & RAISED_EXCEPTION)) {
|
|
return 0;
|
|
}
|
|
th->raised_flag &= ~RAISED_EXCEPTION;
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
rb_thread_fd_close(int fd)
|
|
{
|
|
/* TODO: fix me */
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.raise(exception)
|
|
*
|
|
* Raises an exception (see <code>Kernel::raise</code>) from <i>thr</i>. The
|
|
* caller does not have to be <i>thr</i>.
|
|
*
|
|
* Thread.abort_on_exception = true
|
|
* a = Thread.new { sleep(200) }
|
|
* a.raise("Gotcha")
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* prog.rb:3: Gotcha (RuntimeError)
|
|
* from prog.rb:2:in `initialize'
|
|
* from prog.rb:2:in `new'
|
|
* from prog.rb:2
|
|
*/
|
|
|
|
static VALUE
|
|
thread_raise_m(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(self, th);
|
|
rb_threadptr_raise(th, argc, argv);
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.exit => thr or nil
|
|
* thr.kill => thr or nil
|
|
* thr.terminate => thr or nil
|
|
*
|
|
* Terminates <i>thr</i> and schedules another thread to be run. If this thread
|
|
* is already marked to be killed, <code>exit</code> returns the
|
|
* <code>Thread</code>. If this is the main thread, or the last thread, exits
|
|
* the process.
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_kill(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (th != GET_THREAD() && th->safe_level < 4) {
|
|
rb_secure(4);
|
|
}
|
|
if (th->status == THREAD_TO_KILL || th->status == THREAD_KILLED) {
|
|
return thread;
|
|
}
|
|
if (th == th->vm->main_thread) {
|
|
rb_exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
thread_debug("rb_thread_kill: %p (%p)\n", (void *)th, (void *)th->thread_id);
|
|
|
|
rb_threadptr_interrupt(th);
|
|
th->thrown_errinfo = eKillSignal;
|
|
th->status = THREAD_TO_KILL;
|
|
|
|
return thread;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.kill(thread) => thread
|
|
*
|
|
* Causes the given <em>thread</em> to exit (see <code>Thread::exit</code>).
|
|
*
|
|
* count = 0
|
|
* a = Thread.new { loop { count += 1 } }
|
|
* sleep(0.1) #=> 0
|
|
* Thread.kill(a) #=> #<Thread:0x401b3d30 dead>
|
|
* count #=> 93947
|
|
* a.alive? #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_s_kill(VALUE obj, VALUE th)
|
|
{
|
|
return rb_thread_kill(th);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.exit => thread
|
|
*
|
|
* Terminates the currently running thread and schedules another thread to be
|
|
* run. If this thread is already marked to be killed, <code>exit</code>
|
|
* returns the <code>Thread</code>. If this is the main thread, or the last
|
|
* thread, exit the process.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_exit(void)
|
|
{
|
|
return rb_thread_kill(GET_THREAD()->self);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.wakeup => thr
|
|
*
|
|
* Marks <i>thr</i> as eligible for scheduling (it may still remain blocked on
|
|
* I/O, however). Does not invoke the scheduler (see <code>Thread#run</code>).
|
|
*
|
|
* c = Thread.new { Thread.stop; puts "hey!" }
|
|
* c.wakeup
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* hey!
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_wakeup(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (th->status == THREAD_KILLED) {
|
|
rb_raise(rb_eThreadError, "killed thread");
|
|
}
|
|
rb_threadptr_ready(th);
|
|
if (th->status != THREAD_TO_KILL) {
|
|
th->status = THREAD_RUNNABLE;
|
|
}
|
|
return thread;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.run => thr
|
|
*
|
|
* Wakes up <i>thr</i>, making it eligible for scheduling.
|
|
*
|
|
* a = Thread.new { puts "a"; Thread.stop; puts "c" }
|
|
* Thread.pass
|
|
* puts "Got here"
|
|
* a.run
|
|
* a.join
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* a
|
|
* Got here
|
|
* c
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_run(VALUE thread)
|
|
{
|
|
rb_thread_wakeup(thread);
|
|
rb_thread_schedule();
|
|
return thread;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.stop => nil
|
|
*
|
|
* Stops execution of the current thread, putting it into a ``sleep'' state,
|
|
* and schedules execution of another thread.
|
|
*
|
|
* a = Thread.new { print "a"; Thread.stop; print "c" }
|
|
* Thread.pass
|
|
* print "b"
|
|
* a.run
|
|
* a.join
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* abc
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_stop(void)
|
|
{
|
|
if (rb_thread_alone()) {
|
|
rb_raise(rb_eThreadError,
|
|
"stopping only thread\n\tnote: use sleep to stop forever");
|
|
}
|
|
rb_thread_sleep_deadly();
|
|
return Qnil;
|
|
}
|
|
|
|
static int
|
|
thread_list_i(st_data_t key, st_data_t val, void *data)
|
|
{
|
|
VALUE ary = (VALUE)data;
|
|
rb_thread_t *th;
|
|
GetThreadPtr((VALUE)key, th);
|
|
|
|
switch (th->status) {
|
|
case THREAD_RUNNABLE:
|
|
case THREAD_STOPPED:
|
|
case THREAD_STOPPED_FOREVER:
|
|
case THREAD_TO_KILL:
|
|
rb_ary_push(ary, th->self);
|
|
default:
|
|
break;
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/********************************************************************/
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.list => array
|
|
*
|
|
* Returns an array of <code>Thread</code> objects for all threads that are
|
|
* either runnable or stopped.
|
|
*
|
|
* Thread.new { sleep(200) }
|
|
* Thread.new { 1000000.times {|i| i*i } }
|
|
* Thread.new { Thread.stop }
|
|
* Thread.list.each {|t| p t}
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* #<Thread:0x401b3e84 sleep>
|
|
* #<Thread:0x401b3f38 run>
|
|
* #<Thread:0x401b3fb0 sleep>
|
|
* #<Thread:0x401bdf4c run>
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_list(void)
|
|
{
|
|
VALUE ary = rb_ary_new();
|
|
st_foreach(GET_THREAD()->vm->living_threads, thread_list_i, ary);
|
|
return ary;
|
|
}
|
|
|
|
VALUE
|
|
rb_thread_current(void)
|
|
{
|
|
return GET_THREAD()->self;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.current => thread
|
|
*
|
|
* Returns the currently executing thread.
|
|
*
|
|
* Thread.current #=> #<Thread:0x401bdf4c run>
|
|
*/
|
|
|
|
static VALUE
|
|
thread_s_current(VALUE klass)
|
|
{
|
|
return rb_thread_current();
|
|
}
|
|
|
|
VALUE
|
|
rb_thread_main(void)
|
|
{
|
|
return GET_THREAD()->vm->main_thread->self;
|
|
}
|
|
|
|
static VALUE
|
|
rb_thread_s_main(VALUE klass)
|
|
{
|
|
return rb_thread_main();
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.abort_on_exception => true or false
|
|
*
|
|
* Returns the status of the global ``abort on exception'' condition. The
|
|
* default is <code>false</code>. When set to <code>true</code>, or if the
|
|
* global <code>$DEBUG</code> flag is <code>true</code> (perhaps because the
|
|
* command line option <code>-d</code> was specified) all threads will abort
|
|
* (the process will <code>exit(0)</code>) if an exception is raised in any
|
|
* thread. See also <code>Thread::abort_on_exception=</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_s_abort_exc(void)
|
|
{
|
|
return GET_THREAD()->vm->thread_abort_on_exception ? Qtrue : Qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* Thread.abort_on_exception= boolean => true or false
|
|
*
|
|
* When set to <code>true</code>, all threads will abort if an exception is
|
|
* raised. Returns the new state.
|
|
*
|
|
* Thread.abort_on_exception = true
|
|
* t1 = Thread.new do
|
|
* puts "In new thread"
|
|
* raise "Exception from thread"
|
|
* end
|
|
* sleep(1)
|
|
* puts "not reached"
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* In new thread
|
|
* prog.rb:4: Exception from thread (RuntimeError)
|
|
* from prog.rb:2:in `initialize'
|
|
* from prog.rb:2:in `new'
|
|
* from prog.rb:2
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_s_abort_exc_set(VALUE self, VALUE val)
|
|
{
|
|
rb_secure(4);
|
|
GET_THREAD()->vm->thread_abort_on_exception = RTEST(val);
|
|
return val;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.abort_on_exception => true or false
|
|
*
|
|
* Returns the status of the thread-local ``abort on exception'' condition for
|
|
* <i>thr</i>. The default is <code>false</code>. See also
|
|
* <code>Thread::abort_on_exception=</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_abort_exc(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
return th->abort_on_exception ? Qtrue : Qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.abort_on_exception= boolean => true or false
|
|
*
|
|
* When set to <code>true</code>, causes all threads (including the main
|
|
* program) to abort if an exception is raised in <i>thr</i>. The process will
|
|
* effectively <code>exit(0)</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_abort_exc_set(VALUE thread, VALUE val)
|
|
{
|
|
rb_thread_t *th;
|
|
rb_secure(4);
|
|
|
|
GetThreadPtr(thread, th);
|
|
th->abort_on_exception = RTEST(val);
|
|
return val;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.group => thgrp or nil
|
|
*
|
|
* Returns the <code>ThreadGroup</code> which contains <i>thr</i>, or nil if
|
|
* the thread is not a member of any group.
|
|
*
|
|
* Thread.main.group #=> #<ThreadGroup:0x4029d914>
|
|
*/
|
|
|
|
VALUE
|
|
rb_thread_group(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
VALUE group;
|
|
GetThreadPtr(thread, th);
|
|
group = th->thgroup;
|
|
|
|
if (!group) {
|
|
group = Qnil;
|
|
}
|
|
return group;
|
|
}
|
|
|
|
static const char *
|
|
thread_status_name(enum rb_thread_status status)
|
|
{
|
|
switch (status) {
|
|
case THREAD_RUNNABLE:
|
|
return "run";
|
|
case THREAD_STOPPED:
|
|
case THREAD_STOPPED_FOREVER:
|
|
return "sleep";
|
|
case THREAD_TO_KILL:
|
|
return "aborting";
|
|
case THREAD_KILLED:
|
|
return "dead";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static int
|
|
rb_threadptr_dead(rb_thread_t *th)
|
|
{
|
|
return th->status == THREAD_KILLED;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.status => string, false or nil
|
|
*
|
|
* Returns the status of <i>thr</i>: ``<code>sleep</code>'' if <i>thr</i> is
|
|
* sleeping or waiting on I/O, ``<code>run</code>'' if <i>thr</i> is executing,
|
|
* ``<code>aborting</code>'' if <i>thr</i> is aborting, <code>false</code> if
|
|
* <i>thr</i> terminated normally, and <code>nil</code> if <i>thr</i>
|
|
* terminated with an exception.
|
|
*
|
|
* a = Thread.new { raise("die now") }
|
|
* b = Thread.new { Thread.stop }
|
|
* c = Thread.new { Thread.exit }
|
|
* d = Thread.new { sleep }
|
|
* d.kill #=> #<Thread:0x401b3678 aborting>
|
|
* a.status #=> nil
|
|
* b.status #=> "sleep"
|
|
* c.status #=> false
|
|
* d.status #=> "aborting"
|
|
* Thread.current.status #=> "run"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_status(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (rb_threadptr_dead(th)) {
|
|
if (!NIL_P(th->errinfo) && !FIXNUM_P(th->errinfo)
|
|
/* TODO */ ) {
|
|
return Qnil;
|
|
}
|
|
return Qfalse;
|
|
}
|
|
return rb_str_new2(thread_status_name(th->status));
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.alive? => true or false
|
|
*
|
|
* Returns <code>true</code> if <i>thr</i> is running or sleeping.
|
|
*
|
|
* thr = Thread.new { }
|
|
* thr.join #=> #<Thread:0x401b3fb0 dead>
|
|
* Thread.current.alive? #=> true
|
|
* thr.alive? #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_alive_p(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (rb_threadptr_dead(th))
|
|
return Qfalse;
|
|
return Qtrue;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.stop? => true or false
|
|
*
|
|
* Returns <code>true</code> if <i>thr</i> is dead or sleeping.
|
|
*
|
|
* a = Thread.new { Thread.stop }
|
|
* b = Thread.current
|
|
* a.stop? #=> true
|
|
* b.stop? #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_stop_p(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (rb_threadptr_dead(th))
|
|
return Qtrue;
|
|
if (th->status == THREAD_STOPPED || th->status == THREAD_STOPPED_FOREVER)
|
|
return Qtrue;
|
|
return Qfalse;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.safe_level => integer
|
|
*
|
|
* Returns the safe level in effect for <i>thr</i>. Setting thread-local safe
|
|
* levels can help when implementing sandboxes which run insecure code.
|
|
*
|
|
* thr = Thread.new { $SAFE = 3; sleep }
|
|
* Thread.current.safe_level #=> 0
|
|
* thr.safe_level #=> 3
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_safe_level(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
return INT2NUM(th->safe_level);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.inspect => string
|
|
*
|
|
* Dump the name, id, and status of _thr_ to a string.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_inspect(VALUE thread)
|
|
{
|
|
const char *cname = rb_obj_classname(thread);
|
|
rb_thread_t *th;
|
|
const char *status;
|
|
VALUE str;
|
|
|
|
GetThreadPtr(thread, th);
|
|
status = thread_status_name(th->status);
|
|
str = rb_sprintf("#<%s:%p %s>", cname, (void *)thread, status);
|
|
OBJ_INFECT(str, thread);
|
|
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_thread_local_aref(VALUE thread, ID id)
|
|
{
|
|
rb_thread_t *th;
|
|
VALUE val;
|
|
|
|
GetThreadPtr(thread, th);
|
|
if (rb_safe_level() >= 4 && th != GET_THREAD()) {
|
|
rb_raise(rb_eSecurityError, "Insecure: thread locals");
|
|
}
|
|
if (!th->local_storage) {
|
|
return Qnil;
|
|
}
|
|
if (st_lookup(th->local_storage, id, &val)) {
|
|
return val;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr[sym] => obj or nil
|
|
*
|
|
* Attribute Reference---Returns the value of a thread-local variable, using
|
|
* either a symbol or a string name. If the specified variable does not exist,
|
|
* returns <code>nil</code>.
|
|
*
|
|
* a = Thread.new { Thread.current["name"] = "A"; Thread.stop }
|
|
* b = Thread.new { Thread.current[:name] = "B"; Thread.stop }
|
|
* c = Thread.new { Thread.current["name"] = "C"; Thread.stop }
|
|
* Thread.list.each {|x| puts "#{x.inspect}: #{x[:name]}" }
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* #<Thread:0x401b3b3c sleep>: C
|
|
* #<Thread:0x401b3bc8 sleep>: B
|
|
* #<Thread:0x401b3c68 sleep>: A
|
|
* #<Thread:0x401bdf4c run>:
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_aref(VALUE thread, VALUE id)
|
|
{
|
|
return rb_thread_local_aref(thread, rb_to_id(id));
|
|
}
|
|
|
|
VALUE
|
|
rb_thread_local_aset(VALUE thread, ID id, VALUE val)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (rb_safe_level() >= 4 && th != GET_THREAD()) {
|
|
rb_raise(rb_eSecurityError, "Insecure: can't modify thread locals");
|
|
}
|
|
if (OBJ_FROZEN(thread)) {
|
|
rb_error_frozen("thread locals");
|
|
}
|
|
if (!th->local_storage) {
|
|
th->local_storage = st_init_numtable();
|
|
}
|
|
if (NIL_P(val)) {
|
|
st_delete_wrap(th->local_storage, id);
|
|
return Qnil;
|
|
}
|
|
st_insert(th->local_storage, id, val);
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr[sym] = obj => obj
|
|
*
|
|
* Attribute Assignment---Sets or creates the value of a thread-local variable,
|
|
* using either a symbol or a string. See also <code>Thread#[]</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_aset(VALUE self, ID id, VALUE val)
|
|
{
|
|
return rb_thread_local_aset(self, rb_to_id(id), val);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.key?(sym) => true or false
|
|
*
|
|
* Returns <code>true</code> if the given string (or symbol) exists as a
|
|
* thread-local variable.
|
|
*
|
|
* me = Thread.current
|
|
* me[:oliver] = "a"
|
|
* me.key?(:oliver) #=> true
|
|
* me.key?(:stanley) #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_key_p(VALUE self, VALUE key)
|
|
{
|
|
rb_thread_t *th;
|
|
ID id = rb_to_id(key);
|
|
|
|
GetThreadPtr(self, th);
|
|
|
|
if (!th->local_storage) {
|
|
return Qfalse;
|
|
}
|
|
if (st_lookup(th->local_storage, id, 0)) {
|
|
return Qtrue;
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
static int
|
|
thread_keys_i(ID key, VALUE value, VALUE ary)
|
|
{
|
|
rb_ary_push(ary, ID2SYM(key));
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
vm_living_thread_num(rb_vm_t *vm)
|
|
{
|
|
return vm->living_threads->num_entries;
|
|
}
|
|
|
|
int
|
|
rb_thread_alone(void)
|
|
{
|
|
int num = 1;
|
|
if (GET_THREAD()->vm->living_threads) {
|
|
num = vm_living_thread_num(GET_THREAD()->vm);
|
|
thread_debug("rb_thread_alone: %d\n", num);
|
|
}
|
|
return num == 1;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.keys => array
|
|
*
|
|
* Returns an an array of the names of the thread-local variables (as Symbols).
|
|
*
|
|
* thr = Thread.new do
|
|
* Thread.current[:cat] = 'meow'
|
|
* Thread.current["dog"] = 'woof'
|
|
* end
|
|
* thr.join #=> #<Thread:0x401b3f10 dead>
|
|
* thr.keys #=> [:dog, :cat]
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_keys(VALUE self)
|
|
{
|
|
rb_thread_t *th;
|
|
VALUE ary = rb_ary_new();
|
|
GetThreadPtr(self, th);
|
|
|
|
if (th->local_storage) {
|
|
st_foreach(th->local_storage, thread_keys_i, ary);
|
|
}
|
|
return ary;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.priority => integer
|
|
*
|
|
* Returns the priority of <i>thr</i>. Default is inherited from the
|
|
* current thread which creating the new thread, or zero for the
|
|
* initial main thread; higher-priority threads will run before
|
|
* lower-priority threads.
|
|
*
|
|
* Thread.current.priority #=> 0
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_priority(VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
return INT2NUM(th->priority);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thr.priority= integer => thr
|
|
*
|
|
* Sets the priority of <i>thr</i> to <i>integer</i>. Higher-priority threads
|
|
* will run before lower-priority threads.
|
|
*
|
|
* count1 = count2 = 0
|
|
* a = Thread.new do
|
|
* loop { count1 += 1 }
|
|
* end
|
|
* a.priority = -1
|
|
*
|
|
* b = Thread.new do
|
|
* loop { count2 += 1 }
|
|
* end
|
|
* b.priority = -2
|
|
* sleep 1 #=> 1
|
|
* count1 #=> 622504
|
|
* count2 #=> 5832
|
|
*/
|
|
|
|
static VALUE
|
|
rb_thread_priority_set(VALUE thread, VALUE prio)
|
|
{
|
|
rb_thread_t *th;
|
|
int priority;
|
|
GetThreadPtr(thread, th);
|
|
|
|
rb_secure(4);
|
|
|
|
#if USE_NATIVE_THREAD_PRIORITY
|
|
th->priority = NUM2INT(prio);
|
|
native_thread_apply_priority(th);
|
|
#else
|
|
priority = NUM2INT(prio);
|
|
if (priority > RUBY_THREAD_PRIORITY_MAX) {
|
|
priority = RUBY_THREAD_PRIORITY_MAX;
|
|
}
|
|
else if (priority < RUBY_THREAD_PRIORITY_MIN) {
|
|
priority = RUBY_THREAD_PRIORITY_MIN;
|
|
}
|
|
th->priority = priority;
|
|
th->slice = priority;
|
|
#endif
|
|
return INT2NUM(th->priority);
|
|
}
|
|
|
|
/* for IO */
|
|
|
|
#if defined(NFDBITS) && defined(HAVE_RB_FD_INIT)
|
|
|
|
/*
|
|
* several Unix platforms support file descriptors bigger than FD_SETSIZE
|
|
* in select(2) system call.
|
|
*
|
|
* - Linux 2.2.12 (?)
|
|
* - NetBSD 1.2 (src/sys/kern/sys_generic.c:1.25)
|
|
* select(2) documents how to allocate fd_set dynamically.
|
|
* http://netbsd.gw.com/cgi-bin/man-cgi?select++NetBSD-4.0
|
|
* - FreeBSD 2.2 (src/sys/kern/sys_generic.c:1.19)
|
|
* - OpenBSD 2.0 (src/sys/kern/sys_generic.c:1.4)
|
|
* select(2) documents how to allocate fd_set dynamically.
|
|
* http://www.openbsd.org/cgi-bin/man.cgi?query=select&manpath=OpenBSD+4.4
|
|
* - HP-UX documents how to allocate fd_set dynamically.
|
|
* http://docs.hp.com/en/B2355-60105/select.2.html
|
|
* - Solaris 8 has select_large_fdset
|
|
*
|
|
* When fd_set is not big enough to hold big file descriptors,
|
|
* it should be allocated dynamically.
|
|
* Note that this assumes fd_set is structured as bitmap.
|
|
*
|
|
* rb_fd_init allocates the memory.
|
|
* rb_fd_term free the memory.
|
|
* rb_fd_set may re-allocates bitmap.
|
|
*
|
|
* So rb_fd_set doesn't reject file descriptors bigger than FD_SETSIZE.
|
|
*/
|
|
|
|
void
|
|
rb_fd_init(volatile rb_fdset_t *fds)
|
|
{
|
|
fds->maxfd = 0;
|
|
fds->fdset = ALLOC(fd_set);
|
|
FD_ZERO(fds->fdset);
|
|
}
|
|
|
|
void
|
|
rb_fd_term(rb_fdset_t *fds)
|
|
{
|
|
if (fds->fdset) xfree(fds->fdset);
|
|
fds->maxfd = 0;
|
|
fds->fdset = 0;
|
|
}
|
|
|
|
void
|
|
rb_fd_zero(rb_fdset_t *fds)
|
|
{
|
|
if (fds->fdset) {
|
|
MEMZERO(fds->fdset, fd_mask, howmany(fds->maxfd, NFDBITS));
|
|
FD_ZERO(fds->fdset);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_fd_resize(int n, rb_fdset_t *fds)
|
|
{
|
|
size_t m = howmany(n + 1, NFDBITS) * sizeof(fd_mask);
|
|
size_t o = howmany(fds->maxfd, NFDBITS) * sizeof(fd_mask);
|
|
|
|
if (m < sizeof(fd_set)) m = sizeof(fd_set);
|
|
if (o < sizeof(fd_set)) o = sizeof(fd_set);
|
|
|
|
if (m > o) {
|
|
fds->fdset = realloc(fds->fdset, m);
|
|
memset((char *)fds->fdset + o, 0, m - o);
|
|
}
|
|
if (n >= fds->maxfd) fds->maxfd = n + 1;
|
|
}
|
|
|
|
void
|
|
rb_fd_set(int n, rb_fdset_t *fds)
|
|
{
|
|
rb_fd_resize(n, fds);
|
|
FD_SET(n, fds->fdset);
|
|
}
|
|
|
|
void
|
|
rb_fd_clr(int n, rb_fdset_t *fds)
|
|
{
|
|
if (n >= fds->maxfd) return;
|
|
FD_CLR(n, fds->fdset);
|
|
}
|
|
|
|
int
|
|
rb_fd_isset(int n, const rb_fdset_t *fds)
|
|
{
|
|
if (n >= fds->maxfd) return 0;
|
|
return FD_ISSET(n, fds->fdset) != 0; /* "!= 0" avoids FreeBSD PR 91421 */
|
|
}
|
|
|
|
void
|
|
rb_fd_copy(rb_fdset_t *dst, const fd_set *src, int max)
|
|
{
|
|
size_t size = howmany(max, NFDBITS) * sizeof(fd_mask);
|
|
|
|
if (size < sizeof(fd_set)) size = sizeof(fd_set);
|
|
dst->maxfd = max;
|
|
dst->fdset = realloc(dst->fdset, size);
|
|
memcpy(dst->fdset, src, size);
|
|
}
|
|
|
|
int
|
|
rb_fd_select(int n, rb_fdset_t *readfds, rb_fdset_t *writefds, rb_fdset_t *exceptfds, struct timeval *timeout)
|
|
{
|
|
fd_set *r = NULL, *w = NULL, *e = NULL;
|
|
if (readfds) {
|
|
rb_fd_resize(n - 1, readfds);
|
|
r = rb_fd_ptr(readfds);
|
|
}
|
|
if (writefds) {
|
|
rb_fd_resize(n - 1, writefds);
|
|
w = rb_fd_ptr(writefds);
|
|
}
|
|
if (exceptfds) {
|
|
rb_fd_resize(n - 1, exceptfds);
|
|
e = rb_fd_ptr(exceptfds);
|
|
}
|
|
return select(n, r, w, e, timeout);
|
|
}
|
|
|
|
#undef FD_ZERO
|
|
#undef FD_SET
|
|
#undef FD_CLR
|
|
#undef FD_ISSET
|
|
|
|
#define FD_ZERO(f) rb_fd_zero(f)
|
|
#define FD_SET(i, f) rb_fd_set(i, f)
|
|
#define FD_CLR(i, f) rb_fd_clr(i, f)
|
|
#define FD_ISSET(i, f) rb_fd_isset(i, f)
|
|
|
|
#elif defined(_WIN32)
|
|
|
|
void
|
|
rb_fd_init(volatile rb_fdset_t *set)
|
|
{
|
|
set->capa = FD_SETSIZE;
|
|
set->fdset = ALLOC(fd_set);
|
|
FD_ZERO(set->fdset);
|
|
}
|
|
|
|
void
|
|
rb_fd_term(rb_fdset_t *set)
|
|
{
|
|
xfree(set->fdset);
|
|
set->fdset = NULL;
|
|
set->capa = 0;
|
|
}
|
|
|
|
void
|
|
rb_fd_set(int fd, rb_fdset_t *set)
|
|
{
|
|
unsigned int i;
|
|
SOCKET s = rb_w32_get_osfhandle(fd);
|
|
|
|
for (i = 0; i < set->fdset->fd_count; i++) {
|
|
if (set->fdset->fd_array[i] == s) {
|
|
return;
|
|
}
|
|
}
|
|
if (set->fdset->fd_count >= (unsigned)set->capa) {
|
|
set->capa = (set->fdset->fd_count / FD_SETSIZE + 1) * FD_SETSIZE;
|
|
set->fdset = xrealloc(set->fdset, sizeof(unsigned int) + sizeof(SOCKET) * set->capa);
|
|
}
|
|
set->fdset->fd_array[set->fdset->fd_count++] = s;
|
|
}
|
|
|
|
#undef FD_ZERO
|
|
#undef FD_SET
|
|
#undef FD_CLR
|
|
#undef FD_ISSET
|
|
|
|
#define FD_ZERO(f) rb_fd_zero(f)
|
|
#define FD_SET(i, f) rb_fd_set(i, f)
|
|
#define FD_CLR(i, f) rb_fd_clr(i, f)
|
|
#define FD_ISSET(i, f) rb_fd_isset(i, f)
|
|
|
|
#endif
|
|
|
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
|
static long
|
|
cmp_tv(const struct timeval *a, const struct timeval *b)
|
|
{
|
|
long d = (a->tv_sec - b->tv_sec);
|
|
return (d != 0) ? d : (a->tv_usec - b->tv_usec);
|
|
}
|
|
|
|
static int
|
|
subtract_tv(struct timeval *rest, const struct timeval *wait)
|
|
{
|
|
while (rest->tv_usec < wait->tv_usec) {
|
|
if (rest->tv_sec <= wait->tv_sec) {
|
|
return 0;
|
|
}
|
|
rest->tv_sec -= 1;
|
|
rest->tv_usec += 1000 * 1000;
|
|
}
|
|
rest->tv_sec -= wait->tv_sec;
|
|
rest->tv_usec -= wait->tv_usec;
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
do_select(int n, fd_set *read, fd_set *write, fd_set *except,
|
|
struct timeval *timeout)
|
|
{
|
|
int result, lerrno;
|
|
fd_set orig_read, orig_write, orig_except;
|
|
|
|
#ifndef linux
|
|
double limit = 0;
|
|
struct timeval wait_rest;
|
|
# if defined(__CYGWIN__) || defined(_WIN32)
|
|
struct timeval start_time;
|
|
# endif
|
|
|
|
if (timeout) {
|
|
# if defined(__CYGWIN__) || defined(_WIN32)
|
|
gettimeofday(&start_time, NULL);
|
|
limit = (double)start_time.tv_sec + (double)start_time.tv_usec*1e-6;
|
|
# else
|
|
limit = timeofday();
|
|
# endif
|
|
limit += (double)timeout->tv_sec+(double)timeout->tv_usec*1e-6;
|
|
wait_rest = *timeout;
|
|
timeout = &wait_rest;
|
|
}
|
|
#endif
|
|
|
|
if (read) orig_read = *read;
|
|
if (write) orig_write = *write;
|
|
if (except) orig_except = *except;
|
|
|
|
retry:
|
|
lerrno = 0;
|
|
|
|
#if defined(__CYGWIN__) || defined(_WIN32)
|
|
{
|
|
int finish = 0;
|
|
/* polling duration: 100ms */
|
|
struct timeval wait_100ms, *wait;
|
|
wait_100ms.tv_sec = 0;
|
|
wait_100ms.tv_usec = 100 * 1000; /* 100 ms */
|
|
|
|
do {
|
|
wait = (timeout == 0 || cmp_tv(&wait_100ms, timeout) > 0) ? &wait_100ms : timeout;
|
|
BLOCKING_REGION({
|
|
do {
|
|
result = select(n, read, write, except, wait);
|
|
if (result < 0) lerrno = errno;
|
|
if (result != 0) break;
|
|
|
|
if (read) *read = orig_read;
|
|
if (write) *write = orig_write;
|
|
if (except) *except = orig_except;
|
|
wait = &wait_100ms;
|
|
if (timeout) {
|
|
struct timeval elapsed;
|
|
gettimeofday(&elapsed, NULL);
|
|
subtract_tv(&elapsed, &start_time);
|
|
if (!subtract_tv(timeout, &elapsed)) {
|
|
finish = 1;
|
|
break;
|
|
}
|
|
if (cmp_tv(&wait_100ms, timeout) < 0) wait = timeout;
|
|
}
|
|
} while (__th->interrupt_flag == 0);
|
|
}, 0, 0);
|
|
} while (result == 0 && !finish);
|
|
}
|
|
#else
|
|
BLOCKING_REGION({
|
|
result = select(n, read, write, except, timeout);
|
|
if (result < 0) lerrno = errno;
|
|
}, ubf_select, GET_THREAD());
|
|
#endif
|
|
|
|
errno = lerrno;
|
|
|
|
if (result < 0) {
|
|
switch (errno) {
|
|
case EINTR:
|
|
#ifdef ERESTART
|
|
case ERESTART:
|
|
#endif
|
|
if (read) *read = orig_read;
|
|
if (write) *write = orig_write;
|
|
if (except) *except = orig_except;
|
|
#ifndef linux
|
|
if (timeout) {
|
|
double d = limit - timeofday();
|
|
|
|
wait_rest.tv_sec = (unsigned int)d;
|
|
wait_rest.tv_usec = (int)((d-(double)wait_rest.tv_sec)*1e6);
|
|
if (wait_rest.tv_sec < 0) wait_rest.tv_sec = 0;
|
|
if (wait_rest.tv_usec < 0) wait_rest.tv_usec = 0;
|
|
}
|
|
#endif
|
|
goto retry;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
rb_thread_wait_fd_rw(int fd, int read)
|
|
{
|
|
int result = 0;
|
|
thread_debug("rb_thread_wait_fd_rw(%d, %s)\n", fd, read ? "read" : "write");
|
|
|
|
if (fd < 0) {
|
|
rb_raise(rb_eIOError, "closed stream");
|
|
}
|
|
if (rb_thread_alone()) return;
|
|
while (result <= 0) {
|
|
rb_fdset_t set;
|
|
rb_fd_init(&set);
|
|
FD_SET(fd, &set);
|
|
|
|
if (read) {
|
|
result = do_select(fd + 1, rb_fd_ptr(&set), 0, 0, 0);
|
|
}
|
|
else {
|
|
result = do_select(fd + 1, 0, rb_fd_ptr(&set), 0, 0);
|
|
}
|
|
|
|
rb_fd_term(&set);
|
|
|
|
if (result < 0) {
|
|
rb_sys_fail(0);
|
|
}
|
|
}
|
|
|
|
thread_debug("rb_thread_wait_fd_rw(%d, %s): done\n", fd, read ? "read" : "write");
|
|
}
|
|
|
|
void
|
|
rb_thread_wait_fd(int fd)
|
|
{
|
|
rb_thread_wait_fd_rw(fd, 1);
|
|
}
|
|
|
|
int
|
|
rb_thread_fd_writable(int fd)
|
|
{
|
|
rb_thread_wait_fd_rw(fd, 0);
|
|
return Qtrue;
|
|
}
|
|
|
|
int
|
|
rb_thread_select(int max, fd_set * read, fd_set * write, fd_set * except,
|
|
struct timeval *timeout)
|
|
{
|
|
if (!read && !write && !except) {
|
|
if (!timeout) {
|
|
rb_thread_sleep_forever();
|
|
return 0;
|
|
}
|
|
rb_thread_wait_for(*timeout);
|
|
return 0;
|
|
}
|
|
else {
|
|
return do_select(max, read, write, except, timeout);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
rb_thread_fd_select(int max, rb_fdset_t * read, rb_fdset_t * write, rb_fdset_t * except,
|
|
struct timeval *timeout)
|
|
{
|
|
fd_set *r = NULL, *w = NULL, *e = NULL;
|
|
|
|
if (!read && !write && !except) {
|
|
if (!timeout) {
|
|
rb_thread_sleep_forever();
|
|
return 0;
|
|
}
|
|
rb_thread_wait_for(*timeout);
|
|
return 0;
|
|
}
|
|
|
|
if (read) {
|
|
rb_fd_resize(max - 1, read);
|
|
r = rb_fd_ptr(read);
|
|
}
|
|
if (write) {
|
|
rb_fd_resize(max - 1, write);
|
|
w = rb_fd_ptr(write);
|
|
}
|
|
if (except) {
|
|
rb_fd_resize(max - 1, except);
|
|
e = rb_fd_ptr(except);
|
|
}
|
|
return do_select(max, r, w, e, timeout);
|
|
}
|
|
|
|
|
|
/*
|
|
* for GC
|
|
*/
|
|
|
|
#ifdef USE_CONSERVATIVE_STACK_END
|
|
void
|
|
rb_gc_set_stack_end(VALUE **stack_end_p)
|
|
{
|
|
VALUE stack_end;
|
|
*stack_end_p = &stack_end;
|
|
}
|
|
#endif
|
|
|
|
void
|
|
rb_gc_save_machine_context(rb_thread_t *th)
|
|
{
|
|
SET_MACHINE_STACK_END(&th->machine_stack_end);
|
|
FLUSH_REGISTER_WINDOWS;
|
|
#ifdef __ia64
|
|
th->machine_register_stack_end = rb_ia64_bsp();
|
|
#endif
|
|
setjmp(th->machine_regs);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
int rb_get_next_signal(void);
|
|
|
|
static void
|
|
timer_thread_function(void *arg)
|
|
{
|
|
rb_vm_t *vm = GET_VM(); /* TODO: fix me for Multi-VM */
|
|
int sig;
|
|
rb_thread_t *mth;
|
|
|
|
/* for time slice */
|
|
RUBY_VM_SET_TIMER_INTERRUPT(vm->running_thread);
|
|
|
|
/* check signal */
|
|
mth = vm->main_thread;
|
|
if (!mth->exec_signal && (sig = rb_get_next_signal()) > 0) {
|
|
enum rb_thread_status prev_status = mth->status;
|
|
thread_debug("main_thread: %s, sig: %d\n",
|
|
thread_status_name(prev_status), sig);
|
|
mth->exec_signal = sig;
|
|
if (mth->status != THREAD_KILLED) mth->status = THREAD_RUNNABLE;
|
|
rb_threadptr_interrupt(mth);
|
|
mth->status = prev_status;
|
|
}
|
|
|
|
#if 0
|
|
/* prove profiler */
|
|
if (vm->prove_profile.enable) {
|
|
rb_thread_t *th = vm->running_thread;
|
|
|
|
if (vm->during_gc) {
|
|
/* GC prove profiling */
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
rb_thread_stop_timer_thread(void)
|
|
{
|
|
if (timer_thread_id && native_stop_timer_thread()) {
|
|
native_thread_join(timer_thread_id);
|
|
timer_thread_id = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_thread_reset_timer_thread(void)
|
|
{
|
|
timer_thread_id = 0;
|
|
}
|
|
|
|
void
|
|
rb_thread_start_timer_thread(void)
|
|
{
|
|
system_working = 1;
|
|
rb_thread_create_timer_thread();
|
|
}
|
|
|
|
static int
|
|
clear_coverage_i(st_data_t key, st_data_t val, st_data_t dummy)
|
|
{
|
|
int i;
|
|
VALUE lines = (VALUE)val;
|
|
|
|
for (i = 0; i < RARRAY_LEN(lines); i++) {
|
|
if (RARRAY_PTR(lines)[i] != Qnil) {
|
|
RARRAY_PTR(lines)[i] = INT2FIX(0);
|
|
}
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
clear_coverage(void)
|
|
{
|
|
extern VALUE rb_get_coverages(void);
|
|
VALUE coverages = rb_get_coverages();
|
|
if (RTEST(coverages)) {
|
|
st_foreach(RHASH_TBL(coverages), clear_coverage_i, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_thread_atfork_internal(int (*atfork)(st_data_t, st_data_t, st_data_t))
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
rb_vm_t *vm = th->vm;
|
|
VALUE thval = th->self;
|
|
vm->main_thread = th;
|
|
|
|
st_foreach(vm->living_threads, atfork, (st_data_t)th);
|
|
st_clear(vm->living_threads);
|
|
st_insert(vm->living_threads, thval, (st_data_t)th->thread_id);
|
|
vm->sleeper = 0;
|
|
clear_coverage();
|
|
}
|
|
|
|
static int
|
|
terminate_atfork_i(st_data_t key, st_data_t val, st_data_t current_th)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
if (th != (rb_thread_t *)current_th) {
|
|
thread_cleanup_func(th);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_thread_atfork(void)
|
|
{
|
|
rb_thread_atfork_internal(terminate_atfork_i);
|
|
rb_reset_random_seed();
|
|
}
|
|
|
|
static int
|
|
terminate_atfork_before_exec_i(st_data_t key, st_data_t val, st_data_t current_th)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
if (th != (rb_thread_t *)current_th) {
|
|
thread_cleanup_func_before_exec(th);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_thread_atfork_before_exec(void)
|
|
{
|
|
rb_thread_atfork_internal(terminate_atfork_before_exec_i);
|
|
}
|
|
|
|
struct thgroup {
|
|
int enclosed;
|
|
VALUE group;
|
|
};
|
|
|
|
/*
|
|
* Document-class: ThreadGroup
|
|
*
|
|
* <code>ThreadGroup</code> provides a means of keeping track of a number of
|
|
* threads as a group. A <code>Thread</code> can belong to only one
|
|
* <code>ThreadGroup</code> at a time; adding a thread to a new group will
|
|
* remove it from any previous group.
|
|
*
|
|
* Newly created threads belong to the same group as the thread from which they
|
|
* were created.
|
|
*/
|
|
|
|
static VALUE thgroup_s_alloc(VALUE);
|
|
static VALUE
|
|
thgroup_s_alloc(VALUE klass)
|
|
{
|
|
VALUE group;
|
|
struct thgroup *data;
|
|
|
|
group = Data_Make_Struct(klass, struct thgroup, 0, -1, data);
|
|
data->enclosed = 0;
|
|
data->group = group;
|
|
|
|
return group;
|
|
}
|
|
|
|
struct thgroup_list_params {
|
|
VALUE ary;
|
|
VALUE group;
|
|
};
|
|
|
|
static int
|
|
thgroup_list_i(st_data_t key, st_data_t val, st_data_t data)
|
|
{
|
|
VALUE thread = (VALUE)key;
|
|
VALUE ary = ((struct thgroup_list_params *)data)->ary;
|
|
VALUE group = ((struct thgroup_list_params *)data)->group;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (th->thgroup == group) {
|
|
rb_ary_push(ary, thread);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* thgrp.list => array
|
|
*
|
|
* Returns an array of all existing <code>Thread</code> objects that belong to
|
|
* this group.
|
|
*
|
|
* ThreadGroup::Default.list #=> [#<Thread:0x401bdf4c run>]
|
|
*/
|
|
|
|
static VALUE
|
|
thgroup_list(VALUE group)
|
|
{
|
|
VALUE ary = rb_ary_new();
|
|
struct thgroup_list_params param;
|
|
|
|
param.ary = ary;
|
|
param.group = group;
|
|
st_foreach(GET_THREAD()->vm->living_threads, thgroup_list_i, (st_data_t) & param);
|
|
return ary;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thgrp.enclose => thgrp
|
|
*
|
|
* Prevents threads from being added to or removed from the receiving
|
|
* <code>ThreadGroup</code>. New threads can still be started in an enclosed
|
|
* <code>ThreadGroup</code>.
|
|
*
|
|
* ThreadGroup::Default.enclose #=> #<ThreadGroup:0x4029d914>
|
|
* thr = Thread::new { Thread.stop } #=> #<Thread:0x402a7210 sleep>
|
|
* tg = ThreadGroup::new #=> #<ThreadGroup:0x402752d4>
|
|
* tg.add thr
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* ThreadError: can't move from the enclosed thread group
|
|
*/
|
|
|
|
static VALUE
|
|
thgroup_enclose(VALUE group)
|
|
{
|
|
struct thgroup *data;
|
|
|
|
Data_Get_Struct(group, struct thgroup, data);
|
|
data->enclosed = 1;
|
|
|
|
return group;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thgrp.enclosed? => true or false
|
|
*
|
|
* Returns <code>true</code> if <em>thgrp</em> is enclosed. See also
|
|
* ThreadGroup#enclose.
|
|
*/
|
|
|
|
static VALUE
|
|
thgroup_enclosed_p(VALUE group)
|
|
{
|
|
struct thgroup *data;
|
|
|
|
Data_Get_Struct(group, struct thgroup, data);
|
|
if (data->enclosed)
|
|
return Qtrue;
|
|
return Qfalse;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* thgrp.add(thread) => thgrp
|
|
*
|
|
* Adds the given <em>thread</em> to this group, removing it from any other
|
|
* group to which it may have previously belonged.
|
|
*
|
|
* puts "Initial group is #{ThreadGroup::Default.list}"
|
|
* tg = ThreadGroup.new
|
|
* t1 = Thread.new { sleep }
|
|
* t2 = Thread.new { sleep }
|
|
* puts "t1 is #{t1}"
|
|
* puts "t2 is #{t2}"
|
|
* tg.add(t1)
|
|
* puts "Initial group now #{ThreadGroup::Default.list}"
|
|
* puts "tg group now #{tg.list}"
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* Initial group is #<Thread:0x401bdf4c>
|
|
* t1 is #<Thread:0x401b3c90>
|
|
* t2 is #<Thread:0x401b3c18>
|
|
* Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c>
|
|
* tg group now #<Thread:0x401b3c90>
|
|
*/
|
|
|
|
static VALUE
|
|
thgroup_add(VALUE group, VALUE thread)
|
|
{
|
|
rb_thread_t *th;
|
|
struct thgroup *data;
|
|
|
|
rb_secure(4);
|
|
GetThreadPtr(thread, th);
|
|
|
|
if (OBJ_FROZEN(group)) {
|
|
rb_raise(rb_eThreadError, "can't move to the frozen thread group");
|
|
}
|
|
Data_Get_Struct(group, struct thgroup, data);
|
|
if (data->enclosed) {
|
|
rb_raise(rb_eThreadError, "can't move to the enclosed thread group");
|
|
}
|
|
|
|
if (!th->thgroup) {
|
|
return Qnil;
|
|
}
|
|
|
|
if (OBJ_FROZEN(th->thgroup)) {
|
|
rb_raise(rb_eThreadError, "can't move from the frozen thread group");
|
|
}
|
|
Data_Get_Struct(th->thgroup, struct thgroup, data);
|
|
if (data->enclosed) {
|
|
rb_raise(rb_eThreadError,
|
|
"can't move from the enclosed thread group");
|
|
}
|
|
|
|
th->thgroup = group;
|
|
return group;
|
|
}
|
|
|
|
|
|
/*
|
|
* Document-class: Mutex
|
|
*
|
|
* Mutex implements a simple semaphore that can be used to coordinate access to
|
|
* shared data from multiple concurrent threads.
|
|
*
|
|
* Example:
|
|
*
|
|
* require 'thread'
|
|
* semaphore = Mutex.new
|
|
*
|
|
* a = Thread.new {
|
|
* semaphore.synchronize {
|
|
* # access shared resource
|
|
* }
|
|
* }
|
|
*
|
|
* b = Thread.new {
|
|
* semaphore.synchronize {
|
|
* # access shared resource
|
|
* }
|
|
* }
|
|
*
|
|
*/
|
|
|
|
#define GetMutexPtr(obj, tobj) \
|
|
Data_Get_Struct(obj, mutex_t, tobj)
|
|
|
|
static const char *mutex_unlock(mutex_t *mutex, rb_thread_t volatile *th);
|
|
|
|
static void
|
|
mutex_free(void *ptr)
|
|
{
|
|
if (ptr) {
|
|
mutex_t *mutex = ptr;
|
|
if (mutex->th) {
|
|
/* rb_warn("free locked mutex"); */
|
|
const char *err = mutex_unlock(mutex, mutex->th);
|
|
if (err) rb_bug("%s", err);
|
|
}
|
|
native_mutex_destroy(&mutex->lock);
|
|
native_cond_destroy(&mutex->cond);
|
|
}
|
|
ruby_xfree(ptr);
|
|
}
|
|
|
|
static VALUE
|
|
mutex_alloc(VALUE klass)
|
|
{
|
|
VALUE volatile obj;
|
|
mutex_t *mutex;
|
|
|
|
obj = Data_Make_Struct(klass, mutex_t, NULL, mutex_free, mutex);
|
|
native_mutex_initialize(&mutex->lock);
|
|
native_cond_initialize(&mutex->cond);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Mutex.new => mutex
|
|
*
|
|
* Creates a new Mutex
|
|
*/
|
|
static VALUE
|
|
mutex_initialize(VALUE self)
|
|
{
|
|
return self;
|
|
}
|
|
|
|
VALUE
|
|
rb_mutex_new(void)
|
|
{
|
|
return mutex_alloc(rb_cMutex);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.locked? => true or false
|
|
*
|
|
* Returns +true+ if this lock is currently held by some thread.
|
|
*/
|
|
VALUE
|
|
rb_mutex_locked_p(VALUE self)
|
|
{
|
|
mutex_t *mutex;
|
|
GetMutexPtr(self, mutex);
|
|
return mutex->th ? Qtrue : Qfalse;
|
|
}
|
|
|
|
static void
|
|
mutex_locked(rb_thread_t *th, VALUE self)
|
|
{
|
|
mutex_t *mutex;
|
|
GetMutexPtr(self, mutex);
|
|
|
|
if (th->keeping_mutexes) {
|
|
mutex->next_mutex = th->keeping_mutexes;
|
|
}
|
|
th->keeping_mutexes = mutex;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.try_lock => true or false
|
|
*
|
|
* Attempts to obtain the lock and returns immediately. Returns +true+ if the
|
|
* lock was granted.
|
|
*/
|
|
VALUE
|
|
rb_mutex_trylock(VALUE self)
|
|
{
|
|
mutex_t *mutex;
|
|
VALUE locked = Qfalse;
|
|
GetMutexPtr(self, mutex);
|
|
|
|
native_mutex_lock(&mutex->lock);
|
|
if (mutex->th == 0) {
|
|
mutex->th = GET_THREAD();
|
|
locked = Qtrue;
|
|
|
|
mutex_locked(GET_THREAD(), self);
|
|
}
|
|
native_mutex_unlock(&mutex->lock);
|
|
|
|
return locked;
|
|
}
|
|
|
|
static int
|
|
lock_func(rb_thread_t *th, mutex_t *mutex, int last_thread)
|
|
{
|
|
int interrupted = 0;
|
|
#if 0 /* for debug */
|
|
native_thread_yield();
|
|
#endif
|
|
|
|
native_mutex_lock(&mutex->lock);
|
|
th->transition_for_lock = 0;
|
|
while (mutex->th || (mutex->th = th, 0)) {
|
|
if (last_thread) {
|
|
interrupted = 2;
|
|
break;
|
|
}
|
|
|
|
mutex->cond_waiting++;
|
|
native_cond_wait(&mutex->cond, &mutex->lock);
|
|
mutex->cond_notified--;
|
|
|
|
if (RUBY_VM_INTERRUPTED(th)) {
|
|
interrupted = 1;
|
|
break;
|
|
}
|
|
}
|
|
th->transition_for_lock = 1;
|
|
native_mutex_unlock(&mutex->lock);
|
|
|
|
if (interrupted == 2) native_thread_yield();
|
|
#if 0 /* for debug */
|
|
native_thread_yield();
|
|
#endif
|
|
|
|
return interrupted;
|
|
}
|
|
|
|
static void
|
|
lock_interrupt(void *ptr)
|
|
{
|
|
mutex_t *mutex = (mutex_t *)ptr;
|
|
native_mutex_lock(&mutex->lock);
|
|
if (mutex->cond_waiting > 0) {
|
|
native_cond_broadcast(&mutex->cond);
|
|
mutex->cond_notified = mutex->cond_waiting;
|
|
mutex->cond_waiting = 0;
|
|
}
|
|
native_mutex_unlock(&mutex->lock);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.lock => true or false
|
|
*
|
|
* Attempts to grab the lock and waits if it isn't available.
|
|
* Raises +ThreadError+ if +mutex+ was locked by the current thread.
|
|
*/
|
|
VALUE
|
|
rb_mutex_lock(VALUE self)
|
|
{
|
|
|
|
if (rb_mutex_trylock(self) == Qfalse) {
|
|
mutex_t *mutex;
|
|
rb_thread_t *th = GET_THREAD();
|
|
GetMutexPtr(self, mutex);
|
|
|
|
if (mutex->th == GET_THREAD()) {
|
|
rb_raise(rb_eThreadError, "deadlock; recursive locking");
|
|
}
|
|
|
|
while (mutex->th != th) {
|
|
int interrupted;
|
|
enum rb_thread_status prev_status = th->status;
|
|
int last_thread = 0;
|
|
struct rb_unblock_callback oldubf;
|
|
|
|
set_unblock_function(th, lock_interrupt, mutex, &oldubf);
|
|
th->status = THREAD_STOPPED_FOREVER;
|
|
th->vm->sleeper++;
|
|
th->locking_mutex = self;
|
|
if (vm_living_thread_num(th->vm) == th->vm->sleeper) {
|
|
last_thread = 1;
|
|
}
|
|
|
|
th->transition_for_lock = 1;
|
|
BLOCKING_REGION_CORE({
|
|
interrupted = lock_func(th, mutex, last_thread);
|
|
});
|
|
th->transition_for_lock = 0;
|
|
remove_signal_thread_list(th);
|
|
reset_unblock_function(th, &oldubf);
|
|
|
|
th->locking_mutex = Qfalse;
|
|
if (mutex->th && interrupted == 2) {
|
|
rb_check_deadlock(th->vm);
|
|
}
|
|
if (th->status == THREAD_STOPPED_FOREVER) {
|
|
th->status = prev_status;
|
|
}
|
|
th->vm->sleeper--;
|
|
|
|
if (mutex->th == th) mutex_locked(th, self);
|
|
|
|
if (interrupted) {
|
|
RUBY_VM_CHECK_INTS();
|
|
}
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static const char *
|
|
mutex_unlock(mutex_t *mutex, rb_thread_t volatile *th)
|
|
{
|
|
const char *err = NULL;
|
|
mutex_t *th_mutex;
|
|
|
|
native_mutex_lock(&mutex->lock);
|
|
|
|
if (mutex->th == 0) {
|
|
err = "Attempt to unlock a mutex which is not locked";
|
|
}
|
|
else if (mutex->th != th) {
|
|
err = "Attempt to unlock a mutex which is locked by another thread";
|
|
}
|
|
else {
|
|
mutex->th = 0;
|
|
if (mutex->cond_waiting > 0) {
|
|
/* waiting thread */
|
|
native_cond_signal(&mutex->cond);
|
|
mutex->cond_waiting--;
|
|
mutex->cond_notified++;
|
|
}
|
|
}
|
|
|
|
native_mutex_unlock(&mutex->lock);
|
|
|
|
if (!err) {
|
|
th_mutex = th->keeping_mutexes;
|
|
if (th_mutex == mutex) {
|
|
th->keeping_mutexes = mutex->next_mutex;
|
|
}
|
|
else {
|
|
while (1) {
|
|
mutex_t *tmp_mutex;
|
|
tmp_mutex = th_mutex->next_mutex;
|
|
if (tmp_mutex == mutex) {
|
|
th_mutex->next_mutex = tmp_mutex->next_mutex;
|
|
break;
|
|
}
|
|
th_mutex = tmp_mutex;
|
|
}
|
|
}
|
|
mutex->next_mutex = NULL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.unlock => self
|
|
*
|
|
* Releases the lock.
|
|
* Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
|
|
*/
|
|
VALUE
|
|
rb_mutex_unlock(VALUE self)
|
|
{
|
|
const char *err;
|
|
mutex_t *mutex;
|
|
GetMutexPtr(self, mutex);
|
|
|
|
err = mutex_unlock(mutex, GET_THREAD());
|
|
if (err) rb_raise(rb_eThreadError, "%s", err);
|
|
|
|
return self;
|
|
}
|
|
|
|
static void
|
|
rb_mutex_unlock_all(mutex_t *mutexes, rb_thread_t *th)
|
|
{
|
|
const char *err;
|
|
mutex_t *mutex;
|
|
|
|
while (mutexes) {
|
|
mutex = mutexes;
|
|
/* rb_warn("mutex #<%p> remains to be locked by terminated thread",
|
|
mutexes); */
|
|
mutexes = mutex->next_mutex;
|
|
err = mutex_unlock(mutex, th);
|
|
if (err) rb_bug("invalid keeping_mutexes: %s", err);
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
rb_mutex_sleep_forever(VALUE time)
|
|
{
|
|
rb_thread_sleep_deadly();
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
rb_mutex_wait_for(VALUE time)
|
|
{
|
|
const struct timeval *t = (struct timeval *)time;
|
|
rb_thread_wait_for(*t);
|
|
return Qnil;
|
|
}
|
|
|
|
VALUE
|
|
rb_mutex_sleep(VALUE self, VALUE timeout)
|
|
{
|
|
time_t beg, end;
|
|
struct timeval t;
|
|
|
|
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, rb_mutex_lock, self);
|
|
}
|
|
else {
|
|
rb_ensure(rb_mutex_wait_for, (VALUE)&t, rb_mutex_lock, self);
|
|
}
|
|
end = time(0) - beg;
|
|
return INT2FIX(end);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.sleep(timeout = nil) => number
|
|
*
|
|
* Releases the lock and sleeps +timeout+ seconds if it is given and
|
|
* non-nil or forever. Raises +ThreadError+ if +mutex+ wasn't locked by
|
|
* the current thread.
|
|
*/
|
|
static VALUE
|
|
mutex_sleep(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
VALUE timeout;
|
|
|
|
rb_scan_args(argc, argv, "01", &timeout);
|
|
return rb_mutex_sleep(self, timeout);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mutex.synchronize { ... } => result of the block
|
|
*
|
|
* Obtains a lock, runs the block, and releases the lock when the block
|
|
* completes. See the example under +Mutex+.
|
|
*/
|
|
|
|
VALUE
|
|
rb_mutex_synchronize(VALUE mutex, VALUE (*func)(VALUE arg), VALUE arg)
|
|
{
|
|
rb_mutex_lock(mutex);
|
|
return rb_ensure(func, arg, rb_mutex_unlock, mutex);
|
|
}
|
|
|
|
/*
|
|
* Document-class: Barrier
|
|
*/
|
|
static VALUE
|
|
barrier_alloc(VALUE klass)
|
|
{
|
|
return Data_Wrap_Struct(klass, rb_gc_mark, 0, (void *)mutex_alloc(0));
|
|
}
|
|
|
|
VALUE
|
|
rb_barrier_new(void)
|
|
{
|
|
VALUE barrier = barrier_alloc(rb_cBarrier);
|
|
rb_mutex_lock((VALUE)DATA_PTR(barrier));
|
|
return barrier;
|
|
}
|
|
|
|
VALUE
|
|
rb_barrier_wait(VALUE self)
|
|
{
|
|
VALUE mutex = (VALUE)DATA_PTR(self);
|
|
mutex_t *m;
|
|
|
|
if (!mutex) return Qfalse;
|
|
GetMutexPtr(mutex, m);
|
|
if (m->th == GET_THREAD()) return Qfalse;
|
|
rb_mutex_lock(mutex);
|
|
if (DATA_PTR(self)) return Qtrue;
|
|
rb_mutex_unlock(mutex);
|
|
return Qfalse;
|
|
}
|
|
|
|
VALUE
|
|
rb_barrier_release(VALUE self)
|
|
{
|
|
return rb_mutex_unlock((VALUE)DATA_PTR(self));
|
|
}
|
|
|
|
VALUE
|
|
rb_barrier_destroy(VALUE self)
|
|
{
|
|
VALUE mutex = (VALUE)DATA_PTR(self);
|
|
DATA_PTR(self) = 0;
|
|
return rb_mutex_unlock(mutex);
|
|
}
|
|
|
|
/* variables for recursive traversals */
|
|
static ID recursive_key;
|
|
|
|
static VALUE
|
|
recursive_check(VALUE hash, VALUE obj, VALUE paired_obj)
|
|
{
|
|
if (NIL_P(hash) || TYPE(hash) != T_HASH) {
|
|
return Qfalse;
|
|
}
|
|
else {
|
|
VALUE sym = ID2SYM(rb_frame_this_func());
|
|
VALUE list = rb_hash_aref(hash, sym);
|
|
VALUE pair_list;
|
|
|
|
if (NIL_P(list) || TYPE(list) != T_HASH)
|
|
return Qfalse;
|
|
pair_list = rb_hash_lookup2(list, obj, Qundef);
|
|
if (pair_list == Qundef)
|
|
return Qfalse;
|
|
if (paired_obj) {
|
|
if (TYPE(pair_list) != T_HASH) {
|
|
if (pair_list != paired_obj)
|
|
return Qfalse;
|
|
}
|
|
else {
|
|
if (NIL_P(rb_hash_lookup(pair_list, paired_obj)))
|
|
return Qfalse;
|
|
}
|
|
}
|
|
return Qtrue;
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
recursive_push(VALUE hash, VALUE obj, VALUE paired_obj)
|
|
{
|
|
VALUE list, sym, pair_list;
|
|
|
|
sym = ID2SYM(rb_frame_this_func());
|
|
if (NIL_P(hash) || TYPE(hash) != T_HASH) {
|
|
hash = rb_hash_new();
|
|
rb_thread_local_aset(rb_thread_current(), recursive_key, hash);
|
|
list = Qnil;
|
|
}
|
|
else {
|
|
list = rb_hash_aref(hash, sym);
|
|
}
|
|
if (NIL_P(list) || TYPE(list) != T_HASH) {
|
|
list = rb_hash_new();
|
|
rb_hash_aset(hash, sym, list);
|
|
}
|
|
if (!paired_obj) {
|
|
rb_hash_aset(list, obj, Qtrue);
|
|
}
|
|
else if ((pair_list = rb_hash_lookup2(list, obj, Qundef)) == Qundef) {
|
|
rb_hash_aset(list, obj, paired_obj);
|
|
}
|
|
else {
|
|
if (TYPE(pair_list) != T_HASH){
|
|
VALUE other_paired_obj = pair_list;
|
|
pair_list = rb_hash_new();
|
|
rb_hash_aset(pair_list, other_paired_obj, Qtrue);
|
|
rb_hash_aset(list, obj, pair_list);
|
|
}
|
|
rb_hash_aset(pair_list, paired_obj, Qtrue);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static void
|
|
recursive_pop(VALUE hash, VALUE obj, VALUE paired_obj)
|
|
{
|
|
VALUE list, sym, pair_list, symname, thrname;
|
|
|
|
sym = ID2SYM(rb_frame_this_func());
|
|
if (NIL_P(hash) || TYPE(hash) != T_HASH) {
|
|
symname = rb_inspect(sym);
|
|
thrname = rb_inspect(rb_thread_current());
|
|
rb_raise(rb_eTypeError, "invalid inspect_tbl hash for %s in %s",
|
|
StringValuePtr(symname), StringValuePtr(thrname));
|
|
}
|
|
list = rb_hash_aref(hash, sym);
|
|
if (NIL_P(list) || TYPE(list) != T_HASH) {
|
|
symname = rb_inspect(sym);
|
|
thrname = rb_inspect(rb_thread_current());
|
|
rb_raise(rb_eTypeError, "invalid inspect_tbl list for %s in %s",
|
|
StringValuePtr(symname), StringValuePtr(thrname));
|
|
}
|
|
if (paired_obj) {
|
|
pair_list = rb_hash_lookup2(list, obj, Qundef);
|
|
if (pair_list == Qundef) {
|
|
symname = rb_inspect(sym);
|
|
thrname = rb_inspect(rb_thread_current());
|
|
rb_raise(rb_eTypeError, "invalid inspect_tbl pair_list for %s in %s",
|
|
StringValuePtr(symname), StringValuePtr(thrname));
|
|
}
|
|
if (TYPE(pair_list) == T_HASH) {
|
|
rb_hash_delete(pair_list, paired_obj);
|
|
if (!RHASH_EMPTY_P(pair_list)) {
|
|
return; /* keep hash until is empty */
|
|
}
|
|
}
|
|
}
|
|
rb_hash_delete(list, obj);
|
|
}
|
|
|
|
static VALUE
|
|
exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE pairid, VALUE arg)
|
|
{
|
|
volatile VALUE hash = rb_thread_local_aref(rb_thread_current(), recursive_key);
|
|
VALUE objid = rb_obj_id(obj);
|
|
|
|
if (recursive_check(hash, objid, pairid)) {
|
|
return (*func) (obj, arg, Qtrue);
|
|
}
|
|
else {
|
|
VALUE result = Qundef;
|
|
int state;
|
|
|
|
hash = recursive_push(hash, objid, pairid);
|
|
PUSH_TAG();
|
|
if ((state = EXEC_TAG()) == 0) {
|
|
result = (*func) (obj, arg, Qfalse);
|
|
}
|
|
POP_TAG();
|
|
recursive_pop(hash, objid, pairid);
|
|
if (state)
|
|
JUMP_TAG(state);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg)
|
|
{
|
|
return exec_recursive(func, obj, 0, arg);
|
|
}
|
|
|
|
VALUE
|
|
rb_exec_recursive_paired(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE paired_obj, VALUE arg)
|
|
{
|
|
return exec_recursive(func, obj, rb_obj_id(paired_obj), arg);
|
|
}
|
|
|
|
/* tracer */
|
|
|
|
static rb_event_hook_t *
|
|
alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
|
{
|
|
rb_event_hook_t *hook = ALLOC(rb_event_hook_t);
|
|
hook->func = func;
|
|
hook->flag = events;
|
|
hook->data = data;
|
|
return hook;
|
|
}
|
|
|
|
static void
|
|
thread_reset_event_flags(rb_thread_t *th)
|
|
{
|
|
rb_event_hook_t *hook = th->event_hooks;
|
|
rb_event_flag_t flag = th->event_flags & RUBY_EVENT_VM;
|
|
|
|
while (hook) {
|
|
flag |= hook->flag;
|
|
hook = hook->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_threadptr_add_event_hook(rb_thread_t *th,
|
|
rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
|
{
|
|
rb_event_hook_t *hook = alloc_event_hook(func, events, data);
|
|
hook->next = th->event_hooks;
|
|
th->event_hooks = hook;
|
|
thread_reset_event_flags(th);
|
|
}
|
|
|
|
static rb_thread_t *
|
|
thval2thread_t(VALUE thval)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
return th;
|
|
}
|
|
|
|
void
|
|
rb_thread_add_event_hook(VALUE thval,
|
|
rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
|
{
|
|
rb_threadptr_add_event_hook(thval2thread_t(thval), func, events, data);
|
|
}
|
|
|
|
static int
|
|
set_threads_event_flags_i(st_data_t key, st_data_t val, st_data_t flag)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
if (flag) {
|
|
th->event_flags |= RUBY_EVENT_VM;
|
|
}
|
|
else {
|
|
th->event_flags &= (~RUBY_EVENT_VM);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
set_threads_event_flags(int flag)
|
|
{
|
|
st_foreach(GET_VM()->living_threads, set_threads_event_flags_i, (st_data_t) flag);
|
|
}
|
|
|
|
void
|
|
rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
|
|
{
|
|
rb_event_hook_t *hook = alloc_event_hook(func, events, data);
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
hook->next = vm->event_hooks;
|
|
vm->event_hooks = hook;
|
|
|
|
set_threads_event_flags(1);
|
|
}
|
|
|
|
static int
|
|
remove_event_hook(rb_event_hook_t **root, rb_event_hook_func_t func)
|
|
{
|
|
rb_event_hook_t *prev = NULL, *hook = *root, *next;
|
|
|
|
while (hook) {
|
|
next = hook->next;
|
|
if (func == 0 || hook->func == func) {
|
|
if (prev) {
|
|
prev->next = hook->next;
|
|
}
|
|
else {
|
|
*root = hook->next;
|
|
}
|
|
xfree(hook);
|
|
}
|
|
else {
|
|
prev = hook;
|
|
}
|
|
hook = next;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
rb_threadptr_revmove_event_hook(rb_thread_t *th, rb_event_hook_func_t func)
|
|
{
|
|
int ret = remove_event_hook(&th->event_hooks, func);
|
|
thread_reset_event_flags(th);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
rb_thread_remove_event_hook(VALUE thval, rb_event_hook_func_t func)
|
|
{
|
|
return rb_threadptr_revmove_event_hook(thval2thread_t(thval), func);
|
|
}
|
|
|
|
int
|
|
rb_remove_event_hook(rb_event_hook_func_t func)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
rb_event_hook_t *hook = vm->event_hooks;
|
|
int ret = remove_event_hook(&vm->event_hooks, func);
|
|
|
|
if (hook != NULL && vm->event_hooks == NULL) {
|
|
set_threads_event_flags(0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
clear_trace_func_i(st_data_t key, st_data_t val, st_data_t flag)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr((VALUE)key, th);
|
|
rb_threadptr_revmove_event_hook(th, 0);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_clear_trace_func(void)
|
|
{
|
|
st_foreach(GET_VM()->living_threads, clear_trace_func_i, (st_data_t) 0);
|
|
rb_remove_event_hook(0);
|
|
}
|
|
|
|
static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass);
|
|
|
|
/*
|
|
* call-seq:
|
|
* set_trace_func(proc) => proc
|
|
* set_trace_func(nil) => nil
|
|
*
|
|
* Establishes _proc_ as the handler for tracing, or disables
|
|
* tracing if the parameter is +nil+. _proc_ takes up
|
|
* to six parameters: an event name, a filename, a line number, an
|
|
* object id, a binding, and the name of a class. _proc_ is
|
|
* invoked whenever an event occurs. Events are: <code>c-call</code>
|
|
* (call a C-language routine), <code>c-return</code> (return from a
|
|
* C-language routine), <code>call</code> (call a Ruby method),
|
|
* <code>class</code> (start a class or module definition),
|
|
* <code>end</code> (finish a class or module definition),
|
|
* <code>line</code> (execute code on a new line), <code>raise</code>
|
|
* (raise an exception), and <code>return</code> (return from a Ruby
|
|
* method). Tracing is disabled within the context of _proc_.
|
|
*
|
|
* class Test
|
|
* def test
|
|
* a = 1
|
|
* b = 2
|
|
* end
|
|
* end
|
|
*
|
|
* set_trace_func proc { |event, file, line, id, binding, classname|
|
|
* printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
|
|
* }
|
|
* t = Test.new
|
|
* t.test
|
|
*
|
|
* line prog.rb:11 false
|
|
* c-call prog.rb:11 new Class
|
|
* c-call prog.rb:11 initialize Object
|
|
* c-return prog.rb:11 initialize Object
|
|
* c-return prog.rb:11 new Class
|
|
* line prog.rb:12 false
|
|
* call prog.rb:2 test Test
|
|
* line prog.rb:3 test Test
|
|
* line prog.rb:4 test Test
|
|
* return prog.rb:4 test Test
|
|
*/
|
|
|
|
static VALUE
|
|
set_trace_func(VALUE obj, VALUE trace)
|
|
{
|
|
rb_remove_event_hook(call_trace_func);
|
|
|
|
if (NIL_P(trace)) {
|
|
return Qnil;
|
|
}
|
|
|
|
if (!rb_obj_is_proc(trace)) {
|
|
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
|
|
}
|
|
|
|
rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace);
|
|
return trace;
|
|
}
|
|
|
|
static void
|
|
thread_add_trace_func(rb_thread_t *th, VALUE trace)
|
|
{
|
|
if (!rb_obj_is_proc(trace)) {
|
|
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
|
|
}
|
|
|
|
rb_threadptr_add_event_hook(th, call_trace_func, RUBY_EVENT_ALL, trace);
|
|
}
|
|
|
|
static VALUE
|
|
thread_add_trace_func_m(VALUE obj, VALUE trace)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(obj, th);
|
|
thread_add_trace_func(th, trace);
|
|
return trace;
|
|
}
|
|
|
|
static VALUE
|
|
thread_set_trace_func_m(VALUE obj, VALUE trace)
|
|
{
|
|
rb_thread_t *th;
|
|
GetThreadPtr(obj, th);
|
|
rb_threadptr_revmove_event_hook(th, call_trace_func);
|
|
|
|
if (NIL_P(trace)) {
|
|
return Qnil;
|
|
}
|
|
thread_add_trace_func(th, trace);
|
|
return trace;
|
|
}
|
|
|
|
static const char *
|
|
get_event_name(rb_event_flag_t event)
|
|
{
|
|
switch (event) {
|
|
case RUBY_EVENT_LINE:
|
|
return "line";
|
|
case RUBY_EVENT_CLASS:
|
|
return "class";
|
|
case RUBY_EVENT_END:
|
|
return "end";
|
|
case RUBY_EVENT_CALL:
|
|
return "call";
|
|
case RUBY_EVENT_RETURN:
|
|
return "return";
|
|
case RUBY_EVENT_C_CALL:
|
|
return "c-call";
|
|
case RUBY_EVENT_C_RETURN:
|
|
return "c-return";
|
|
case RUBY_EVENT_RAISE:
|
|
return "raise";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
VALUE ruby_suppress_tracing(VALUE (*func)(VALUE, int), VALUE arg, int always);
|
|
|
|
struct call_trace_func_args {
|
|
rb_event_flag_t event;
|
|
VALUE proc;
|
|
VALUE self;
|
|
ID id;
|
|
VALUE klass;
|
|
};
|
|
|
|
static VALUE
|
|
call_trace_proc(VALUE args, int tracing)
|
|
{
|
|
struct call_trace_func_args *p = (struct call_trace_func_args *)args;
|
|
const char *srcfile = rb_sourcefile();
|
|
VALUE eventname = rb_str_new2(get_event_name(p->event));
|
|
VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil;
|
|
VALUE argv[6];
|
|
int line = rb_sourceline();
|
|
ID id = 0;
|
|
VALUE klass = 0;
|
|
|
|
if (p->event == RUBY_EVENT_C_CALL ||
|
|
p->event == RUBY_EVENT_C_RETURN) {
|
|
id = p->id;
|
|
klass = p->klass;
|
|
}
|
|
else {
|
|
rb_thread_method_id_and_class(GET_THREAD(), &id, &klass);
|
|
}
|
|
if (id == ID_ALLOCATOR)
|
|
return Qnil;
|
|
if (klass) {
|
|
if (TYPE(klass) == T_ICLASS) {
|
|
klass = RBASIC(klass)->klass;
|
|
}
|
|
else if (FL_TEST(klass, FL_SINGLETON)) {
|
|
klass = rb_iv_get(klass, "__attached__");
|
|
}
|
|
}
|
|
|
|
argv[0] = eventname;
|
|
argv[1] = filename;
|
|
argv[2] = INT2FIX(line);
|
|
argv[3] = id ? ID2SYM(id) : Qnil;
|
|
argv[4] = (p->self && srcfile) ? rb_binding_new() : Qnil;
|
|
argv[5] = klass ? klass : Qnil;
|
|
|
|
return rb_proc_call_with_block(p->proc, 6, argv, Qnil);
|
|
}
|
|
|
|
static void
|
|
call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
|
{
|
|
struct call_trace_func_args args;
|
|
|
|
args.event = event;
|
|
args.proc = proc;
|
|
args.self = self;
|
|
args.id = id;
|
|
args.klass = klass;
|
|
ruby_suppress_tracing(call_trace_proc, (VALUE)&args, Qfalse);
|
|
}
|
|
|
|
VALUE
|
|
ruby_suppress_tracing(VALUE (*func)(VALUE, int), VALUE arg, int always)
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
int state, raised, tracing;
|
|
VALUE result = Qnil;
|
|
|
|
if ((tracing = th->tracing) != 0 && !always) {
|
|
return Qnil;
|
|
}
|
|
else {
|
|
th->tracing = 1;
|
|
}
|
|
|
|
raised = rb_threadptr_reset_raised(th);
|
|
|
|
PUSH_TAG();
|
|
if ((state = EXEC_TAG()) == 0) {
|
|
result = (*func)(arg, tracing);
|
|
}
|
|
|
|
if (raised) {
|
|
rb_threadptr_set_raised(th);
|
|
}
|
|
POP_TAG();
|
|
|
|
th->tracing = tracing;
|
|
if (state) {
|
|
JUMP_TAG(state);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
VALUE rb_thread_backtrace(VALUE thval);
|
|
|
|
static VALUE
|
|
rb_thread_backtrace_m(VALUE thval)
|
|
{
|
|
return rb_thread_backtrace(thval);
|
|
}
|
|
|
|
/*
|
|
* +Thread+ encapsulates the behavior of a thread of
|
|
* execution, including the main thread of the Ruby script.
|
|
*
|
|
* In the descriptions of the methods in this class, the parameter _sym_
|
|
* refers to a symbol, which is either a quoted string or a
|
|
* +Symbol+ (such as <code>:name</code>).
|
|
*/
|
|
|
|
void
|
|
Init_Thread(void)
|
|
{
|
|
#undef rb_intern
|
|
#define rb_intern(str) rb_intern_const(str)
|
|
|
|
VALUE cThGroup;
|
|
|
|
rb_define_singleton_method(rb_cThread, "new", thread_s_new, -1);
|
|
rb_define_singleton_method(rb_cThread, "start", thread_start, -2);
|
|
rb_define_singleton_method(rb_cThread, "fork", thread_start, -2);
|
|
rb_define_singleton_method(rb_cThread, "main", rb_thread_s_main, 0);
|
|
rb_define_singleton_method(rb_cThread, "current", thread_s_current, 0);
|
|
rb_define_singleton_method(rb_cThread, "stop", rb_thread_stop, 0);
|
|
rb_define_singleton_method(rb_cThread, "kill", rb_thread_s_kill, 1);
|
|
rb_define_singleton_method(rb_cThread, "exit", rb_thread_exit, 0);
|
|
rb_define_singleton_method(rb_cThread, "pass", thread_s_pass, 0);
|
|
rb_define_singleton_method(rb_cThread, "list", rb_thread_list, 0);
|
|
rb_define_singleton_method(rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0);
|
|
rb_define_singleton_method(rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1);
|
|
#if THREAD_DEBUG < 0
|
|
rb_define_singleton_method(rb_cThread, "DEBUG", rb_thread_s_debug, 0);
|
|
rb_define_singleton_method(rb_cThread, "DEBUG=", rb_thread_s_debug_set, 1);
|
|
#endif
|
|
|
|
rb_define_method(rb_cThread, "initialize", thread_initialize, -2);
|
|
rb_define_method(rb_cThread, "raise", thread_raise_m, -1);
|
|
rb_define_method(rb_cThread, "join", thread_join_m, -1);
|
|
rb_define_method(rb_cThread, "value", thread_value, 0);
|
|
rb_define_method(rb_cThread, "kill", rb_thread_kill, 0);
|
|
rb_define_method(rb_cThread, "terminate", rb_thread_kill, 0);
|
|
rb_define_method(rb_cThread, "exit", rb_thread_kill, 0);
|
|
rb_define_method(rb_cThread, "run", rb_thread_run, 0);
|
|
rb_define_method(rb_cThread, "wakeup", rb_thread_wakeup, 0);
|
|
rb_define_method(rb_cThread, "[]", rb_thread_aref, 1);
|
|
rb_define_method(rb_cThread, "[]=", rb_thread_aset, 2);
|
|
rb_define_method(rb_cThread, "key?", rb_thread_key_p, 1);
|
|
rb_define_method(rb_cThread, "keys", rb_thread_keys, 0);
|
|
rb_define_method(rb_cThread, "priority", rb_thread_priority, 0);
|
|
rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1);
|
|
rb_define_method(rb_cThread, "status", rb_thread_status, 0);
|
|
rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0);
|
|
rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0);
|
|
rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0);
|
|
rb_define_method(rb_cThread, "abort_on_exception=", rb_thread_abort_exc_set, 1);
|
|
rb_define_method(rb_cThread, "safe_level", rb_thread_safe_level, 0);
|
|
rb_define_method(rb_cThread, "group", rb_thread_group, 0);
|
|
rb_define_method(rb_cThread, "backtrace", rb_thread_backtrace_m, 0);
|
|
|
|
rb_define_method(rb_cThread, "inspect", rb_thread_inspect, 0);
|
|
|
|
cThGroup = rb_define_class("ThreadGroup", rb_cObject);
|
|
rb_define_alloc_func(cThGroup, thgroup_s_alloc);
|
|
rb_define_method(cThGroup, "list", thgroup_list, 0);
|
|
rb_define_method(cThGroup, "enclose", thgroup_enclose, 0);
|
|
rb_define_method(cThGroup, "enclosed?", thgroup_enclosed_p, 0);
|
|
rb_define_method(cThGroup, "add", thgroup_add, 1);
|
|
|
|
{
|
|
rb_thread_t *th = GET_THREAD();
|
|
th->thgroup = th->vm->thgroup_default = rb_obj_alloc(cThGroup);
|
|
rb_define_const(cThGroup, "Default", th->thgroup);
|
|
}
|
|
|
|
rb_cMutex = rb_define_class("Mutex", rb_cObject);
|
|
rb_define_alloc_func(rb_cMutex, mutex_alloc);
|
|
rb_define_method(rb_cMutex, "initialize", mutex_initialize, 0);
|
|
rb_define_method(rb_cMutex, "locked?", rb_mutex_locked_p, 0);
|
|
rb_define_method(rb_cMutex, "try_lock", rb_mutex_trylock, 0);
|
|
rb_define_method(rb_cMutex, "lock", rb_mutex_lock, 0);
|
|
rb_define_method(rb_cMutex, "unlock", rb_mutex_unlock, 0);
|
|
rb_define_method(rb_cMutex, "sleep", mutex_sleep, -1);
|
|
|
|
recursive_key = rb_intern("__recursive_key__");
|
|
rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError);
|
|
|
|
/* trace */
|
|
rb_define_global_function("set_trace_func", set_trace_func, 1);
|
|
rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1);
|
|
rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1);
|
|
|
|
/* init thread core */
|
|
Init_native_thread();
|
|
{
|
|
/* main thread setting */
|
|
{
|
|
/* acquire global vm lock */
|
|
rb_thread_lock_t *lp = &GET_THREAD()->vm->global_vm_lock;
|
|
native_mutex_initialize(lp);
|
|
native_mutex_lock(lp);
|
|
native_mutex_initialize(&GET_THREAD()->interrupt_lock);
|
|
}
|
|
}
|
|
|
|
rb_thread_create_timer_thread();
|
|
|
|
(void)native_mutex_trylock;
|
|
(void)ruby_thread_set_native;
|
|
}
|
|
|
|
int
|
|
ruby_native_thread_p(void)
|
|
{
|
|
rb_thread_t *th = ruby_thread_from_native();
|
|
|
|
return th ? Qtrue : Qfalse;
|
|
}
|
|
|
|
static int
|
|
check_deadlock_i(st_data_t key, st_data_t val, int *found)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
if (th->status != THREAD_STOPPED_FOREVER || RUBY_VM_INTERRUPTED(th) || th->transition_for_lock) {
|
|
*found = 1;
|
|
}
|
|
else if (th->locking_mutex) {
|
|
mutex_t *mutex;
|
|
GetMutexPtr(th->locking_mutex, mutex);
|
|
|
|
native_mutex_lock(&mutex->lock);
|
|
if (mutex->th == th || (!mutex->th && mutex->cond_notified)) {
|
|
*found = 1;
|
|
}
|
|
native_mutex_unlock(&mutex->lock);
|
|
}
|
|
|
|
return (*found) ? ST_STOP : ST_CONTINUE;
|
|
}
|
|
|
|
#if 0 /* for debug */
|
|
static int
|
|
debug_i(st_data_t key, st_data_t val, int *found)
|
|
{
|
|
VALUE thval = key;
|
|
rb_thread_t *th;
|
|
GetThreadPtr(thval, th);
|
|
|
|
printf("th:%p %d %d %d", th, th->status, th->interrupt_flag, th->transition_for_lock);
|
|
if (th->locking_mutex) {
|
|
mutex_t *mutex;
|
|
GetMutexPtr(th->locking_mutex, mutex);
|
|
|
|
native_mutex_lock(&mutex->lock);
|
|
printf(" %p %d\n", mutex->th, mutex->cond_notified);
|
|
native_mutex_unlock(&mutex->lock);
|
|
}
|
|
else puts("");
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
rb_check_deadlock(rb_vm_t *vm)
|
|
{
|
|
int found = 0;
|
|
|
|
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)");
|
|
|
|
st_foreach(vm->living_threads, check_deadlock_i, (st_data_t)&found);
|
|
|
|
if (!found) {
|
|
VALUE argv[2];
|
|
argv[0] = rb_eFatal;
|
|
argv[1] = rb_str_new2("deadlock detected");
|
|
#if 0 /* for debug */
|
|
printf("%d %d %p %p\n", vm->living_threads->num_entries, vm->sleeper, GET_THREAD(), vm->main_thread);
|
|
st_foreach(vm->living_threads, debug_i, (st_data_t)0);
|
|
#endif
|
|
rb_threadptr_raise(vm->main_thread, 2, argv);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_coverage(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
|
|
{
|
|
VALUE coverage = GET_THREAD()->cfp->iseq->coverage;
|
|
if (coverage && RBASIC(coverage)->klass == 0) {
|
|
long line = rb_sourceline() - 1;
|
|
long count;
|
|
if (RARRAY_PTR(coverage)[line] == Qnil) {
|
|
rb_bug("bug");
|
|
}
|
|
count = FIX2LONG(RARRAY_PTR(coverage)[line]) + 1;
|
|
if (POSFIXABLE(count)) {
|
|
RARRAY_PTR(coverage)[line] = LONG2FIX(count);
|
|
}
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_get_coverages(void)
|
|
{
|
|
return GET_VM()->coverages;
|
|
}
|
|
|
|
void
|
|
rb_set_coverages(VALUE coverages)
|
|
{
|
|
GET_VM()->coverages = coverages;
|
|
rb_add_event_hook(update_coverage, RUBY_EVENT_COVERAGE, Qnil);
|
|
}
|
|
|
|
void
|
|
rb_reset_coverages(void)
|
|
{
|
|
GET_VM()->coverages = Qfalse;
|
|
rb_remove_event_hook(update_coverage);
|
|
}
|