diff options
Diffstat (limited to 'thread.c')
-rw-r--r-- | thread.c | 2036 |
1 files changed, 2036 insertions, 0 deletions
diff --git a/thread.c b/thread.c new file mode 100644 index 0000000000..5e0c92d44f --- /dev/null +++ b/thread.c @@ -0,0 +1,2036 @@ +/********************************************************************** + + thread.c - + + $Author$ + $Date$ + + Copyright (C) 2004-2006 Koichi Sasada + +**********************************************************************/ + +/* + YARV Thread Desgin + + model 1: Userlevel Thread + Same as traditional ruby thread. + + model 2: Native Thread with Giant 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) 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 (YARV_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 "vm.h" + +#define THREAD_DEBUG 0 + +static void sleep_for_polling(); +static void sleep_timeval(yarv_thread_t *th, struct timeval time); +static void sleep_wait_for_interrupt(yarv_thread_t *th, double sleepsec); +static void sleep_forever(yarv_thread_t *th); +static double timeofday(); +struct timeval rb_time_interval(VALUE); +static int rb_thread_dead(yarv_thread_t *th); + +void rb_signal_exec(yarv_thread_t *th, int sig); +void rb_disable_interrupt(); + +NOINLINE(void yarv_set_stack_end(VALUE **stack_end_p)); + +static VALUE eKillSignal = INT2FIX(0); +static VALUE eTerminateSignal = INT2FIX(1); +static int system_working = 1; + +inline static void +st_delete_wrap(st_table * table, VALUE key) +{ + st_delete(table, (st_data_t *) & key, 0); +} + +/********************************************************************************/ + +#define THREAD_SYSTEM_DEPENDENT_IMPLEMENTATION + +static void native_thread_interrupt(yarv_thread_t *th); +static void yarv_set_interrupt_function(yarv_thread_t *th, yarv_interrupt_function_t *func, int is_return); +static void yarv_clear_interrupt_function(yarv_thread_t *th); + +#define GVL_UNLOCK_RANGE(exec) do { \ + yarv_thread_t *__th = GET_THREAD(); \ + int __prev_status = __th->status; \ + yarv_set_interrupt_function(__th, native_thread_interrupt, 0); \ + __th->status = THREAD_STOPPED; \ + GVL_UNLOCK_BEGIN(); {\ + exec; \ + } \ + GVL_UNLOCK_END(); \ + yarv_remove_signal_thread_list(__th); \ + yarv_clear_interrupt_function(__th); \ + if (__th->status == THREAD_STOPPED) { \ + __th->status = __prev_status; \ + } \ + YARV_CHECK_INTS(); \ +} while(0) + +#if THREAD_DEBUG +void thread_debug(const char *fmt, ...); +#else +#define thread_debug if(0)printf +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +#include "thread_win32.ci" + +#define DEBUG_OUT() \ + WaitForSingleObject(&debug_mutex, INFINITE); \ + printf("%8p - %s", GetCurrentThreadId(), buf); \ + ReleaseMutex(&debug_mutex); + +#elif defined(HAVE_PTHREAD_H) +#include "thread_pthread.ci" + +#define DEBUG_OUT() \ + pthread_mutex_lock(&debug_mutex); \ + printf("%8p - %s", pthread_self(), buf); \ + pthread_mutex_unlock(&debug_mutex); + +#else +#error "unsupported thread type" +#endif + +#if THREAD_DEBUG +static int debug_mutex_initialized = 1; +static yarv_thread_lock_t debug_mutex; + +void +thread_debug(const char *fmt, ...) +{ + va_list args; + char buf[BUFSIZ]; + + 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 +yarv_set_interrupt_function(yarv_thread_t *th, yarv_interrupt_function_t *func, int is_return) +{ + check_ints: + YARV_CHECK_INTS(); + native_mutex_lock(&th->interrupt_lock); + if (th->interrupt_flag) { + native_mutex_unlock(&th->interrupt_lock); + if (is_return) { + return; + } + else { + goto check_ints; + } + } + else { + th->interrupt_function = func; + } + native_mutex_unlock(&th->interrupt_lock); +} + +static void +yarv_clear_interrupt_function(yarv_thread_t *th) +{ + native_mutex_lock(&th->interrupt_lock); + th->interrupt_function = 0; + native_mutex_unlock(&th->interrupt_lock); +} + +static void +rb_thread_interrupt(yarv_thread_t *th) +{ + native_mutex_lock(&th->interrupt_lock); + th->interrupt_flag = 1; + + if (th->interrupt_function) { + (th->interrupt_function)(th); + } + else { + /* none */ + } + native_mutex_unlock(&th->interrupt_lock); +} + + +static int +terminate_i(st_data_t key, st_data_t val, yarv_thread_t *main_thread) +{ + VALUE thval = key; + yarv_thread_t *th; + GetThreadPtr(thval, th); + + if (th != main_thread) { + thread_debug("terminate_i: %p\n", th); + rb_thread_interrupt(th); + th->throwed_errinfo = eTerminateSignal; + th->status = THREAD_TO_KILL; + } + else { + thread_debug("terminate_i: main thread (%p)\n", th); + } + return ST_CONTINUE; +} + +void +rb_thread_terminate_all(void) +{ + yarv_thread_t *th = GET_THREAD(); /* main thread */ + yarv_vm_t *vm = th->vm; + if (vm->main_thread != th) { + rb_bug("rb_thread_terminate_all: called by child thread (%p, %p)", vm->main_thread, th); + } + + thread_debug("rb_thread_terminate_all (main thread: %p)\n", th); + st_foreach(vm->living_threads, terminate_i, (st_data_t)th); + + while (!rb_thread_alone()) { + rb_thread_schedule(); + } + system_working = 0; +} + + +VALUE th_eval_body(yarv_thread_t *th); + +static void +thread_cleanup_func(void *th_ptr) +{ + yarv_thread_t *th = th_ptr; + th->status = THREAD_KILLED; + th->machine_stack_start = th->machine_stack_end = 0; +} + + +static int +thread_start_func_2(yarv_thread_t *th, VALUE *stack_start) +{ + int state; + VALUE args = th->first_args; + yarv_proc_t *proc; + yarv_thread_t *join_th; + th->machine_stack_start = stack_start; + th->thgroup = th->vm->thgroup_default; + + thread_debug("thread start: %p\n", th); + + native_mutex_lock(&th->vm->global_interpreter_lock); + { + thread_debug("thread start (get lock): %p\n", th); + yarv_set_current_running_thread(th); + + TH_PUSH_TAG(th); + if ((state = EXEC_TAG()) == 0) { + GetProcPtr(th->first_proc, proc); + th->errinfo = Qnil; + th->local_lfp = proc->block.lfp; + th->local_svar = Qnil; + th->value = th_invoke_proc(th, proc, proc->block.self, + RARRAY_LEN(args), RARRAY_PTR(args)); + } + else { + th->value = Qnil; + } + TH_POP_TAG(); + + th->status = THREAD_KILLED; + thread_debug("thread end: %p\n", th); + st_delete_wrap(th->vm->living_threads, th->self); + + /* wake up joinning threads */ + join_th = th->join_list_head; + while (join_th) { + rb_thread_interrupt(join_th); + join_th = join_th->join_list_next; + } + st_delete_wrap(th->vm->living_threads, th->self); + } + native_mutex_unlock(&th->vm->global_interpreter_lock); + return 0; +} + +VALUE yarv_thread_alloc(VALUE klass); + +static VALUE +yarv_thread_s_new(VALUE klass, VALUE args) +{ + yarv_thread_t *th; + VALUE thval; + + /* create thread object */ + thval = yarv_thread_alloc(cYarvThread); + GetThreadPtr(thval, th); + + /* setup thread environment */ + th->first_args = args; + th->first_proc = rb_block_proc(); + + 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; +} + +/* +infty, for this purpose */ +#define DELAY_INFTY 1E30 + +VALUE th_make_jump_tag_but_local_jump(int state, VALUE val); + +static VALUE +yarv_thread_join(yarv_thread_t *target_th, double delay) +{ + yarv_thread_t *th = GET_THREAD(); + double now, limit = timeofday() + delay; + + thread_debug("yarv_thread_join (thid: %p)\n", 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; + } + + while (target_th->status != THREAD_KILLED) { + if (delay == DELAY_INFTY) { + sleep_forever(th); + } + else { + now = timeofday(); + if (now > limit) { + thread_debug("yarv_thread_join: timeout (thid: %p)\n", + target_th->thread_id); + return Qnil; + } + sleep_wait_for_interrupt(th, limit - now); + } + thread_debug("yarv_thread_join: interrupted (thid: %p)\n", + target_th->thread_id); + } + + thread_debug("yarv_thread_join: success (thid: %p)\n", + 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(th_make_jump_tag_but_local_jump( + GET_THROWOBJ_STATE(err), GET_THROWOBJ_VAL(err))); + } + else { + 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 +yarv_thread_join_m(int argc, VALUE *argv, VALUE self) +{ + yarv_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 yarv_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 +yarv_thread_value(VALUE self) +{ + yarv_thread_t *th; + GetThreadPtr(self, th); + yarv_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 += (long)1e6; + time.tv_sec -= 1; + } + return time; +} + +static void +sleep_forever(yarv_thread_t *th) +{ + native_sleep(th, 0); + YARV_CHECK_INTS(); +} + +static void +sleep_timeval(yarv_thread_t *th, struct timeval tv) +{ + native_sleep(th, &tv); +} + +void +rb_thread_sleep_forever() +{ + thread_debug("rb_thread_sleep_forever\n"); + sleep_forever(GET_THREAD()); +} + +static double +timeofday(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6; +} + +static void +sleep_wait_for_interrupt(yarv_thread_t *th, double sleepsec) +{ + sleep_timeval(th, double2timeval(sleepsec)); +} + +static void +sleep_for_polling(yarv_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) +{ + yarv_thread_t *th = GET_THREAD(); + sleep_timeval(th, time); +} + +void +rb_thread_polling(void) +{ + if (!rb_thread_alone()) { + yarv_thread_t *th = GET_THREAD(); + sleep_for_polling(th); + } +} + +struct timeval rb_time_timeval(); + +void +rb_thread_sleep(int sec) +{ + rb_thread_wait_for(rb_time_timeval(INT2FIX(sec))); +} + +void +rb_thread_schedule() +{ + thread_debug("rb_thread_schedule\n"); + if (!rb_thread_alone()) { + yarv_thread_t *th = GET_THREAD(); + + thread_debug("rb_thread_schedule/switch start\n"); + + yarv_save_machine_context(th); + native_mutex_unlock(&th->vm->global_interpreter_lock); + { + native_thread_yield(); + } + native_mutex_lock(&th->vm->global_interpreter_lock); + + yarv_set_current_running_thread(th); + thread_debug("rb_thread_schedule/switch done\n"); + + YARV_CHECK_INTS(); + } +} + + +static VALUE +rb_thread_s_critical(VALUE self) +{ + rb_warn("Thread.critical is unsupported. Use Mutex instead."); + return Qnil; +} + + +VALUE +rb_thread_run_parallel(VALUE(*func)(yarv_thread_t *th, void *), void *data) +{ + VALUE val; + yarv_thread_t *th = GET_THREAD(); + + GVL_UNLOCK_RANGE({ + val = func(th, data); + }); + + return val; +} + + +/* + * 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 +yarv_thread_s_pass(VALUE klass) +{ + rb_thread_schedule(); + return Qnil; +} + +/* + * + */ + +void +yarv_thread_execute_interrupts(yarv_thread_t *th) +{ + while (th->interrupt_flag) { + int status = th->status; + 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->throwed_errinfo) { + VALUE err = th->throwed_errinfo; + th->throwed_errinfo = 0; + thread_debug("yarv_thread_execute_interrupts: %p\n", err); + + if (err == eKillSignal) { + th->errinfo = INT2FIX(TAG_FATAL); + TH_JUMP_TAG(th, TAG_FATAL); + } + else if (err == eTerminateSignal) { + struct yarv_tag *tag = th->tag; + + /* rewind to toplevel stack */ + while (th->tag->prev) { + th->tag = th->tag->prev; + } + + th->errinfo = INT2FIX(TAG_FATAL); + TH_JUMP_TAG(th, TAG_FATAL); + } + else { + rb_exc_raise(err); + } + } + th->status = status; + + /* thread pass */ + rb_thread_schedule(); + } +} + + +void +rb_gc_mark_threads() +{ + // TODO: remove +} + +/*****************************************************/ + +static void +rb_thread_ready(yarv_thread_t *th) +{ + rb_thread_interrupt(th); +} + +static VALUE +yarv_thread_raise(int argc, VALUE *argv, yarv_thread_t *th) +{ + VALUE exc; + + if (rb_thread_dead(th)) { + return Qnil; + } + + exc = rb_make_exception(argc, argv); + // TODO: need synchronization if run threads in parallel + th->throwed_errinfo = exc; + rb_thread_ready(th); + return Qnil; +} + +void +rb_thread_signal_raise(void *thptr, const char *sig) +{ + VALUE argv[1]; + char buf[BUFSIZ]; + yarv_thread_t *th = thptr; + + if (sig == 0) { + return; /* should not happen */ + } + snprintf(buf, BUFSIZ, "SIG%s", sig); + argv[0] = rb_exc_new3(rb_eSignal, rb_str_new2(buf)); + yarv_thread_raise(1, argv, th->vm->main_thread); +} + +void +rb_thread_signal_exit(void *thptr) +{ + VALUE argv[1]; + VALUE args[2]; + yarv_thread_t *th = thptr; + + args[0] = INT2NUM(EXIT_SUCCESS); + args[1] = rb_str_new2("exit"); + argv[0] = rb_class_new_instance(2, args, rb_eSystemExit); + yarv_thread_raise(1, argv, th->vm->main_thread); +} + + +/* + * 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 +yarv_thread_raise_m(int argc, VALUE *argv, VALUE self) +{ + yarv_thread_t *th; + GetThreadPtr(self, th); + yarv_thread_raise(argc, argv, th); + 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) +{ + yarv_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", th, th->thread_id); + + rb_thread_interrupt(th); + th->throwed_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() +{ + 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) +{ + yarv_thread_t *th; + GetThreadPtr(thread, th); + + if (th->status == THREAD_KILLED) { + rb_raise(rb_eThreadError, "killed thread"); + } + rb_thread_ready(th); + return thread; +} + + +/* + * call-seq: + * thr.run => thr + * + * Wakes up <i>thr</i>, making it eligible for scheduling. If not in a critical + * section, then invokes the scheduler. + * + * 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(thread) + 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. Resets the ``critical'' condition + * to <code>false</code>. + * + * 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_forever(); + return Qnil; +} + +static int +thread_list_i(st_data_t key, st_data_t val, void *data) +{ + VALUE ary = (VALUE)data; + yarv_thread_t *th; + GetThreadPtr((VALUE)key, th); + + switch (th->status) { + case THREAD_RUNNABLE: + case THREAD_STOPPED: + 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; +} + +/* + * call-seq: + * Thread.current => thread + * + * Returns the currently executing thread. + * + * Thread.current #=> #<Thread:0x401bdf4c run> + */ + +static VALUE +yarv_thread_s_current(VALUE klass) +{ + return GET_THREAD()->self; +} + +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() +{ + 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) +{ + yarv_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) +{ + yarv_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) +{ + yarv_thread_t *th; + VALUE group; + GetThreadPtr(thread, th); + group = th->thgroup; + + if (!group) { + group = Qnil; + } + return group; +} + +static const char * +thread_status_name(enum yarv_thread_status status) +{ + switch (status) { + case THREAD_RUNNABLE: + return "run"; + case THREAD_STOPPED: + return "sleep"; + case THREAD_TO_KILL: + return "aborting"; + case THREAD_KILLED: + return "dead"; + default: + return "unknown"; + } +} + +static int +rb_thread_dead(yarv_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 } + * Thread.critical = true + * 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) +{ + yarv_thread_t *th; + GetThreadPtr(thread, th); + + if (rb_thread_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) +{ + yarv_thread_t *th; + GetThreadPtr(thread, th); + + if (rb_thread_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) +{ + yarv_thread_t *th; + GetThreadPtr(thread, th); + + if (rb_thread_dead(th)) + return Qtrue; + if (th->status == THREAD_STOPPED) + 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) +{ + yarv_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) +{ + char *cname = rb_obj_classname(thread); + yarv_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) +{ + yarv_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) +{ + yarv_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(th->local_storage, (st_data_t *) & id, 0); + 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, ID id) +{ + yarv_thread_t *th; + GetThreadPtr(self, th); + + if (!th->local_storage) { + return Qfalse; + } + if (st_lookup(th->local_storage, rb_to_id(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; +} + +int +rb_thread_alone() +{ + int num = 1; + if (GET_THREAD()->vm->living_threads) { + num = GET_THREAD()->vm->living_threads->num_entries; + 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) +{ + yarv_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 zero; higher-priority threads + * will run before lower-priority threads. + * + * Thread.current.priority #=> 0 + */ + +static VALUE +rb_thread_priority(VALUE thread) +{ + yarv_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 + * Thread.critical = 1 + * count1 #=> 622504 + * count2 #=> 5832 + */ + +static VALUE +rb_thread_priority_set(VALUE thread, VALUE prio) +{ + yarv_thread_t *th; + GetThreadPtr(thread, th); + + rb_secure(4); + + th->priority = NUM2INT(prio); + native_thread_apply_priority(th); + return prio; +} + +/* for IO */ + +void +rb_thread_wait_fd(int fd) +{ + fd_set set; + int result = 0; + + FD_ZERO(&set); + FD_SET(fd, &set); + thread_debug("rb_thread_wait_fd (%d)\n", fd); + while (result <= 0) { + GVL_UNLOCK_RANGE(result = select(fd + 1, &set, 0, 0, 0)); + } + thread_debug("rb_thread_wait_fd done\n", fd); +} + +int +rb_thread_fd_writable(int fd) +{ + fd_set set; + int result = 0; + + FD_ZERO(&set); + FD_SET(fd, &set); + + thread_debug("rb_thread_fd_writable (%d)\n", fd); + while (result <= 0) { + GVL_UNLOCK_RANGE(result = select(fd + 1, 0, &set, 0, 0)); + } + thread_debug("rb_thread_fd_writable done\n"); + return Qtrue; +} + +int +rb_thread_select(int max, fd_set * read, fd_set * write, fd_set * except, + struct timeval *timeout) +{ + struct timeval *tvp = timeout; + int lerrno, n; +#ifndef linux + double limit; + struct timeval tv; +#endif + + if (!read && !write && !except) { + if (!timeout) { + rb_thread_sleep_forever(); + return 0; + } + rb_thread_wait_for(*timeout); + return 0; + } + +#ifndef linux + if (timeout) { + limit = timeofday() + + (double)timeout->tv_sec + (double)timeout->tv_usec * 1e-6; + } +#endif + +#ifndef linux + if (timeout) { + tv = *timeout; + tvp = &tv; + } +#else + tvp = timeout; +#endif + + for (;;) { + GVL_UNLOCK_RANGE(n = select(max, read, write, except, tvp); + lerrno = errno; + ); + + if (n < 0) { + switch (errno) { + case EINTR: +#ifdef ERESTART + case ERESTART: +#endif + +#ifndef linux + if (timeout) { + double d = limit - timeofday(); + tv = double2timeval(d); + } +#endif + continue; + default: + break; + } + } + return n; + } +} + + +/* + * for GC + */ + +void +yarv_set_stack_end(VALUE **stack_end_p) +{ + VALUE stack_end; + *stack_end_p = &stack_end; +} + +void +yarv_save_machine_context(yarv_thread_t *th) +{ + yarv_set_stack_end(&th->machine_stack_end); + setjmp(th->machine_regs); +} + +/* + * + */ + +int rb_get_next_signal(yarv_vm_t *vm); + +static void +timer_thread_function(void) +{ + yarv_vm_t *vm = GET_VM(); /* TODO: fix me for Multi-VM */ + vm->running_thread->interrupt_flag = 1; + + if (vm->bufferd_signal_size && vm->main_thread->exec_signal == 0) { + vm->main_thread->exec_signal = rb_get_next_signal(vm); + thread_debug("bufferd_signal_size: %d, sig: %d\n", + vm->bufferd_signal_size, vm->main_thread->exec_signal); + rb_thread_interrupt(vm->main_thread); + } +} + +/***/ + +void +rb_thread_atfork(void) +{ + yarv_thread_t *th = GET_THREAD(); + yarv_vm_t *vm = th->vm; + vm->main_thread = th; + + st_free_table(vm->living_threads); + vm->living_threads = st_init_numtable(); + st_insert(vm->living_threads, th->self, (st_data_t) th->thread_id); +} + +/* + * for tests + */ + +static VALUE +raw_gets(VALUE klass) +{ + char buff[100]; + GVL_UNLOCK_BEGIN(); + { + fgets(buff, 100, stdin); + } + GVL_UNLOCK_END(); + return rb_str_new2(buff); +} + + +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, free, 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; + yarv_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 = { + ary, 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 + */ + +VALUE +thgroup_enclose(group) + 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) +{ + yarv_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; +} + +/* + Mutex + */ + +typedef struct mutex_struct { + yarv_thread_t *th; + yarv_thread_lock_t lock; +} mutex_t; + +#define GetMutexVal(obj, tobj) \ + Data_Get_Struct(obj, mutex_t, tobj) + +static void +mutex_mark(void *ptr) +{ + if (ptr) { + mutex_t *mutex = ptr; + if (mutex->th) { + rb_gc_mark(mutex->th->self); + } + } +} + +static void +mutex_free(void *ptr) +{ + if (ptr) { + mutex_t *mutex = ptr; + if (mutex->th) { + native_mutex_unlock(&mutex->lock); + } + } + ruby_xfree(ptr); +} + +static VALUE +mutex_alloc(VALUE klass) +{ + VALUE volatile obj; + mutex_t *mutex; + + obj = Data_Make_Struct(klass, mutex_t, mutex_mark, mutex_free, mutex); + mutex->th = 0; + native_mutex_initialize(&mutex->lock); + return obj; +} + +static VALUE +mutex_initialize(VALUE self) +{ + return self; +} + +static VALUE +mutex_locked_p(VALUE self) +{ + mutex_t *mutex; + GetMutexVal(self, mutex); + return mutex->th ? Qtrue : Qfalse; +} + +static VALUE +mutex_try_lock(VALUE self) +{ + mutex_t *mutex; + GetMutexVal(self, mutex); + + if (native_mutex_trylock(&mutex->lock) != EBUSY) { + return Qtrue; + } + else { + return Qfalse; + } +} + +static VALUE +mutex_lock(VALUE self) +{ + mutex_t *mutex; + GetMutexVal(self, mutex); + + if (mutex->th == GET_THREAD()) { + rb_raise(rb_eThreadError, "deadlock; recursive locking"); + } + + if (native_mutex_trylock(&mutex->lock) != 0) { + /* can't cancel */ + GVL_UNLOCK_BEGIN(); + native_mutex_lock(&mutex->lock); + GVL_UNLOCK_END(); + } + + mutex->th = GET_THREAD(); + return self; +} + +static VALUE +mutex_unlock(VALUE self) +{ + mutex_t *mutex; + GetMutexVal(self, mutex); + + if (mutex->th != GET_THREAD()) { + rb_raise(rb_eThreadError, + "Attempt to unlock a mutex which is locked by another thread"); + } + mutex->th = 0; + native_mutex_unlock(&mutex->lock); + return self; +} + +static VALUE +mutex_sleep(int argc, VALUE *argv, VALUE self) +{ + int beg, end; + mutex_unlock(self); + + beg = time(0); + if (argc == 0) { + rb_thread_sleep_forever(); + } + else if (argc == 1) { + rb_thread_wait_for(rb_time_interval(argv[0])); + } + else { + rb_raise(rb_eArgError, "wrong number of arguments"); + } + mutex_lock(self); + end = time(0) - beg; + return INT2FIX(end); +} + + +void +Init_yarvthread() +{ + VALUE cThGroup; + VALUE thgroup_default; + VALUE cMutex; + + rb_define_global_function("raw_gets", raw_gets, 0); + + rb_define_singleton_method(cYarvThread, "new", yarv_thread_s_new, -2); + rb_define_singleton_method(cYarvThread, "start", yarv_thread_s_new, -2); + rb_define_singleton_method(cYarvThread, "fork", yarv_thread_s_new, -2); + rb_define_singleton_method(cYarvThread, "main", rb_thread_s_main, 0); + rb_define_singleton_method(cYarvThread, "current", yarv_thread_s_current, 0); + rb_define_singleton_method(cYarvThread, "stop", rb_thread_stop, 0); + rb_define_singleton_method(cYarvThread, "kill", rb_thread_s_kill, 1); + rb_define_singleton_method(cYarvThread, "exit", rb_thread_exit, 0); + rb_define_singleton_method(cYarvThread, "pass", yarv_thread_s_pass, 0); + rb_define_singleton_method(cYarvThread, "list", rb_thread_list, 0); + rb_define_singleton_method(cYarvThread, "critical", rb_thread_s_critical, 0); + rb_define_singleton_method(cYarvThread, "critical=", rb_thread_s_critical, 1); + rb_define_singleton_method(cYarvThread, "abort_on_exception", rb_thread_s_abort_exc, 0); + rb_define_singleton_method(cYarvThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1); + + rb_define_method(cYarvThread, "raise", yarv_thread_raise_m, -1); + rb_define_method(cYarvThread, "join", yarv_thread_join_m, -1); + rb_define_method(cYarvThread, "value", yarv_thread_value, 0); + rb_define_method(cYarvThread, "kill", rb_thread_kill, 0); + rb_define_method(cYarvThread, "terminate", rb_thread_kill, 0); + rb_define_method(cYarvThread, "exit", rb_thread_kill, 0); + rb_define_method(cYarvThread, "run", rb_thread_run, 0); + rb_define_method(cYarvThread, "wakeup", rb_thread_wakeup, 0); + rb_define_method(cYarvThread, "[]", rb_thread_aref, 1); + rb_define_method(cYarvThread, "[]=", rb_thread_aset, 2); + rb_define_method(cYarvThread, "key?", rb_thread_key_p, 1); + rb_define_method(cYarvThread, "keys", rb_thread_keys, 0); + rb_define_method(cYarvThread, "priority", rb_thread_priority, 0); + rb_define_method(cYarvThread, "priority=", rb_thread_priority_set, 1); + rb_define_method(cYarvThread, "status", rb_thread_status, 0); + rb_define_method(cYarvThread, "alive?", rb_thread_alive_p, 0); + rb_define_method(cYarvThread, "stop?", rb_thread_stop_p, 0); + rb_define_method(cYarvThread, "abort_on_exception", rb_thread_abort_exc, 0); + rb_define_method(cYarvThread, "abort_on_exception=", rb_thread_abort_exc_set, 1); + rb_define_method(cYarvThread, "safe_level", rb_thread_safe_level, 0); + rb_define_method(cYarvThread, "group", rb_thread_group, 0); + + rb_define_method(cYarvThread, "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); + GET_THREAD()->vm->thgroup_default = thgroup_default = rb_obj_alloc(cThGroup); + rb_define_const(cThGroup, "Default", thgroup_default); + + cMutex = rb_define_class("Mutex", rb_cObject); + rb_define_alloc_func(cMutex, mutex_alloc); + rb_define_method(cMutex, "initialize", mutex_initialize, 0); + rb_define_method(cMutex, "locked?", mutex_locked_p, 0); + rb_define_method(cMutex, "try_lock", mutex_try_lock, 0); + rb_define_method(cMutex, "lock", mutex_lock, 0); + rb_define_method(cMutex, "unlock", mutex_unlock, 0); + rb_define_method(cMutex, "sleep", mutex_sleep, -1); + yarvcore_eval(Qnil, rb_str_new2( + "class Mutex;" + " def synchronize; self.lock; yield; ensure; self.unlock; end;" + "end;") , rb_str_new2("<preload>"), INT2FIX(1)); + Init_native_thread(); + { + /* main thread setting */ + { + /* acquire global interpreter lock */ + yarv_thread_lock_t *lp = &GET_THREAD()->vm->global_interpreter_lock; + native_mutex_initialize(lp); + native_mutex_lock(lp); + native_mutex_initialize(&GET_THREAD()->interrupt_lock); + } + } + + rb_thread_create_timer_thread(); +} + |