X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=libgc%2Fpthread_stop_world.c;h=8b25376b54ab6d691176ba61f0d7345c6b5c1ab6;hb=1e726ce7a38a92860acab28f4427813d2ba14c13;hp=001a9e05c5c158b42ed6073fc8978df9df96ea89;hpb=0abc2e6270020edc4a5b4c66f93b4ae582815f20;p=mono.git diff --git a/libgc/pthread_stop_world.c b/libgc/pthread_stop_world.c index 001a9e05c5c..8b25376b54a 100644 --- a/libgc/pthread_stop_world.c +++ b/libgc/pthread_stop_world.c @@ -2,12 +2,39 @@ #if defined(GC_PTHREADS) && !defined(GC_SOLARIS_THREADS) \ && !defined(GC_IRIX_THREADS) && !defined(GC_WIN32_THREADS) \ - && !defined(GC_DARWIN_THREADS) && !defined(GC_AIX_THREADS) + && !defined(GC_DARWIN_THREADS) && !defined(GC_AIX_THREADS) \ + && !defined(GC_OPENBSD_THREADS) #include #include #include #include +#include + +/* work around a dlopen issue (bug #75390), undefs to avoid warnings with redefinitions */ +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#include "mono/utils/mono-compiler.h" + +#ifdef MONO_DEBUGGER_SUPPORTED +#include "include/libgc-mono-debugger.h" +#endif + +#ifdef NACL +volatile int __nacl_thread_suspension_needed = 0; +pthread_t nacl_thread_parker = -1; + +volatile int nacl_thread_parked[MAX_NACL_GC_THREADS]; +volatile int nacl_thread_used[MAX_NACL_GC_THREADS]; +volatile int nacl_thread_parking_inited = 0; +volatile int nacl_num_gc_threads = 0; +pthread_mutex_t nacl_thread_alloc_lock = PTHREAD_MUTEX_INITIALIZER; +__thread int nacl_thread_idx = -1; +__thread GC_thread nacl_gc_thread_self = NULL; +#endif #if DEBUG_THREADS @@ -23,6 +50,7 @@ # endif #endif +#ifndef NACL void GC_print_sig_mask() { sigset_t blocked; @@ -36,7 +64,7 @@ void GC_print_sig_mask() } GC_printf0("\n"); } - +#endif /* NACL */ #endif /* Remove the signals that we want to allow in thread stopping */ @@ -101,8 +129,9 @@ word GC_stop_count; /* Incremented at the beginning of GC_stop_world. */ sem_t GC_suspend_ack_sem; -void GC_suspend_handler(int sig) +static void _GC_suspend_handler(int sig) { +#ifndef NACL int dummy; pthread_t my_thread = pthread_self(); GC_thread me; @@ -163,12 +192,27 @@ void GC_suspend_handler(int sig) /* to accidentally leave a RESTART signal pending, thus causing us to */ /* continue prematurely in a future round. */ + /* Tell the thread that wants to start the world that this */ + /* thread has been started. Note that sem_post() is */ + /* the only async-signal-safe primitive in LinuxThreads. */ + sem_post(&GC_suspend_ack_sem); + + #if DEBUG_THREADS GC_printf1("Continuing 0x%lx\n", my_thread); #endif + +#endif /* NACL */ } -void GC_restart_handler(int sig) +void GC_suspend_handler(int sig) +{ + int old_errno = errno; + _GC_suspend_handler(sig); + errno = old_errno; +} + +static void _GC_restart_handler(int sig) { pthread_t my_thread = pthread_self(); GC_thread me; @@ -246,12 +290,20 @@ static void pthread_push_all_stacks() (unsigned long) lo, (unsigned long) hi); #endif if (0 == lo) ABORT("GC_push_all_stacks: sp not set!\n"); + if (p->altstack && lo >= p->altstack && lo <= p->altstack + p->altstack_size) + hi = p->altstack + p->altstack_size; + /* FIXME: Need to scan the normal stack too, but how ? */ + # ifdef STACK_GROWS_UP /* We got them backwards! */ GC_push_all_stack(hi, lo); # else GC_push_all_stack(lo, hi); # endif +# ifdef NACL + /* Push reg_storage as roots, this will cover the reg context */ + GC_push_all_stack(p -> stop_info.reg_storage, p -> stop_info.reg_storage + NACL_GC_REG_STORAGE_SIZE); +# endif # ifdef IA64 # if DEBUG_THREADS GC_printf3("Reg stack for thread 0x%lx = [%lx,%lx)\n", @@ -270,11 +322,18 @@ static void pthread_push_all_stacks() ABORT("Collecting from unknown thread."); } +void GC_restart_handler(int sig) +{ + int old_errno = errno; + _GC_restart_handler (sig); + errno = old_errno; +} + /* We hold allocation lock. Should do exactly the right thing if the */ /* world is stopped. Should not fail if it isn't. */ void GC_push_all_stacks() { - gc_thread_vtable->push_all_stacks(); + pthread_push_all_stacks(); } /* There seems to be a very rare thread stopping problem. To help us */ @@ -282,11 +341,29 @@ void GC_push_all_stacks() pthread_t GC_stopping_thread; int GC_stopping_pid; +#ifdef PLATFORM_ANDROID +static +int android_thread_kill(pid_t tid, int sig) +{ + int ret; + int old_errno = errno; + + ret = tkill(tid, sig); + if (ret < 0) { + ret = errno; + errno = old_errno; + } + + return ret; +} +#endif + /* We hold the allocation lock. Suspend all threads that might */ /* still be running. Return the number of suspend signals that */ /* were sent. */ int GC_suspend_all() { +#ifndef NACL int n_live_threads = 0; int i; GC_thread p; @@ -305,8 +382,12 @@ int GC_suspend_all() #if DEBUG_THREADS GC_printf1("Sending suspend signal to 0x%lx\n", p -> id); #endif - + +#ifndef PLATFORM_ANDROID result = pthread_kill(p -> id, SIG_SUSPEND); +#else + result = android_thread_kill(p -> kernel_id, SIG_SUSPEND); +#endif switch(result) { case ESRCH: /* Not really there anymore. Possible? */ @@ -321,11 +402,15 @@ int GC_suspend_all() } } return n_live_threads; +#else /* NACL */ + return 0; +#endif } /* Caller holds allocation lock. */ static void pthread_stop_world() { +#ifndef NACL int i; int n_live_threads; int code; @@ -333,7 +418,7 @@ static void pthread_stop_world() #if DEBUG_THREADS GC_printf1("Stopping the world from 0x%lx\n", pthread_self()); #endif - + n_live_threads = GC_suspend_all(); if (GC_retry_signals) { @@ -366,20 +451,151 @@ static void pthread_stop_world() } } for (i = 0; i < n_live_threads; i++) { - if (0 != (code = sem_wait(&GC_suspend_ack_sem))) { - GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code); - ABORT("sem_wait for handler failed"); + while (0 != (code = sem_wait(&GC_suspend_ack_sem))) { + if (errno != EINTR) { + GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code); + ABORT("sem_wait for handler failed"); + } } } #if DEBUG_THREADS GC_printf1("World stopped from 0x%lx\n", pthread_self()); #endif GC_stopping_thread = 0; /* debugging only */ +#else /* NACL */ + GC_thread p; + int i; + int num_sleeps = 0; + + #if DEBUG_THREADS + GC_printf1("pthread_stop_world: num_threads %d\n", nacl_num_gc_threads - 1); + #endif + nacl_thread_parker = pthread_self(); + __nacl_thread_suspension_needed = 1; + + while (1) { + #define NACL_PARK_WAIT_NANOSECONDS 100000 + #define NANOS_PER_SECOND 1000000000 + int num_threads_parked = 0; + struct timespec ts; + int num_used = 0; + /* Check the 'parked' flag for each thread the GC knows about */ + for (i = 0; i < MAX_NACL_GC_THREADS && num_used < nacl_num_gc_threads; i++) { + if (nacl_thread_used[i] == 1) { + num_used++; + if (nacl_thread_parked[i] == 1) { + num_threads_parked++; + } + } + } + /* -1 for the current thread */ + if (num_threads_parked >= nacl_num_gc_threads - 1) + break; + ts.tv_sec = 0; + ts.tv_nsec = NACL_PARK_WAIT_NANOSECONDS; + #if DEBUG_THREADS + GC_printf1("sleeping waiting for %d threads to park...\n", nacl_num_gc_threads - num_threads_parked - 1); + #endif + nanosleep(&ts, 0); + if (++num_sleeps > NANOS_PER_SECOND / NACL_PARK_WAIT_NANOSECONDS) { + GC_printf1("GC appears stalled waiting for %d threads to park...\n", nacl_num_gc_threads - num_threads_parked - 1); + num_sleeps = 0; + } + } + +#endif /* NACL */ +} + + +#ifdef NACL + +#if __x86_64__ + +#define NACL_STORE_REGS() \ + do { \ + __asm__ __volatile__ ("push %rbx");\ + __asm__ __volatile__ ("push %rbp");\ + __asm__ __volatile__ ("push %r12");\ + __asm__ __volatile__ ("push %r13");\ + __asm__ __volatile__ ("push %r14");\ + __asm__ __volatile__ ("push %r15");\ + __asm__ __volatile__ ("mov %%esp, %0" : "=m" (nacl_gc_thread_self->stop_info.stack_ptr));\ + memcpy(nacl_gc_thread_self->stop_info.reg_storage, nacl_gc_thread_self->stop_info.stack_ptr, NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t));\ + __asm__ __volatile__ ("naclasp $48, %r15");\ + } while (0) + +#elif __i386__ + +#define NACL_STORE_REGS() \ + do { \ + __asm__ __volatile__ ("push %ebx");\ + __asm__ __volatile__ ("push %ebp");\ + __asm__ __volatile__ ("push %esi");\ + __asm__ __volatile__ ("push %edi");\ + __asm__ __volatile__ ("mov %%esp, %0" : "=m" (nacl_gc_thread_self->stop_info.stack_ptr));\ + memcpy(nacl_gc_thread_self->stop_info.reg_storage, nacl_gc_thread_self->stop_info.stack_ptr, NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t));\ + __asm__ __volatile__ ("add $16, %esp");\ + } while (0) + +#endif + +void nacl_pre_syscall_hook() +{ + int local_dummy = 0; + if (nacl_thread_idx != -1) { + NACL_STORE_REGS(); + nacl_gc_thread_self->stop_info.stack_ptr = (ptr_t)(&local_dummy); + nacl_thread_parked[nacl_thread_idx] = 1; + } } +void __nacl_suspend_thread_if_needed(); + +void nacl_post_syscall_hook() +{ + /* Calling __nacl_suspend_thread_if_needed() right away should guarantee we don't mutate the GC set. */ + __nacl_suspend_thread_if_needed(); + if (nacl_thread_idx != -1) { + nacl_thread_parked[nacl_thread_idx] = 0; + } +} + +void __nacl_suspend_thread_if_needed() { + if (__nacl_thread_suspension_needed) { + pthread_t self = pthread_self(); + int local_dummy = 0; + /* Don't try to park the thread parker. */ + if (nacl_thread_parker == self) + return; + + /* This can happen when a thread is created */ + /* outside of the GC system (wthread mostly). */ + if (nacl_thread_idx < 0) + return; + + /* If it was already 'parked', we're returning from a syscall, */ + /* so don't bother storing registers again, the GC has a set. */ + if (!nacl_thread_parked[nacl_thread_idx]) { + NACL_STORE_REGS(); + nacl_gc_thread_self->stop_info.stack_ptr = (ptr_t)(&local_dummy); + } + nacl_thread_parked[nacl_thread_idx] = 1; + while (__nacl_thread_suspension_needed) + ; /* spin */ + nacl_thread_parked[nacl_thread_idx] = 0; + + /* Clear out the reg storage for next suspend. */ + memset(nacl_gc_thread_self->stop_info.reg_storage, 0, NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t)); + } +} + +#endif /* NACL */ + /* Caller holds allocation lock. */ void GC_stop_world() { + if (GC_notify_event) + GC_notify_event (GC_EVENT_PRE_STOP_WORLD); /* Make sure all free list construction has stopped before we start. */ /* No new construction can start, since free list construction is */ /* required to acquire and release the GC lock before it starts, */ @@ -390,25 +606,36 @@ void GC_stop_world() /* We should have previously waited for it to become zero. */ # endif /* PARALLEL_MARK */ ++GC_stop_count; - gc_thread_vtable->stop_world (); +#ifdef MONO_DEBUGGER_SUPPORTED + if (gc_thread_vtable && gc_thread_vtable->stop_world) + gc_thread_vtable->stop_world (); + else +#endif + pthread_stop_world (); # ifdef PARALLEL_MARK GC_release_mark_lock(); # endif + if (GC_notify_event) + GC_notify_event (GC_EVENT_POST_STOP_WORLD); } /* Caller holds allocation lock, and has held it continuously since */ /* the world stopped. */ static void pthread_start_world() { +#ifndef NACL pthread_t my_thread = pthread_self(); register int i; register GC_thread p; register int n_live_threads = 0; register int result; + int code; # if DEBUG_THREADS GC_printf0("World starting\n"); # endif + if (GC_notify_event) + GC_notify_event (GC_EVENT_PRE_START_WORLD); for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { @@ -419,8 +646,12 @@ static void pthread_start_world() #if DEBUG_THREADS GC_printf1("Sending restart signal to 0x%lx\n", p -> id); #endif - + +#ifndef PLATFORM_ANDROID result = pthread_kill(p -> id, SIG_THR_RESTART); +#else + result = android_thread_kill(p -> kernel_id, SIG_THR_RESTART); +#endif switch(result) { case ESRCH: /* Not really there anymore. Possible? */ @@ -434,17 +665,49 @@ static void pthread_start_world() } } } + + #if DEBUG_THREADS + GC_printf0 ("All threads signaled"); + #endif + + for (i = 0; i < n_live_threads; i++) { + while (0 != (code = sem_wait(&GC_suspend_ack_sem))) { + if (errno != EINTR) { + GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code); + ABORT("sem_wait for handler failed"); + } + } + } + + if (GC_notify_event) + GC_notify_event (GC_EVENT_POST_START_WORLD); #if DEBUG_THREADS GC_printf0("World started\n"); #endif +#else /* NACL */ + if (GC_notify_event) + GC_notify_event (GC_EVENT_PRE_START_WORLD); +# if DEBUG_THREADS + GC_printf0("World starting\n"); +# endif + __nacl_thread_suspension_needed = 0; + if (GC_notify_event) + GC_notify_event (GC_EVENT_POST_START_WORLD); +#endif /* NACL */ } void GC_start_world() { - gc_thread_vtable->start_world(); +#ifdef MONO_DEBUGGER_SUPPORTED + if (gc_thread_vtable && gc_thread_vtable->start_world) + gc_thread_vtable->start_world(); + else +#endif + pthread_start_world (); } static void pthread_stop_init() { +#ifndef NACL struct sigaction act; if (sem_init(&GC_suspend_ack_sem, 0, 0) != 0) @@ -485,25 +748,33 @@ static void pthread_stop_init() { GC_printf0("Will retry suspend signal if necessary.\n"); } # endif +#endif /* NACL */ } /* We hold the allocation lock. */ void GC_stop_init() { - gc_thread_vtable->initialize (); +#ifdef MONO_DEBUGGER_SUPPORTED + if (gc_thread_vtable && gc_thread_vtable->initialize) + gc_thread_vtable->initialize (); + else +#endif + pthread_stop_init (); } -/* - * This is used by the Mono Debugger to stop/start the world. - */ -GCThreadFunctions pthread_thread_vtable = { - pthread_stop_init, - - pthread_stop_world, - pthread_push_all_stacks, - pthread_start_world -}; - -GCThreadFunctions *gc_thread_vtable = &pthread_thread_vtable; +#ifdef MONO_DEBUGGER_SUPPORTED + +GCThreadFunctions *gc_thread_vtable = NULL; + +void * +GC_mono_debugger_get_stack_ptr (void) +{ + GC_thread me; + + me = GC_lookup_thread (pthread_self ()); + return &me->stop_info.stack_ptr; +} + +#endif #endif