X-Git-Url: http://wien.tomnetworks.com/gitweb/?p=hs-boehmgc.git;a=blobdiff_plain;f=gc-7.2%2Fpthread_stop_world.c;fp=gc-7.2%2Fpthread_stop_world.c;h=adb2fb2f50e7f8d9d071c9af790c2eb6a2a50e28;hp=0000000000000000000000000000000000000000;hb=324587ba93dc77f37406d41fd2a20d0e0d94fb1d;hpb=2a4ea609491b225a1ceb06da70396e93916f137a diff --git a/gc-7.2/pthread_stop_world.c b/gc-7.2/pthread_stop_world.c new file mode 100644 index 0000000..adb2fb2 --- /dev/null +++ b/gc-7.2/pthread_stop_world.c @@ -0,0 +1,879 @@ +/* + * Copyright (c) 1994 by Xerox Corporation. All rights reserved. + * Copyright (c) 1996 by Silicon Graphics. All rights reserved. + * Copyright (c) 1998 by Fergus Henderson. All rights reserved. + * Copyright (c) 2000-2009 by Hewlett-Packard Development Company. + * All rights reserved. + * + * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED + * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + * + * Permission is hereby granted to use or copy this program + * for any purpose, provided the above notices are retained on all copies. + * Permission to modify the code and to distribute modified code is granted, + * provided the above notices are retained, and a notice that the code was + * modified is included with the above copyright notice. + */ + +#include "private/pthread_support.h" + +#if defined(GC_PTHREADS) && !defined(GC_WIN32_THREADS) && \ + !defined(GC_DARWIN_THREADS) + +#ifdef NACL + +#include +#include + +STATIC int GC_nacl_num_gc_threads = 0; +STATIC __thread int GC_nacl_thread_idx = -1; +STATIC int GC_nacl_park_threads_now = 0; +STATIC pthread_t GC_nacl_thread_parker = -1; + +GC_INNER __thread GC_thread GC_nacl_gc_thread_self = NULL; + +int GC_nacl_thread_parked[MAX_NACL_GC_THREADS]; +int GC_nacl_thread_used[MAX_NACL_GC_THREADS]; + +#elif !defined(GC_OPENBSD_THREADS) + +#include +#include +#include +#include +#include "atomic_ops.h" + +/* It's safe to call original pthread_sigmask() here. */ +#undef pthread_sigmask + +#ifdef DEBUG_THREADS +# ifndef NSIG +# if defined(MAXSIG) +# define NSIG (MAXSIG+1) +# elif defined(_NSIG) +# define NSIG _NSIG +# elif defined(__SIGRTMAX) +# define NSIG (__SIGRTMAX+1) +# else + --> please fix it +# endif +# endif /* NSIG */ + + void GC_print_sig_mask(void) + { + sigset_t blocked; + int i; + + if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0) + ABORT("pthread_sigmask failed"); + GC_printf("Blocked: "); + for (i = 1; i < NSIG; i++) { + if (sigismember(&blocked, i)) + GC_printf("%d ", i); + } + GC_printf("\n"); + } +#endif /* DEBUG_THREADS */ + +/* Remove the signals that we want to allow in thread stopping */ +/* handler from a set. */ +STATIC void GC_remove_allowed_signals(sigset_t *set) +{ + if (sigdelset(set, SIGINT) != 0 + || sigdelset(set, SIGQUIT) != 0 + || sigdelset(set, SIGABRT) != 0 + || sigdelset(set, SIGTERM) != 0) { + ABORT("sigdelset() failed"); + } + +# ifdef MPROTECT_VDB + /* Handlers write to the thread structure, which is in the heap, */ + /* and hence can trigger a protection fault. */ + if (sigdelset(set, SIGSEGV) != 0 +# ifdef SIGBUS + || sigdelset(set, SIGBUS) != 0 +# endif + ) { + ABORT("sigdelset() failed"); + } +# endif +} + +static sigset_t suspend_handler_mask; + +STATIC volatile AO_t GC_stop_count = 0; + /* Incremented at the beginning of GC_stop_world. */ + +STATIC volatile AO_t GC_world_is_stopped = FALSE; + /* FALSE ==> it is safe for threads to restart, i.e. */ + /* they will see another suspend signal before they */ + /* are expected to stop (unless they have voluntarily */ + /* stopped). */ + +#ifdef GC_OSF1_THREADS + STATIC GC_bool GC_retry_signals = TRUE; +#else + STATIC GC_bool GC_retry_signals = FALSE; +#endif + +/* + * We use signals to stop threads during GC. + * + * Suspended threads wait in signal handler for SIG_THR_RESTART. + * That's more portable than semaphores or condition variables. + * (We do use sem_post from a signal handler, but that should be portable.) + * + * The thread suspension signal SIG_SUSPEND is now defined in gc_priv.h. + * Note that we can't just stop a thread; we need it to save its stack + * pointer(s) and acknowledge. + */ + +#ifndef SIG_THR_RESTART +# if defined(GC_HPUX_THREADS) || defined(GC_OSF1_THREADS) \ + || defined(GC_NETBSD_THREADS) +# ifdef _SIGRTMIN +# define SIG_THR_RESTART _SIGRTMIN + 5 +# else +# define SIG_THR_RESTART SIGRTMIN + 5 +# endif +# else +# define SIG_THR_RESTART SIGXCPU +# endif +#endif + +#ifdef GC_EXPLICIT_SIGNALS_UNBLOCK + /* Some targets (eg., Solaris) might require this to be called when */ + /* doing thread registering from the thread destructor. */ + GC_INNER void GC_unblock_gc_signals(void) + { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIG_SUSPEND); + sigaddset(&set, SIG_THR_RESTART); + if (pthread_sigmask(SIG_UNBLOCK, &set, NULL) != 0) + ABORT("pthread_sigmask failed"); + } +#endif /* GC_EXPLICIT_SIGNALS_UNBLOCK */ + +STATIC sem_t GC_suspend_ack_sem; + +#ifdef GC_NETBSD_THREADS +# define GC_NETBSD_THREADS_WORKAROUND + /* It seems to be necessary to wait until threads have restarted. */ + /* But it is unclear why that is the case. */ + STATIC sem_t GC_restart_ack_sem; +#endif + +STATIC void GC_suspend_handler_inner(ptr_t sig_arg, void *context); + +#ifdef SA_SIGINFO + /*ARGSUSED*/ + STATIC void GC_suspend_handler(int sig, siginfo_t *info, void *context) +#else + STATIC void GC_suspend_handler(int sig) +#endif +{ +# if defined(IA64) || defined(HP_PA) || defined(M68K) + int old_errno = errno; + GC_with_callee_saves_pushed(GC_suspend_handler_inner, (ptr_t)(word)sig); + errno = old_errno; +# else + /* We believe that in all other cases the full context is already */ + /* in the signal handler frame. */ + int old_errno = errno; +# ifndef SA_SIGINFO + void *context = 0; +# endif + GC_suspend_handler_inner((ptr_t)(word)sig, context); + errno = old_errno; +# endif +} + +/*ARGSUSED*/ +STATIC void GC_suspend_handler_inner(ptr_t sig_arg, void *context) +{ + pthread_t self = pthread_self(); + GC_thread me; + IF_CANCEL(int cancel_state;) + AO_t my_stop_count = AO_load(&GC_stop_count); + + if ((signed_word)sig_arg != SIG_SUSPEND) + ABORT("Bad signal in suspend_handler"); + + DISABLE_CANCEL(cancel_state); + /* pthread_setcancelstate is not defined to be async-signal-safe. */ + /* But the glibc version appears to be in the absence of */ + /* asynchronous cancellation. And since this signal handler */ + /* to block on sigsuspend, which is both async-signal-safe */ + /* and a cancellation point, there seems to be no obvious way */ + /* out of it. In fact, it looks to me like an async-signal-safe */ + /* cancellation point is inherently a problem, unless there is */ + /* some way to disable cancellation in the handler. */ +# ifdef DEBUG_THREADS + GC_log_printf("Suspending 0x%x\n", (unsigned)self); +# endif + + me = GC_lookup_thread(self); + /* The lookup here is safe, since I'm doing this on behalf */ + /* of a thread which holds the allocation lock in order */ + /* to stop the world. Thus concurrent modification of the */ + /* data structure is impossible. */ + if (me -> stop_info.last_stop_count == my_stop_count) { + /* Duplicate signal. OK if we are retrying. */ + if (!GC_retry_signals) { + WARN("Duplicate suspend signal in thread %p\n", self); + } + RESTORE_CANCEL(cancel_state); + return; + } +# ifdef SPARC + me -> stop_info.stack_ptr = GC_save_regs_in_stack(); +# else + me -> stop_info.stack_ptr = GC_approx_sp(); +# endif +# ifdef IA64 + me -> backing_store_ptr = GC_save_regs_in_stack(); +# endif + + /* Tell the thread that wants to stop the world that this */ + /* thread has been stopped. Note that sem_post() is */ + /* the only async-signal-safe primitive in LinuxThreads. */ + sem_post(&GC_suspend_ack_sem); + me -> stop_info.last_stop_count = my_stop_count; + + /* Wait until that thread tells us to restart by sending */ + /* this thread a SIG_THR_RESTART signal. */ + /* SIG_THR_RESTART should be masked at this point. Thus */ + /* there is no race. */ + /* We do not continue until we receive a SIG_THR_RESTART, */ + /* but we do not take that as authoritative. (We may be */ + /* accidentally restarted by one of the user signals we */ + /* don't block.) After we receive the signal, we use a */ + /* primitive and expensive mechanism to wait until it's */ + /* really safe to proceed. Under normal circumstances, */ + /* this code should not be executed. */ + do { + sigsuspend (&suspend_handler_mask); + } while (AO_load_acquire(&GC_world_is_stopped) + && AO_load(&GC_stop_count) == my_stop_count); + /* If the RESTART signal gets lost, we can still lose. That should */ + /* be less likely than losing the SUSPEND signal, since we don't do */ + /* much between the sem_post and sigsuspend. */ + /* We'd need more handshaking to work around that. */ + /* Simply dropping the sigsuspend call should be safe, but is */ + /* unlikely to be efficient. */ + +# ifdef DEBUG_THREADS + GC_log_printf("Continuing 0x%x\n", (unsigned)self); +# endif + RESTORE_CANCEL(cancel_state); +} + +STATIC void GC_restart_handler(int sig) +{ +# if defined(DEBUG_THREADS) || defined(GC_NETBSD_THREADS_WORKAROUND) + int old_errno = errno; /* Preserve errno value. */ +# endif + + if (sig != SIG_THR_RESTART) ABORT("Bad signal in suspend_handler"); + +# ifdef GC_NETBSD_THREADS_WORKAROUND + sem_post(&GC_restart_ack_sem); +# endif + + /* + ** Note: even if we don't do anything useful here, + ** it would still be necessary to have a signal handler, + ** rather than ignoring the signals, otherwise + ** the signals will not be delivered at all, and + ** will thus not interrupt the sigsuspend() above. + */ + +# ifdef DEBUG_THREADS + GC_log_printf("In GC_restart_handler for 0x%x\n", + (unsigned)pthread_self()); +# endif +# if defined(DEBUG_THREADS) || defined(GC_NETBSD_THREADS_WORKAROUND) + errno = old_errno; +# endif +} + +#endif /* !GC_OPENBSD_THREADS && !NACL */ + +#ifdef IA64 +# define IF_IA64(x) x +#else +# define IF_IA64(x) +#endif +/* We hold allocation lock. Should do exactly the right thing if the */ +/* world is stopped. Should not fail if it isn't. */ +GC_INNER void GC_push_all_stacks(void) +{ + GC_bool found_me = FALSE; + size_t nthreads = 0; + int i; + GC_thread p; + ptr_t lo, hi; + /* On IA64, we also need to scan the register backing store. */ + IF_IA64(ptr_t bs_lo; ptr_t bs_hi;) + pthread_t self = pthread_self(); + word total_size = 0; + + if (!GC_thr_initialized) GC_thr_init(); +# ifdef DEBUG_THREADS + GC_log_printf("Pushing stacks from thread 0x%x\n", (unsigned)self); +# endif + for (i = 0; i < THREAD_TABLE_SZ; i++) { + for (p = GC_threads[i]; p != 0; p = p -> next) { + if (p -> flags & FINISHED) continue; + ++nthreads; + if (THREAD_EQUAL(p -> id, self)) { + GC_ASSERT(!p->thread_blocked); +# ifdef SPARC + lo = (ptr_t)GC_save_regs_in_stack(); +# else + lo = GC_approx_sp(); +# endif + found_me = TRUE; + IF_IA64(bs_hi = (ptr_t)GC_save_regs_in_stack();) + } else { + lo = p -> stop_info.stack_ptr; + IF_IA64(bs_hi = p -> backing_store_ptr;) + } + if ((p -> flags & MAIN_THREAD) == 0) { + hi = p -> stack_end; + IF_IA64(bs_lo = p -> backing_store_end); + } else { + /* The original stack. */ + hi = GC_stackbottom; + IF_IA64(bs_lo = BACKING_STORE_BASE;) + } +# ifdef DEBUG_THREADS + GC_log_printf("Stack for thread 0x%x = [%p,%p)\n", + (unsigned)(p -> id), lo, hi); +# endif + if (0 == lo) ABORT("GC_push_all_stacks: sp not set!"); + GC_push_all_stack_sections(lo, hi, p -> traced_stack_sect); +# ifdef STACK_GROWS_UP + total_size += lo - hi; +# else + total_size += hi - lo; /* lo <= hi */ +# endif +# ifdef NACL + /* Push reg_storage as roots, this will cover the reg context. */ + GC_push_all_stack((ptr_t)p -> stop_info.reg_storage, + (ptr_t)(p -> stop_info.reg_storage + NACL_GC_REG_STORAGE_SIZE)); + total_size += NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t); +# endif +# ifdef IA64 +# ifdef DEBUG_THREADS + GC_log_printf("Reg stack for thread 0x%x = [%p,%p)\n", + (unsigned)p -> id, bs_lo, bs_hi); +# endif + /* FIXME: This (if p->id==self) may add an unbounded number of */ + /* entries, and hence overflow the mark stack, which is bad. */ + GC_push_all_register_sections(bs_lo, bs_hi, + THREAD_EQUAL(p -> id, self), + p -> traced_stack_sect); + total_size += bs_hi - bs_lo; /* bs_lo <= bs_hi */ +# endif + } + } + if (GC_print_stats == VERBOSE) { + GC_log_printf("Pushed %d thread stacks\n", (int)nthreads); + } + if (!found_me && !GC_in_thread_creation) + ABORT("Collecting from unknown thread"); + GC_total_stacksize = total_size; +} + +#ifdef DEBUG_THREADS + /* There seems to be a very rare thread stopping problem. To help us */ + /* debug that, we save the ids of the stopping thread. */ + pthread_t GC_stopping_thread; + int GC_stopping_pid = 0; +#endif + +#ifdef PLATFORM_ANDROID + extern int tkill(pid_t tid, int sig); /* from sys/linux-unistd.h */ + + 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 /* PLATFORM_ANDROID */ + +/* We hold the allocation lock. Suspend all threads that might */ +/* still be running. Return the number of suspend signals that */ +/* were sent. */ +STATIC int GC_suspend_all(void) +{ + int n_live_threads = 0; + int i; + +# ifndef NACL + GC_thread p; +# ifndef GC_OPENBSD_THREADS + int result; +# endif + pthread_t self = pthread_self(); + +# ifdef DEBUG_THREADS + GC_stopping_thread = self; + GC_stopping_pid = getpid(); +# endif + for (i = 0; i < THREAD_TABLE_SZ; i++) { + for (p = GC_threads[i]; p != 0; p = p -> next) { + if (!THREAD_EQUAL(p -> id, self)) { + if (p -> flags & FINISHED) continue; + if (p -> thread_blocked) /* Will wait */ continue; +# ifndef GC_OPENBSD_THREADS + if (p -> stop_info.last_stop_count == GC_stop_count) continue; + n_live_threads++; +# endif +# ifdef DEBUG_THREADS + GC_log_printf("Sending suspend signal to 0x%x\n", + (unsigned)(p -> id)); +# endif + +# ifdef GC_OPENBSD_THREADS + { + stack_t stack; + if (pthread_suspend_np(p -> id) != 0) + ABORT("pthread_suspend_np failed"); + if (pthread_stackseg_np(p->id, &stack)) + ABORT("pthread_stackseg_np failed"); + p -> stop_info.stack_ptr = (ptr_t)stack.ss_sp - stack.ss_size; + } +# else +# 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? */ + n_live_threads--; + break; + case 0: + break; + default: + ABORT("pthread_kill failed"); + } +# endif + } + } + } + +# else /* NACL */ +# ifndef NACL_PARK_WAIT_NANOSECONDS +# define NACL_PARK_WAIT_NANOSECONDS (100 * 1000) +# endif +# ifdef DEBUG_THREADS + GC_log_printf("pthread_stop_world: num_threads %d\n", + GC_nacl_num_gc_threads - 1); +# endif + GC_nacl_thread_parker = pthread_self(); + GC_nacl_park_threads_now = 1; +# ifdef DEBUG_THREADS + GC_stopping_thread = GC_nacl_thread_parker; + GC_stopping_pid = getpid(); +# endif + + while (1) { + 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 < GC_nacl_num_gc_threads; i++) { + if (GC_nacl_thread_used[i] == 1) { + num_used++; + if (GC_nacl_thread_parked[i] == 1) { + num_threads_parked++; + } + } + } + /* -1 for the current thread. */ + if (num_threads_parked >= GC_nacl_num_gc_threads - 1) + break; + ts.tv_sec = 0; + ts.tv_nsec = NACL_PARK_WAIT_NANOSECONDS; +# ifdef DEBUG_THREADS + GC_log_printf("Sleep waiting for %d threads to park...\n", + GC_nacl_num_gc_threads - num_threads_parked - 1); +# endif + /* This requires _POSIX_TIMERS feature. */ + nanosleep(&ts, 0); + } +# endif /* NACL */ + return n_live_threads; +} + +GC_INNER void GC_stop_world(void) +{ +# if !defined(GC_OPENBSD_THREADS) && !defined(NACL) + int i; + int n_live_threads; + int code; +# endif + GC_ASSERT(I_HOLD_LOCK()); +# ifdef DEBUG_THREADS + GC_log_printf("Stopping the world from 0x%x\n", (unsigned)pthread_self()); +# endif + + /* 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, */ + /* and we have the lock. */ +# ifdef PARALLEL_MARK + if (GC_parallel) { + GC_acquire_mark_lock(); + GC_ASSERT(GC_fl_builder_count == 0); + /* We should have previously waited for it to become zero. */ + } +# endif /* PARALLEL_MARK */ + +# if defined(GC_OPENBSD_THREADS) || defined(NACL) + (void)GC_suspend_all(); +# else + AO_store(&GC_stop_count, GC_stop_count+1); + /* Only concurrent reads are possible. */ + AO_store_release(&GC_world_is_stopped, TRUE); + n_live_threads = GC_suspend_all(); + + if (GC_retry_signals) { + unsigned long wait_usecs = 0; /* Total wait since retry. */ +# define WAIT_UNIT 3000 +# define RETRY_INTERVAL 100000 + for (;;) { + int ack_count; + + sem_getvalue(&GC_suspend_ack_sem, &ack_count); + if (ack_count == n_live_threads) break; + if (wait_usecs > RETRY_INTERVAL) { + int newly_sent = GC_suspend_all(); + + if (GC_print_stats) { + GC_log_printf("Resent %d signals after timeout\n", newly_sent); + } + sem_getvalue(&GC_suspend_ack_sem, &ack_count); + if (newly_sent < n_live_threads - ack_count) { + WARN("Lost some threads during GC_stop_world?!\n",0); + n_live_threads = ack_count + newly_sent; + } + wait_usecs = 0; + } + usleep(WAIT_UNIT); + wait_usecs += WAIT_UNIT; + } + } + + for (i = 0; i < n_live_threads; i++) { + retry: + if (0 != (code = sem_wait(&GC_suspend_ack_sem))) { + /* On Linux, sem_wait is documented to always return zero. */ + /* But the documentation appears to be incorrect. */ + if (errno == EINTR) { + /* Seems to happen with some versions of gdb. */ + goto retry; + } + ABORT("sem_wait for handler failed"); + } + } +# endif + +# ifdef PARALLEL_MARK + if (GC_parallel) + GC_release_mark_lock(); +# endif +# ifdef DEBUG_THREADS + GC_log_printf("World stopped from 0x%x\n", (unsigned)pthread_self()); + GC_stopping_thread = 0; +# endif +} + +#ifdef NACL +# if defined(__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" (GC_nacl_gc_thread_self->stop_info.stack_ptr)); \ + BCOPY(GC_nacl_gc_thread_self->stop_info.stack_ptr, \ + GC_nacl_gc_thread_self->stop_info.reg_storage, \ + NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t)); \ + __asm__ __volatile__ ("naclasp $48, %r15"); \ + } while (0) +# elif defined(__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" (GC_nacl_gc_thread_self->stop_info.stack_ptr)); \ + BCOPY(GC_nacl_gc_thread_self->stop_info.stack_ptr, \ + GC_nacl_gc_thread_self->stop_info.reg_storage, \ + NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t));\ + __asm__ __volatile__ ("add $16, %esp"); \ + } while (0) +# else +# error FIXME for non-amd64/x86 NaCl +# endif + + GC_API_OSCALL void nacl_pre_syscall_hook(void) + { + if (GC_nacl_thread_idx != -1) { + NACL_STORE_REGS(); + GC_nacl_gc_thread_self->stop_info.stack_ptr = GC_approx_sp(); + GC_nacl_thread_parked[GC_nacl_thread_idx] = 1; + } + } + + GC_API_OSCALL void __nacl_suspend_thread_if_needed(void) + { + if (GC_nacl_park_threads_now) { + pthread_t self = pthread_self(); + + /* Don't try to park the thread parker. */ + if (GC_nacl_thread_parker == self) + return; + + /* This can happen when a thread is created outside of the GC */ + /* system (wthread mostly). */ + if (GC_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 (!GC_nacl_thread_parked[GC_nacl_thread_idx]) { + NACL_STORE_REGS(); + GC_nacl_gc_thread_self->stop_info.stack_ptr = GC_approx_sp(); + } + GC_nacl_thread_parked[GC_nacl_thread_idx] = 1; + while (GC_nacl_park_threads_now) { + /* Just spin. */ + } + GC_nacl_thread_parked[GC_nacl_thread_idx] = 0; + + /* Clear out the reg storage for next suspend. */ + BZERO(GC_nacl_gc_thread_self->stop_info.reg_storage, + NACL_GC_REG_STORAGE_SIZE * sizeof(ptr_t)); + } + } + + GC_API_OSCALL void nacl_post_syscall_hook(void) + { + /* Calling __nacl_suspend_thread_if_needed right away should */ + /* guarantee we don't mutate the GC set. */ + __nacl_suspend_thread_if_needed(); + if (GC_nacl_thread_idx != -1) { + GC_nacl_thread_parked[GC_nacl_thread_idx] = 0; + } + } + + STATIC GC_bool GC_nacl_thread_parking_inited = FALSE; + STATIC pthread_mutex_t GC_nacl_thread_alloc_lock = PTHREAD_MUTEX_INITIALIZER; + + GC_INNER void GC_nacl_initialize_gc_thread(void) + { + int i; + pthread_mutex_lock(&GC_nacl_thread_alloc_lock); + if (!GC_nacl_thread_parking_inited) { + BZERO(GC_nacl_thread_parked, sizeof(GC_nacl_thread_parked)); + BZERO(GC_nacl_thread_used, sizeof(GC_nacl_thread_used)); + GC_nacl_thread_parking_inited = TRUE; + } + GC_ASSERT(GC_nacl_num_gc_threads <= MAX_NACL_GC_THREADS); + for (i = 0; i < MAX_NACL_GC_THREADS; i++) { + if (GC_nacl_thread_used[i] == 0) { + GC_nacl_thread_used[i] = 1; + GC_nacl_thread_idx = i; + GC_nacl_num_gc_threads++; + break; + } + } + pthread_mutex_unlock(&GC_nacl_thread_alloc_lock); + } + + GC_INNER void GC_nacl_shutdown_gc_thread(void) + { + pthread_mutex_lock(&GC_nacl_thread_alloc_lock); + GC_ASSERT(GC_nacl_thread_idx >= 0); + GC_ASSERT(GC_nacl_thread_idx < MAX_NACL_GC_THREADS); + GC_ASSERT(GC_nacl_thread_used[GC_nacl_thread_idx] != 0); + GC_nacl_thread_used[GC_nacl_thread_idx] = 0; + GC_nacl_thread_idx = -1; + GC_nacl_num_gc_threads--; + pthread_mutex_unlock(&GC_nacl_thread_alloc_lock); + } +#endif /* NACL */ + +/* Caller holds allocation lock, and has held it continuously since */ +/* the world stopped. */ +GC_INNER void GC_start_world(void) +{ +# ifndef NACL + pthread_t self = pthread_self(); + register int i; + register GC_thread p; +# ifndef GC_OPENBSD_THREADS + register int n_live_threads = 0; + register int result; +# endif +# ifdef GC_NETBSD_THREADS_WORKAROUND + int code; +# endif + +# ifdef DEBUG_THREADS + GC_log_printf("World starting\n"); +# endif + +# ifndef GC_OPENBSD_THREADS + AO_store(&GC_world_is_stopped, FALSE); +# endif + for (i = 0; i < THREAD_TABLE_SZ; i++) { + for (p = GC_threads[i]; p != 0; p = p -> next) { + if (!THREAD_EQUAL(p -> id, self)) { + if (p -> flags & FINISHED) continue; + if (p -> thread_blocked) continue; +# ifndef GC_OPENBSD_THREADS + n_live_threads++; +# endif +# ifdef DEBUG_THREADS + GC_log_printf("Sending restart signal to 0x%x\n", + (unsigned)(p -> id)); +# endif + +# ifdef GC_OPENBSD_THREADS + if (pthread_resume_np(p -> id) != 0) + ABORT("pthread_resume_np failed"); +# else +# 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? */ + n_live_threads--; + break; + case 0: + break; + default: + ABORT("pthread_kill failed"); + } +# endif + } + } + } +# ifdef GC_NETBSD_THREADS_WORKAROUND + for (i = 0; i < n_live_threads; i++) { + while (0 != (code = sem_wait(&GC_restart_ack_sem))) { + if (errno != EINTR) { + if (GC_print_stats) + GC_log_printf("sem_wait() returned %d\n", code); + ABORT("sem_wait() for restart handler failed"); + } + } + } +# endif +# ifdef DEBUG_THREADS + GC_log_printf("World started\n"); +# endif +# else /* NACL */ +# ifdef DEBUG_THREADS + GC_log_printf("World starting...\n"); +# endif + GC_nacl_park_threads_now = 0; +# endif +} + +GC_INNER void GC_stop_init(void) +{ +# if !defined(GC_OPENBSD_THREADS) && !defined(NACL) + struct sigaction act; + + if (sem_init(&GC_suspend_ack_sem, GC_SEM_INIT_PSHARED, 0) != 0) + ABORT("sem_init failed"); +# ifdef GC_NETBSD_THREADS_WORKAROUND + if (sem_init(&GC_restart_ack_sem, GC_SEM_INIT_PSHARED, 0) != 0) + ABORT("sem_init failed"); +# endif + +# ifdef SA_RESTART + act.sa_flags = SA_RESTART +# else + act.sa_flags = 0 +# endif +# ifdef SA_SIGINFO + | SA_SIGINFO +# endif + ; + if (sigfillset(&act.sa_mask) != 0) { + ABORT("sigfillset() failed"); + } +# ifdef GC_RTEMS_PTHREADS + if(sigprocmask(SIG_UNBLOCK, &act.sa_mask, NULL) != 0) { + ABORT("rtems sigprocmask() failed"); + } +# endif + GC_remove_allowed_signals(&act.sa_mask); + /* SIG_THR_RESTART is set in the resulting mask. */ + /* It is unmasked by the handler when necessary. */ +# ifdef SA_SIGINFO + act.sa_sigaction = GC_suspend_handler; +# else + act.sa_handler = GC_suspend_handler; +# endif + if (sigaction(SIG_SUSPEND, &act, NULL) != 0) { + ABORT("Cannot set SIG_SUSPEND handler"); + } + +# ifdef SA_SIGINFO + act.sa_flags &= ~ SA_SIGINFO; +# endif + act.sa_handler = GC_restart_handler; + if (sigaction(SIG_THR_RESTART, &act, NULL) != 0) { + ABORT("Cannot set SIG_THR_RESTART handler"); + } + + /* Initialize suspend_handler_mask. It excludes SIG_THR_RESTART. */ + if (sigfillset(&suspend_handler_mask) != 0) ABORT("sigfillset() failed"); + GC_remove_allowed_signals(&suspend_handler_mask); + if (sigdelset(&suspend_handler_mask, SIG_THR_RESTART) != 0) + ABORT("sigdelset() failed"); + + /* Check for GC_RETRY_SIGNALS. */ + if (0 != GETENV("GC_RETRY_SIGNALS")) { + GC_retry_signals = TRUE; + } + if (0 != GETENV("GC_NO_RETRY_SIGNALS")) { + GC_retry_signals = FALSE; + } + if (GC_print_stats && GC_retry_signals) { + GC_log_printf("Will retry suspend signal if necessary\n"); + } +# endif /* !GC_OPENBSD_THREADS && !NACL */ +} + +#endif