Revision: 1944 http://trac.macosforge.org/projects/ruby/changeset/1944 Author: lsansonetti@apple.com Date: 2009-06-26 22:57:24 -0700 (Fri, 26 Jun 2009) Log Message: ----------- more MT work Modified Paths: -------------- MacRuby/branches/experimental/thread.c MacRuby/branches/experimental/vm.cpp MacRuby/branches/experimental/vm.h Modified: MacRuby/branches/experimental/thread.c =================================================================== --- MacRuby/branches/experimental/thread.c 2009-06-27 02:04:20 UTC (rev 1943) +++ MacRuby/branches/experimental/thread.c 2009-06-27 05:57:24 UTC (rev 1944) @@ -16,16 +16,6 @@ rb_vm_thread_t *thread; } rb_vm_mutex_t; -#define assert_ok(call) \ - do { \ - const int __r = call; \ - if (__r != 0) { \ - rb_raise(rb_eRuntimeError, "pthread operation failed: error %d", \ - __r); \ - } \ - } \ - while (0) - VALUE rb_cThread; VALUE rb_cMutex; @@ -136,16 +126,36 @@ static VALUE thread_join_m(VALUE self, SEL sel, int argc, VALUE *argv) { - if (argc != 0) { - rb_raise(rb_eArgError, - "calling #join with an argument is not implemented yet"); - } + VALUE timeout; + rb_scan_args(argc, argv, "01", &timeout); + rb_vm_thread_t *t = GetThreadPtr(self); - if (t->status != THREAD_DEAD) { - assert(pthread_join(t->thread, NULL) == 0); + if (t->status == THREAD_DEAD) { + return self; } + if (timeout == Qnil) { + // No timeout given: block until the thread finishes. + pthread_assert(pthread_join(t->thread, NULL)); + } + else { + // Timeout given: sleep then check if the thread is dead. + // TODO do multiple sleeps instead of only one. + struct timeval tv = rb_time_interval(timeout); + struct timespec ts; + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; + while (ts.tv_nsec >= 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + nanosleep(&ts, NULL); + if (t->status != THREAD_DEAD) { + return Qnil; + } + } + return self; } @@ -558,6 +568,11 @@ static VALUE rb_thread_status(VALUE thread, SEL sel) { + rb_vm_thread_t *t = GetThreadPtr(thread); + if (t->status == THREAD_DEAD) { + // TODO should return Qnil in case the thread got an exception. + return Qfalse; + } return rb_str_new2(rb_thread_status_cstr(thread)); } @@ -1162,7 +1177,7 @@ static VALUE mutex_initialize(VALUE self, SEL sel) { - assert_ok(pthread_mutex_init(&GetMutexPtr(self)->mutex, NULL)); + pthread_assert(pthread_mutex_init(&GetMutexPtr(self)->mutex, NULL)); return self; } @@ -1215,7 +1230,7 @@ } current->status = THREAD_SLEEP; - assert_ok(pthread_mutex_lock(&m->mutex)); + pthread_assert(pthread_mutex_lock(&m->mutex)); current->status = THREAD_ALIVE; m->thread = current; @@ -1239,7 +1254,7 @@ "Attempt to unlock a mutex which is not locked"); } - assert_ok(pthread_mutex_unlock(&m->mutex)); + pthread_assert(pthread_mutex_unlock(&m->mutex)); m->thread = 0; return self; Modified: MacRuby/branches/experimental/vm.cpp =================================================================== --- MacRuby/branches/experimental/vm.cpp 2009-06-27 02:04:20 UTC (rev 1943) +++ MacRuby/branches/experimental/vm.cpp 2009-06-27 05:57:24 UTC (rev 1944) @@ -188,7 +188,7 @@ multithreaded = false; abort_on_exception = false; - assert(pthread_mutex_init(&gl, 0) == 0); + pthread_assert(pthread_mutex_init(&gl, 0)); load_path = rb_ary_new(); rb_objc_retain((void *)load_path); @@ -4340,7 +4340,7 @@ unlock(); rb_vm_thread_t *t = GetThreadPtr(thread); - assert(pthread_setspecific(RoxorVM::vm_thread_key, t->vm) == 0); + pthread_assert(pthread_setspecific(RoxorVM::vm_thread_key, t->vm)); RoxorVM *vm = (RoxorVM *)t->vm; vm->set_thread(thread); @@ -4364,19 +4364,19 @@ // 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); + pthread_assert(pthread_mutex_unlock(&t->sleep_mutex)); + pthread_assert(pthread_mutex_destroy(&t->sleep_mutex)); } else if (code != 0) { abort(); } - assert(pthread_cond_destroy(&t->sleep_cond) == 0); + pthread_assert(pthread_cond_destroy(&t->sleep_cond)); RoxorVM *vm = (RoxorVM *)t->vm; delete vm; t->vm = NULL; - assert(pthread_setspecific(RoxorVM::vm_thread_key, NULL) == 0); + pthread_assert(pthread_setspecific(RoxorVM::vm_thread_key, NULL)); t->status = THREAD_DEAD; } @@ -4410,6 +4410,11 @@ rb_vm_thread_t *t = GetThreadPtr(thread); + // Normally the pthread ID is set into the VM structure in the other + // thread right after pthread_create(), but we might run before the + // assignment! + t->thread = pthread_self(); + try { VALUE val = rb_vm_block_eval(t->body, t->argc, t->argv); GC_WB(&t->value, val); @@ -4493,15 +4498,15 @@ t->status = THREAD_ALIVE; t->in_cond_wait = false; - assert(pthread_mutex_init(&t->sleep_mutex, NULL) == 0); - assert(pthread_cond_init(&t->sleep_cond, NULL) == 0); + pthread_assert(pthread_mutex_init(&t->sleep_mutex, NULL)); + pthread_assert(pthread_cond_init(&t->sleep_cond, NULL)); } static inline void pre_wait(rb_vm_thread_t *t) { + pthread_assert(pthread_mutex_lock(&t->sleep_mutex)); t->status = THREAD_SLEEP; - assert(pthread_mutex_lock(&t->sleep_mutex) == 0); t->in_cond_wait = true; } @@ -4509,7 +4514,7 @@ post_wait(rb_vm_thread_t *t) { t->in_cond_wait = false; - assert(pthread_mutex_unlock(&t->sleep_mutex) == 0); + pthread_assert(pthread_mutex_unlock(&t->sleep_mutex)); if (t->status == THREAD_KILLED) { rb_vm_thread_throw_kill(); } @@ -4552,14 +4557,6 @@ post_wait(t); } -static inline void -signal_thread(rb_vm_thread_t *t) -{ - assert(pthread_mutex_lock(&t->sleep_mutex) == 0); - assert(pthread_cond_signal(&t->sleep_cond) == 0); - assert(pthread_mutex_unlock(&t->sleep_mutex) == 0); -} - extern "C" void rb_vm_thread_wakeup(rb_vm_thread_t *t) @@ -4568,7 +4565,9 @@ rb_raise(rb_eThreadError, "can't wake up thread from the death"); } if (t->status == THREAD_SLEEP && t->in_cond_wait) { - signal_thread(t); + pthread_assert(pthread_mutex_lock(&t->sleep_mutex)); + pthread_assert(pthread_cond_signal(&t->sleep_cond)); + pthread_assert(pthread_mutex_unlock(&t->sleep_mutex)); } } @@ -4576,22 +4575,26 @@ 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 { - if (t->in_cond_wait) { - // We are trying to kill a thread which is currently waiting - // for a condition variable (#sleep). Instead of canceling the - // thread, we are simply signaling the variable, and the thread - // will autodestroy itself, to work around a stack unwinding bug - // in the Mac OS X pthread implementation that messes our C++ - // exception handlers. - signal_thread(t); + if (t->status != THREAD_KILLED && t->status != THREAD_DEAD) { + t->status = THREAD_KILLED; + if (t->thread == pthread_self()) { + rb_vm_thread_throw_kill(); } else { - assert(pthread_cancel(t->thread) == 0); + pthread_assert(pthread_mutex_lock(&t->sleep_mutex)); + if (t->in_cond_wait) { + // We are trying to kill a thread which is currently waiting + // for a condition variable (#sleep). Instead of canceling the + // thread, we are simply signaling the variable, and the thread + // will autodestroy itself, to work around a stack unwinding + // bug in the Mac OS X pthread implementation that messes our + // C++ exception handlers. + pthread_assert(pthread_cond_signal(&t->sleep_cond)); + } + else { + pthread_assert(pthread_cancel(t->thread)); + } + pthread_assert(pthread_mutex_unlock(&t->sleep_mutex)); } } } @@ -4650,7 +4653,7 @@ RoxorCore::shared = new RoxorCore(); RoxorVM::main = new RoxorVM(); - assert(pthread_key_create(&RoxorVM::vm_thread_key, NULL) == 0); + pthread_assert(pthread_key_create(&RoxorVM::vm_thread_key, NULL)); setup_builtin_stubs(); Modified: MacRuby/branches/experimental/vm.h =================================================================== --- MacRuby/branches/experimental/vm.h 2009-06-27 02:04:20 UTC (rev 1943) +++ MacRuby/branches/experimental/vm.h 2009-06-27 05:57:24 UTC (rev 1944) @@ -80,6 +80,17 @@ #include <pthread.h> +#define pthread_assert(cmd) \ + do { \ + const int code = cmd; \ + if (code != 0) { \ + printf("pthread command `%s' failed: %s (%d)\n", \ + #cmd, strerror(code), code); \ + abort(); \ + } \ + } \ + while (0) + typedef struct rb_vm_thread { pthread_t thread; rb_vm_block_t *body;