Revision: 1930 http://trac.macosforge.org/projects/ruby/changeset/1930 Author: lsansonetti@apple.com Date: 2009-06-25 21:21:03 -0700 (Thu, 25 Jun 2009) Log Message: ----------- more work on MacRuby MT Modified Paths: -------------- MacRuby/branches/experimental/include/ruby/intern.h MacRuby/branches/experimental/thread.c MacRuby/branches/experimental/vm.cpp MacRuby/branches/experimental/vm.h Modified: MacRuby/branches/experimental/include/ruby/intern.h =================================================================== --- MacRuby/branches/experimental/include/ruby/intern.h 2009-06-25 18:09:46 UTC (rev 1929) +++ MacRuby/branches/experimental/include/ruby/intern.h 2009-06-26 04:21:03 UTC (rev 1930) @@ -297,10 +297,6 @@ void rb_thread_polling(void); void rb_thread_sleep(int); void rb_thread_sleep_forever(void); -VALUE rb_thread_stop(void); -VALUE rb_thread_wakeup(VALUE); -VALUE rb_thread_run(VALUE); -VALUE rb_thread_kill(VALUE); VALUE rb_thread_create(VALUE (*)(ANYARGS), void*); void rb_thread_signal_raise(void *, int); void rb_thread_signal_exit(void *); @@ -608,6 +604,7 @@ /* time.c */ VALUE rb_time_new(time_t, long); VALUE rb_time_nano_new(time_t, long); +struct timeval rb_time_interval(VALUE num); /* variable.c */ //VALUE rb_mod_name(VALUE); VALUE rb_class_path(VALUE); Modified: MacRuby/branches/experimental/thread.c =================================================================== --- MacRuby/branches/experimental/thread.c 2009-06-25 18:09:46 UTC (rev 1929) +++ MacRuby/branches/experimental/thread.c 2009-06-26 04:21:03 UTC (rev 1930) @@ -1,3 +1,12 @@ +/* + * MacRuby implementation of Thread. + * + * This file is covered by the Ruby license. See COPYING for more details. + * + * Copyright (C) 2009, Apple Inc. All rights reserved. + * Copyright (C) 2004-2007 Koichi Sasada + */ + #include "ruby/ruby.h" #include "ruby/node.h" #include "vm.h" @@ -2,7 +11,5 @@ -#include <pthread.h> - typedef struct rb_vm_mutex { pthread_mutex_t mutex; - pthread_t thread; + rb_vm_thread_t *thread; } rb_vm_mutex_t; @@ -56,7 +63,7 @@ } static VALUE -thread_initialize(VALUE thread, SEL sel, int argc, VALUE *argv) +thread_initialize(VALUE thread, SEL sel, int argc, const VALUE *argv) { if (!rb_block_given_p()) { rb_raise(rb_eThreadError, "must be called with a block"); @@ -65,21 +72,8 @@ assert(b != NULL); rb_vm_thread_t *t = GetThreadPtr(thread); - assert(t->body == NULL); - GC_WB(&t->body, b); + rb_vm_thread_pre_init(t, b, argc, argv, rb_vm_create_vm()); - if (argc > 0) { - t->argc = argc; - GC_WB(&t->argv, xmalloc(sizeof(VALUE) * argc)); - int i; - for (i = 0; i < argc; i++) { - GC_WB(&t->argv[i], argv[i]); - } - } - - t->vm = rb_vm_create_vm(); - t->value = Qundef; - // Retain the Thread object to avoid a potential GC, the corresponding // release is done in rb_vm_thread_run(). rb_objc_retain((void *)thread); @@ -89,6 +83,8 @@ rb_sys_fail("pthread_create() failed"); } + //assert(pthread_detach(t->thread) == 0); + return thread; } @@ -148,7 +144,7 @@ } rb_vm_thread_t *t = GetThreadPtr(self); - pthread_join(t->thread, NULL); + assert(pthread_join(t->thread, NULL) == 0); return self; } @@ -172,30 +168,12 @@ } void -rb_thread_sleep_forever() -{ - // TODO -} - -void -rb_thread_wait_for(struct timeval time) -{ - // TODO -} - -void rb_thread_polling(void) { // TODO } void -rb_thread_sleep(int sec) -{ - // TODO -} - -void rb_thread_schedule(void) { // TODO @@ -224,9 +202,9 @@ */ static VALUE -thread_s_pass(VALUE klass) +thread_s_pass(VALUE klass, SEL sel) { - // TODO + pthread_yield_np(); return Qnil; } @@ -258,9 +236,9 @@ /* * call-seq: - * thr.exit => thr or nil - * thr.kill => thr or nil - * thr.terminate => thr or nil + * thr.exit => thr + * thr.kill => thr + * thr.terminate => thr * * 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 @@ -268,11 +246,16 @@ * the process. */ -VALUE -rb_thread_kill(VALUE thread) +static VALUE +rb_thread_kill(VALUE thread, SEL sel) { - // TODO - return Qnil; + rb_vm_thread_t *t = GetThreadPtr(thread); + if (t->status == THREAD_KILLED) { + // Already being killed! + return thread; + } + rb_vm_thread_cancel(t); + return thread; } /* @@ -328,10 +311,10 @@ * hey! */ -VALUE -rb_thread_wakeup(VALUE thread) +static VALUE +rb_thread_wakeup(VALUE thread, SEL sel) { - // TODO + rb_vm_thread_wakeup(GetThreadPtr(thread)); return Qnil; } @@ -354,11 +337,11 @@ * c */ -VALUE -rb_thread_run(VALUE thread) +static VALUE +rb_thread_run(VALUE thread, SEL sel) { - // TODO - return Qnil; + // On MacRuby, #wakeup and #run are the same. + return rb_thread_wakeup(thread, 0); } /* @@ -379,10 +362,10 @@ * abc */ -VALUE -rb_thread_stop(void) +static VALUE +rb_thread_stop(VALUE rcv, SEL sel) { - // TODO + rb_thread_sleep_forever(); return Qnil; } @@ -446,10 +429,9 @@ */ static VALUE -rb_thread_s_abort_exc(void) +rb_thread_s_abort_exc(VALUE rcv, SEL sel) { - // TODO - return Qnil; + return rb_vm_abort_on_exception() ? Qtrue : Qfalse; } /* @@ -477,10 +459,10 @@ */ static VALUE -rb_thread_s_abort_exc_set(VALUE self, VALUE val) +rb_thread_s_abort_exc_set(VALUE self, SEL sel, VALUE val) { - // TODO - return Qnil; + rb_vm_set_abort_on_exception(RTEST(val)); + return val; } /* @@ -554,11 +536,30 @@ * Thread.current.status #=> "run" */ +static const char * +rb_thread_status_cstr(VALUE thread) +{ + rb_vm_thread_t *t = GetThreadPtr(thread); + switch (t->status) { + case THREAD_ALIVE: + return "run"; + + case THREAD_SLEEP: + return "sleep"; + + case THREAD_KILLED: + return "abort"; + + case THREAD_DEAD: + return "dead"; + } + return "unknown"; +} + static VALUE -rb_thread_status(VALUE thread) +rb_thread_status(VALUE thread, SEL sel) { - // TODO - return Qnil; + return rb_str_new2(rb_thread_status_cstr(thread)); } /* @@ -574,10 +575,10 @@ */ static VALUE -rb_thread_alive_p(VALUE thread) +rb_thread_alive_p(VALUE thread, SEL sel) { - // TODO - return Qnil; + rb_vm_thread_status_t s = GetThreadPtr(thread)->status; + return s == THREAD_ALIVE || s == THREAD_SLEEP ? Qtrue : Qfalse; } /* @@ -595,7 +596,8 @@ static VALUE rb_thread_stop_p(VALUE thread) { - return Qnil; + rb_vm_thread_status_t s = GetThreadPtr(thread)->status; + return s == THREAD_DEAD || s == THREAD_SLEEP ? Qtrue : Qfalse; } /* @@ -627,7 +629,7 @@ static VALUE rb_thread_inspect(VALUE thread, SEL sel) { - const char *status = "unknown"; // TODO + const char *status = rb_thread_status_cstr(thread); char buf[100]; snprintf(buf, sizeof buf, "#<%s:%p %s>", rb_obj_classname(thread), @@ -1182,7 +1184,7 @@ rb_mutex_trylock(VALUE self, SEL sel) { if (pthread_mutex_trylock(&GetMutexPtr(self)->mutex) == 0) { - GetMutexPtr(self)->thread = pthread_self(); + GetMutexPtr(self)->thread = GetThreadPtr(rb_vm_current_thread()); return Qtrue; } return Qfalse; @@ -1199,13 +1201,15 @@ static VALUE rb_mutex_lock(VALUE self, SEL sel) { - pthread_t current = pthread_self(); + rb_vm_thread_t *current = GetThreadPtr(rb_vm_current_thread()); rb_vm_mutex_t *m = GetMutexPtr(self); if (m->thread == current) { rb_raise(rb_eThreadError, "deadlock; recursive locking"); } + current->status = THREAD_SLEEP; assert_ok(pthread_mutex_lock(&m->mutex)); + current->status = THREAD_ALIVE; m->thread = current; return self; @@ -1244,10 +1248,26 @@ */ static VALUE -mutex_sleep(int argc, VALUE *argv, VALUE self) +mutex_sleep(VALUE self, SEL sel, int argc, VALUE *argv) { - // TODO - return Qnil; + VALUE timeout; + rb_scan_args(argc, argv, "01", &timeout); + + rb_mutex_unlock(self, 0); + + time_t beg, end; + beg = time(0); + + if (timeout == Qnil) { + rb_thread_sleep_forever(); + } + else { + struct timeval t = rb_time_interval(timeout); + rb_thread_wait_for(t); + } + + end = time(0) - beg; + return INT2FIX(end); } /* @@ -1262,9 +1282,16 @@ mutex_synchronize(VALUE self, SEL sel) { rb_mutex_lock(self, 0); + // TODO catch exception VALUE ret = rb_yield(Qundef); - rb_mutex_unlock(self, 0); + + if (rb_mutex_locked_p(self, 0) == Qtrue) { + // We only unlock the mutex if it's still locked, since it could have + // been unlocked in the block! + rb_mutex_unlock(self, 0); + } + return ret; } @@ -1297,32 +1324,32 @@ rb_define_singleton_method(rb_cThread, "fork", thread_start, -2); rb_objc_define_method(*(VALUE *)rb_cThread, "main", rb_thread_s_main, 0); rb_objc_define_method(*(VALUE *)rb_cThread, "current", thread_s_current, 0); - rb_define_singleton_method(rb_cThread, "stop", rb_thread_stop, 0); + rb_objc_define_method(*(VALUE *)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_objc_define_method(*(VALUE *)rb_cThread, "pass", thread_s_pass, 0); rb_objc_define_method(*(VALUE *)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); + rb_objc_define_method(*(VALUE *)rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0); + rb_objc_define_method(*(VALUE *)rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1); rb_objc_define_method(rb_cThread, "initialize", thread_initialize, -1); rb_define_method(rb_cThread, "raise", thread_raise_m, -1); rb_objc_define_method(rb_cThread, "join", thread_join_m, -1); rb_objc_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_objc_define_method(rb_cThread, "kill", rb_thread_kill, 0); + rb_objc_define_method(rb_cThread, "terminate", rb_thread_kill, 0); + rb_objc_define_method(rb_cThread, "exit", rb_thread_kill, 0); + rb_objc_define_method(rb_cThread, "run", rb_thread_run, 0); + rb_objc_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_objc_define_method(rb_cThread, "status", rb_thread_status, 0); + rb_objc_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0); + rb_objc_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); @@ -1343,7 +1370,7 @@ rb_objc_define_method(rb_cMutex, "try_lock", rb_mutex_trylock, 0); rb_objc_define_method(rb_cMutex, "lock", rb_mutex_lock, 0); rb_objc_define_method(rb_cMutex, "unlock", rb_mutex_unlock, 0); - rb_define_method(rb_cMutex, "sleep", mutex_sleep, -1); + rb_objc_define_method(rb_cMutex, "sleep", mutex_sleep, -1); rb_objc_define_method(rb_cMutex, "synchronize", mutex_synchronize, 0); rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError); Modified: MacRuby/branches/experimental/vm.cpp =================================================================== --- MacRuby/branches/experimental/vm.cpp 2009-06-25 18:09:46 UTC (rev 1929) +++ MacRuby/branches/experimental/vm.cpp 2009-06-26 04:21:03 UTC (rev 1930) @@ -186,6 +186,7 @@ { running = false; multithreaded = false; + abort_on_exception = false; assert(pthread_mutex_init(&gl, 0) == 0); @@ -697,6 +698,20 @@ } extern "C" +bool +rb_vm_abort_on_exception(void) +{ + return GET_CORE()->get_abort_on_exception(); +} + +extern "C" +void +rb_vm_set_abort_on_exception(bool flag) +{ + GET_CORE()->set_abort_on_exception(flag); +} + +extern "C" void rb_vm_set_const(VALUE outer, ID id, VALUE obj) { @@ -3695,7 +3710,10 @@ assert(argc > 0); VALUE current_exception = GET_VM()->current_exception(); - assert(current_exception != Qnil); + if (current_exception == Qnil) { + // Not a Ruby exception... + return 0; + } va_list ar; unsigned char active = 0; @@ -4241,13 +4259,41 @@ unlock(); rb_vm_thread_t *t = GetThreadPtr(thread); + + const int code = pthread_mutex_destroy(&t->sleep_mutex); + if (code == EBUSY) { + // The mutex is already locked, which means we are being called from + // a cancellation point inside the wait logic. Let's unlock the mutex + // and try again. + assert(pthread_mutex_unlock(&t->sleep_mutex) == 0); + assert(pthread_mutex_destroy(&t->sleep_mutex) == 0); + } + else if (code != 0) { + abort(); + } + assert(pthread_cond_destroy(&t->sleep_cond) == 0); + RoxorVM *vm = (RoxorVM *)t->vm; delete vm; t->vm = NULL; assert(pthread_setspecific(RoxorVM::vm_thread_key, NULL) == 0); + + t->status = THREAD_DEAD; } +static inline void +rb_vm_thread_throw_kill(void) +{ + rb_vm_rethrow(); +} + +static void +rb_vm_thread_destructor(void *userdata) +{ + rb_vm_thread_throw_kill(); +} + extern "C" void * rb_vm_thread_run(VALUE thread) @@ -4255,24 +4301,30 @@ rb_objc_gc_register_thread(); GET_CORE()->register_thread(thread); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + // Release the thread now. rb_objc_release((void *)thread); + pthread_cleanup_push(rb_vm_thread_destructor, (void *)thread); + + rb_vm_thread_t *t = GetThreadPtr(thread); try { - rb_vm_thread_t *t = GetThreadPtr(thread); VALUE val = rb_vm_block_eval(t->body, t->argc, t->argv); GC_WB(&t->value, val); } catch (...) { // TODO handle thread-level exceptions. - printf("exception raised inside thread %p\n", pthread_self()); + //printf("exception raised inside thread %p\n", pthread_self()); } + pthread_cleanup_pop(0); + GET_CORE()->unregister_thread(thread); rb_objc_gc_unregister_thread(); return NULL; - } extern "C" @@ -4296,6 +4348,118 @@ return RoxorVM::main->get_thread(); } +extern "C" +void +rb_vm_thread_pre_init(rb_vm_thread_t *t, rb_vm_block_t *body, int argc, + const VALUE *argv, void *vm) +{ + t->thread = 0; // this will be set later + + if (body != NULL) { + GC_WB(&t->body, body); + } + else { + t->body = NULL; + } + + if (argc > 0) { + t->argc = argc; + GC_WB(&t->argv, xmalloc(sizeof(VALUE) * argc)); + int i; + for (i = 0; i < argc; i++) { + GC_WB(&t->argv[i], argv[i]); + } + } + else { + t->argc = 0; + t->argv = NULL; + } + + t->vm = vm; + t->value = Qundef; + t->status = THREAD_ALIVE; + + assert(pthread_mutex_init(&t->sleep_mutex, NULL) == 0); + assert(pthread_cond_init(&t->sleep_cond, NULL) == 0); +} + +extern "C" +void +rb_thread_sleep_forever() +{ + rb_vm_thread_t *t = GET_THREAD(); + t->status = THREAD_SLEEP; + + assert(pthread_mutex_lock(&t->sleep_mutex) == 0); + const int code = pthread_cond_wait(&t->sleep_cond, &t->sleep_mutex); + assert(code == 0 || code == ETIMEDOUT); + assert(pthread_mutex_unlock(&t->sleep_mutex) == 0); + + t->status = THREAD_ALIVE; +} + +extern "C" +void +rb_thread_wait_for(struct timeval time) +{ + struct timeval tvn; + gettimeofday(&tvn, NULL); + + struct timespec ts; + ts.tv_sec = tvn.tv_sec + time.tv_sec; + ts.tv_nsec = (tvn.tv_usec + time.tv_usec) * 1000; + while (ts.tv_nsec >= 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + + rb_vm_thread_t *t = GET_THREAD(); + t->status = THREAD_SLEEP; + + assert(pthread_mutex_lock(&t->sleep_mutex) == 0); + const int code = pthread_cond_timedwait(&t->sleep_cond, &t->sleep_mutex, + &ts); + assert(code == 0 || code == ETIMEDOUT); + assert(pthread_mutex_unlock(&t->sleep_mutex) == 0); + + t->status = THREAD_ALIVE; +} + +extern "C" +void +rb_vm_thread_wakeup(rb_vm_thread_t *t) +{ + if (t->status == THREAD_DEAD) { + rb_raise(rb_eThreadError, "can't wake up thread from the dead"); + } + if (t->status == THREAD_SLEEP) { + assert(pthread_cond_signal(&t->sleep_cond) == 0); + } +} + +extern "C" +void +rb_vm_thread_cancel(rb_vm_thread_t *t) +{ + t->status = THREAD_KILLED; + if (t->thread == pthread_self()) { + rb_vm_thread_throw_kill(); + } + else { + assert(pthread_cancel(t->thread) == 0); + } +} + +extern "C" +void +rb_thread_sleep(int sec) +{ + struct timeval time; + time.tv_sec = sec; + time.tv_usec = 0; + rb_thread_wait_for(time); +} + static VALUE builtin_ostub1(IMP imp, id self, SEL sel, int argc, VALUE *argv) { @@ -4384,11 +4548,13 @@ Init_PostVM(void) { // Create and register the main thread; + RoxorVM *main_vm = GET_VM(); rb_vm_thread_t *t = (rb_vm_thread_t *)xmalloc(sizeof(rb_vm_thread_t)); + rb_vm_thread_pre_init(t, NULL, 0, NULL, (void *)main_vm); t->thread = pthread_self(); - t->vm = (void *)GET_VM(); VALUE main = Data_Wrap_Struct(rb_cThread, NULL, NULL, t); GET_CORE()->register_thread(main); + main_vm->set_thread(main); } extern "C" Modified: MacRuby/branches/experimental/vm.h =================================================================== --- MacRuby/branches/experimental/vm.h 2009-06-25 18:09:46 UTC (rev 1929) +++ MacRuby/branches/experimental/vm.h 2009-06-26 04:21:03 UTC (rev 1930) @@ -71,6 +71,15 @@ #define GetThreadPtr(obj) ((rb_vm_thread_t *)DATA_PTR(obj)) +typedef enum { + THREAD_ALIVE, // this thread was born to be alive + THREAD_SLEEP, // this thread is sleeping + THREAD_KILLED, // this thread is being killed! + THREAD_DEAD // this thread is dead, sigh +} rb_vm_thread_status_t; + +#include <pthread.h> + typedef struct rb_vm_thread { pthread_t thread; rb_vm_block_t *body; @@ -78,6 +87,9 @@ const VALUE *argv; void *vm; // an instance of RoxorVM VALUE value; + pthread_mutex_t sleep_mutex; + pthread_cond_t sleep_cond; + rb_vm_thread_status_t status; } rb_vm_thread_t; typedef struct rb_vm_outer { @@ -323,12 +335,19 @@ void rb_vm_add_binding(rb_vm_binding_t *binding); void rb_vm_pop_binding(); +void rb_vm_thread_pre_init(rb_vm_thread_t *t, rb_vm_block_t *body, int argc, + const VALUE *argv, void *vm); void *rb_vm_create_vm(void); void *rb_vm_thread_run(VALUE thread); VALUE rb_vm_current_thread(void); VALUE rb_vm_main_thread(void); VALUE rb_vm_threads(void); +void rb_vm_thread_wakeup(rb_vm_thread_t *t); +void rb_vm_thread_cancel(rb_vm_thread_t *t); +bool rb_vm_abort_on_exception(void); +void rb_vm_set_abort_on_exception(bool flag); + static inline VALUE rb_robject_allocate_instance(VALUE klass) { @@ -360,6 +379,7 @@ VALUE rb_vm_pop_broken_value(void); #define RETURN_IF_BROKEN() \ do { \ + pthread_testcancel(); \ VALUE __v = rb_vm_pop_broken_value(); \ if (__v != Qundef) { \ return __v; \ @@ -472,6 +492,7 @@ // State. bool running; bool multithreaded; + bool abort_on_exception; pthread_mutex_t gl; VALUE loaded_features; VALUE load_path; @@ -525,6 +546,7 @@ ACCESSOR(running, bool); ACCESSOR(multithreaded, bool); + ACCESSOR(abort_on_exception, bool); READER(loaded_features, VALUE); READER(load_path, VALUE); READER(threads, VALUE); @@ -826,6 +848,7 @@ }; #define GET_VM() (RoxorVM::current()) +#define GET_THREAD() (GetThreadPtr(GET_VM()->get_thread())) #endif /* __cplusplus */