| /* |
| * Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved. |
| * Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. |
| * Copyright (c) 1999 by Hewlett-Packard 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. |
| */ |
| /* |
| * Support code for Irix (>=6.2) Pthreads. This relies on properties |
| * not guaranteed by the Pthread standard. It may or may not be portable |
| * to other implementations. |
| * |
| * This now also includes an initial attempt at thread support for |
| * HP/UX 11. |
| * |
| * Note that there is a lot of code duplication between linux_threads.c |
| * and irix_threads.c; any changes made here may need to be reflected |
| * there too. |
| */ |
| |
| # if defined(GC_IRIX_THREADS) |
| |
| # include "private/gc_priv.h" |
| # include <pthread.h> |
| # include <semaphore.h> |
| # include <time.h> |
| # include <errno.h> |
| # include <unistd.h> |
| # include <sys/mman.h> |
| # include <sys/time.h> |
| |
| #undef pthread_create |
| #undef pthread_sigmask |
| #undef pthread_join |
| #undef pthread_detach |
| |
| void GC_thr_init(); |
| |
| #if 0 |
| void GC_print_sig_mask() |
| { |
| sigset_t blocked; |
| int i; |
| |
| if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0) |
| ABORT("pthread_sigmask"); |
| GC_printf0("Blocked: "); |
| for (i = 1; i <= MAXSIG; i++) { |
| if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); } |
| } |
| GC_printf0("\n"); |
| } |
| #endif |
| |
| /* We use the allocation lock to protect thread-related data structures. */ |
| |
| /* The set of all known threads. We intercept thread creation and */ |
| /* joins. We never actually create detached threads. We allocate all */ |
| /* new thread stacks ourselves. These allow us to maintain this */ |
| /* data structure. */ |
| /* Protected by GC_thr_lock. */ |
| /* Some of this should be declared volatile, but that's incosnsistent */ |
| /* with some library routine declarations. */ |
| typedef struct GC_Thread_Rep { |
| struct GC_Thread_Rep * next; /* More recently allocated threads */ |
| /* with a given pthread id come */ |
| /* first. (All but the first are */ |
| /* guaranteed to be dead, but we may */ |
| /* not yet have registered the join.) */ |
| pthread_t id; |
| word stop; |
| # define NOT_STOPPED 0 |
| # define PLEASE_STOP 1 |
| # define STOPPED 2 |
| word flags; |
| # define FINISHED 1 /* Thread has exited. */ |
| # define DETACHED 2 /* Thread is intended to be detached. */ |
| # define CLIENT_OWNS_STACK 4 |
| /* Stack was supplied by client. */ |
| ptr_t stack; |
| ptr_t stack_ptr; /* Valid only when stopped. */ |
| /* But must be within stack region at */ |
| /* all times. */ |
| size_t stack_size; /* 0 for original thread. */ |
| void * status; /* Used only to avoid premature */ |
| /* reclamation of any data it might */ |
| /* reference. */ |
| } * GC_thread; |
| |
| GC_thread GC_lookup_thread(pthread_t id); |
| |
| /* |
| * The only way to suspend threads given the pthread interface is to send |
| * signals. Unfortunately, this means we have to reserve |
| * a signal, and intercept client calls to change the signal mask. |
| * We use SIG_SUSPEND, defined in gc_priv.h. |
| */ |
| |
| pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER; |
| /* Number of threads stopped so far */ |
| pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER; |
| pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER; |
| |
| void GC_suspend_handler(int sig) |
| { |
| int dummy; |
| GC_thread me; |
| sigset_t all_sigs; |
| sigset_t old_sigs; |
| int i; |
| |
| if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler"); |
| me = GC_lookup_thread(pthread_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 (PLEASE_STOP != me -> stop) { |
| /* Misdirected signal. */ |
| pthread_mutex_unlock(&GC_suspend_lock); |
| return; |
| } |
| pthread_mutex_lock(&GC_suspend_lock); |
| me -> stack_ptr = (ptr_t)(&dummy); |
| me -> stop = STOPPED; |
| pthread_cond_signal(&GC_suspend_ack_cv); |
| pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock); |
| pthread_mutex_unlock(&GC_suspend_lock); |
| /* GC_printf1("Continuing 0x%x\n", pthread_self()); */ |
| } |
| |
| |
| GC_bool GC_thr_initialized = FALSE; |
| |
| size_t GC_min_stack_sz; |
| |
| # define N_FREE_LISTS 25 |
| ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 }; |
| /* GC_stack_free_lists[i] is free list for stacks of */ |
| /* size GC_min_stack_sz*2**i. */ |
| /* Free lists are linked through first word. */ |
| |
| /* Return a stack of size at least *stack_size. *stack_size is */ |
| /* replaced by the actual stack size. */ |
| /* Caller holds allocation lock. */ |
| ptr_t GC_stack_alloc(size_t * stack_size) |
| { |
| register size_t requested_sz = *stack_size; |
| register size_t search_sz = GC_min_stack_sz; |
| register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */ |
| register ptr_t result; |
| |
| while (search_sz < requested_sz) { |
| search_sz *= 2; |
| index++; |
| } |
| if ((result = GC_stack_free_lists[index]) == 0 |
| && (result = GC_stack_free_lists[index+1]) != 0) { |
| /* Try next size up. */ |
| search_sz *= 2; index++; |
| } |
| if (result != 0) { |
| GC_stack_free_lists[index] = *(ptr_t *)result; |
| } else { |
| result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_size); |
| result = (ptr_t)(((word)result + GC_page_size) & ~(GC_page_size - 1)); |
| /* Protect hottest page to detect overflow. */ |
| # ifdef STACK_GROWS_UP |
| /* mprotect(result + search_sz, GC_page_size, PROT_NONE); */ |
| # else |
| /* mprotect(result, GC_page_size, PROT_NONE); */ |
| result += GC_page_size; |
| # endif |
| } |
| *stack_size = search_sz; |
| return(result); |
| } |
| |
| /* Caller holds allocation lock. */ |
| void GC_stack_free(ptr_t stack, size_t size) |
| { |
| register int index = 0; |
| register size_t search_sz = GC_min_stack_sz; |
| |
| while (search_sz < size) { |
| search_sz *= 2; |
| index++; |
| } |
| if (search_sz != size) ABORT("Bad stack size"); |
| *(ptr_t *)stack = GC_stack_free_lists[index]; |
| GC_stack_free_lists[index] = stack; |
| } |
| |
| |
| |
| # define THREAD_TABLE_SZ 128 /* Must be power of 2 */ |
| volatile GC_thread GC_threads[THREAD_TABLE_SZ]; |
| |
| void GC_push_thread_structures GC_PROTO((void)) |
| { |
| GC_push_all((ptr_t)(GC_threads), (ptr_t)(GC_threads)+sizeof(GC_threads)); |
| } |
| |
| /* Add a thread to GC_threads. We assume it wasn't already there. */ |
| /* Caller holds allocation lock. */ |
| GC_thread GC_new_thread(pthread_t id) |
| { |
| int hv = ((word)id) % THREAD_TABLE_SZ; |
| GC_thread result; |
| static struct GC_Thread_Rep first_thread; |
| static GC_bool first_thread_used = FALSE; |
| |
| if (!first_thread_used) { |
| result = &first_thread; |
| first_thread_used = TRUE; |
| /* Dont acquire allocation lock, since we may already hold it. */ |
| } else { |
| result = (struct GC_Thread_Rep *) |
| GC_INTERNAL_MALLOC(sizeof(struct GC_Thread_Rep), NORMAL); |
| } |
| if (result == 0) return(0); |
| result -> id = id; |
| result -> next = GC_threads[hv]; |
| GC_threads[hv] = result; |
| /* result -> flags = 0; */ |
| /* result -> stop = 0; */ |
| return(result); |
| } |
| |
| /* Delete a thread from GC_threads. We assume it is there. */ |
| /* (The code intentionally traps if it wasn't.) */ |
| /* Caller holds allocation lock. */ |
| void GC_delete_thread(pthread_t id) |
| { |
| int hv = ((word)id) % THREAD_TABLE_SZ; |
| register GC_thread p = GC_threads[hv]; |
| register GC_thread prev = 0; |
| |
| while (!pthread_equal(p -> id, id)) { |
| prev = p; |
| p = p -> next; |
| } |
| if (prev == 0) { |
| GC_threads[hv] = p -> next; |
| } else { |
| prev -> next = p -> next; |
| } |
| } |
| |
| /* If a thread has been joined, but we have not yet */ |
| /* been notified, then there may be more than one thread */ |
| /* in the table with the same pthread id. */ |
| /* This is OK, but we need a way to delete a specific one. */ |
| void GC_delete_gc_thread(pthread_t id, GC_thread gc_id) |
| { |
| int hv = ((word)id) % THREAD_TABLE_SZ; |
| register GC_thread p = GC_threads[hv]; |
| register GC_thread prev = 0; |
| |
| while (p != gc_id) { |
| prev = p; |
| p = p -> next; |
| } |
| if (prev == 0) { |
| GC_threads[hv] = p -> next; |
| } else { |
| prev -> next = p -> next; |
| } |
| } |
| |
| /* Return a GC_thread corresponding to a given thread_t. */ |
| /* Returns 0 if it's not there. */ |
| /* Caller holds allocation lock or otherwise inhibits */ |
| /* updates. */ |
| /* If there is more than one thread with the given id we */ |
| /* return the most recent one. */ |
| GC_thread GC_lookup_thread(pthread_t id) |
| { |
| int hv = ((word)id) % THREAD_TABLE_SZ; |
| register GC_thread p = GC_threads[hv]; |
| |
| while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next; |
| return(p); |
| } |
| |
| |
| /* Caller holds allocation lock. */ |
| void GC_stop_world() |
| { |
| pthread_t my_thread = pthread_self(); |
| register int i; |
| register GC_thread p; |
| register int result; |
| struct timespec timeout; |
| |
| for (i = 0; i < THREAD_TABLE_SZ; i++) { |
| for (p = GC_threads[i]; p != 0; p = p -> next) { |
| if (p -> id != my_thread) { |
| if (p -> flags & FINISHED) { |
| p -> stop = STOPPED; |
| continue; |
| } |
| p -> stop = PLEASE_STOP; |
| result = pthread_kill(p -> id, SIG_SUSPEND); |
| /* GC_printf1("Sent signal to 0x%x\n", p -> id); */ |
| switch(result) { |
| case ESRCH: |
| /* Not really there anymore. Possible? */ |
| p -> stop = STOPPED; |
| break; |
| case 0: |
| break; |
| default: |
| ABORT("pthread_kill failed"); |
| } |
| } |
| } |
| } |
| pthread_mutex_lock(&GC_suspend_lock); |
| for (i = 0; i < THREAD_TABLE_SZ; i++) { |
| for (p = GC_threads[i]; p != 0; p = p -> next) { |
| while (p -> id != my_thread && p -> stop != STOPPED) { |
| clock_gettime(CLOCK_REALTIME, &timeout); |
| timeout.tv_nsec += 50000000; /* 50 msecs */ |
| if (timeout.tv_nsec >= 1000000000) { |
| timeout.tv_nsec -= 1000000000; |
| ++timeout.tv_sec; |
| } |
| result = pthread_cond_timedwait(&GC_suspend_ack_cv, |
| &GC_suspend_lock, |
| &timeout); |
| if (result == ETIMEDOUT) { |
| /* Signal was lost or misdirected. Try again. */ |
| /* Duplicate signals should be benign. */ |
| result = pthread_kill(p -> id, SIG_SUSPEND); |
| } |
| } |
| } |
| } |
| pthread_mutex_unlock(&GC_suspend_lock); |
| /* GC_printf1("World stopped 0x%x\n", pthread_self()); */ |
| } |
| |
| /* Caller holds allocation lock. */ |
| void GC_start_world() |
| { |
| GC_thread p; |
| unsigned i; |
| |
| /* GC_printf0("World starting\n"); */ |
| for (i = 0; i < THREAD_TABLE_SZ; i++) { |
| for (p = GC_threads[i]; p != 0; p = p -> next) { |
| p -> stop = NOT_STOPPED; |
| } |
| } |
| pthread_mutex_lock(&GC_suspend_lock); |
| /* All other threads are at pthread_cond_wait in signal handler. */ |
| /* Otherwise we couldn't have acquired the lock. */ |
| pthread_mutex_unlock(&GC_suspend_lock); |
| pthread_cond_broadcast(&GC_continue_cv); |
| } |
| |
| # ifdef MMAP_STACKS |
| --> not really supported yet. |
| int GC_is_thread_stack(ptr_t addr) |
| { |
| register int i; |
| register GC_thread p; |
| |
| for (i = 0; i < THREAD_TABLE_SZ; i++) { |
| for (p = GC_threads[i]; p != 0; p = p -> next) { |
| if (p -> stack_size != 0) { |
| if (p -> stack <= addr && |
| addr < p -> stack + p -> stack_size) |
| return 1; |
| } |
| } |
| } |
| return 0; |
| } |
| # endif |
| |
| /* 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() |
| { |
| register int i; |
| register GC_thread p; |
| register ptr_t sp = GC_approx_sp(); |
| register ptr_t hot, cold; |
| pthread_t me = pthread_self(); |
| |
| if (!GC_thr_initialized) GC_thr_init(); |
| /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */ |
| for (i = 0; i < THREAD_TABLE_SZ; i++) { |
| for (p = GC_threads[i]; p != 0; p = p -> next) { |
| if (p -> flags & FINISHED) continue; |
| if (pthread_equal(p -> id, me)) { |
| hot = GC_approx_sp(); |
| } else { |
| hot = p -> stack_ptr; |
| } |
| if (p -> stack_size != 0) { |
| # ifdef STACK_GROWS_UP |
| cold = p -> stack; |
| # else |
| cold = p -> stack + p -> stack_size; |
| # endif |
| } else { |
| /* The original stack. */ |
| cold = GC_stackbottom; |
| } |
| # ifdef STACK_GROWS_UP |
| GC_push_all_stack(cold, hot); |
| # else |
| GC_push_all_stack(hot, cold); |
| # endif |
| } |
| } |
| } |
| |
| |
| /* We hold the allocation lock. */ |
| void GC_thr_init() |
| { |
| GC_thread t; |
| struct sigaction act; |
| |
| if (GC_thr_initialized) return; |
| GC_thr_initialized = TRUE; |
| GC_min_stack_sz = HBLKSIZE; |
| (void) sigaction(SIG_SUSPEND, 0, &act); |
| if (act.sa_handler != SIG_DFL) |
| ABORT("Previously installed SIG_SUSPEND handler"); |
| /* Install handler. */ |
| act.sa_handler = GC_suspend_handler; |
| act.sa_flags = SA_RESTART; |
| (void) sigemptyset(&act.sa_mask); |
| if (0 != sigaction(SIG_SUSPEND, &act, 0)) |
| ABORT("Failed to install SIG_SUSPEND handler"); |
| /* Add the initial thread, so we can stop it. */ |
| t = GC_new_thread(pthread_self()); |
| t -> stack_size = 0; |
| t -> stack_ptr = (ptr_t)(&t); |
| t -> flags = DETACHED; |
| } |
| |
| int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) |
| { |
| sigset_t fudged_set; |
| |
| if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) { |
| fudged_set = *set; |
| sigdelset(&fudged_set, SIG_SUSPEND); |
| set = &fudged_set; |
| } |
| return(pthread_sigmask(how, set, oset)); |
| } |
| |
| struct start_info { |
| void *(*start_routine)(void *); |
| void *arg; |
| word flags; |
| ptr_t stack; |
| size_t stack_size; |
| sem_t registered; /* 1 ==> in our thread table, but */ |
| /* parent hasn't yet noticed. */ |
| }; |
| |
| void GC_thread_exit_proc(void *arg) |
| { |
| GC_thread me; |
| |
| LOCK(); |
| me = GC_lookup_thread(pthread_self()); |
| if (me -> flags & DETACHED) { |
| GC_delete_thread(pthread_self()); |
| } else { |
| me -> flags |= FINISHED; |
| } |
| UNLOCK(); |
| } |
| |
| int GC_pthread_join(pthread_t thread, void **retval) |
| { |
| int result; |
| GC_thread thread_gc_id; |
| |
| LOCK(); |
| thread_gc_id = GC_lookup_thread(thread); |
| /* This is guaranteed to be the intended one, since the thread id */ |
| /* cant have been recycled by pthreads. */ |
| UNLOCK(); |
| result = pthread_join(thread, retval); |
| /* Some versions of the Irix pthreads library can erroneously */ |
| /* return EINTR when the call succeeds. */ |
| if (EINTR == result) result = 0; |
| if (result == 0) { |
| LOCK(); |
| /* Here the pthread thread id may have been recycled. */ |
| GC_delete_gc_thread(thread, thread_gc_id); |
| UNLOCK(); |
| } |
| return result; |
| } |
| |
| int GC_pthread_detach(pthread_t thread) |
| { |
| int result; |
| GC_thread thread_gc_id; |
| |
| LOCK(); |
| thread_gc_id = GC_lookup_thread(thread); |
| UNLOCK(); |
| result = pthread_detach(thread); |
| if (result == 0) { |
| LOCK(); |
| thread_gc_id -> flags |= DETACHED; |
| /* Here the pthread thread id may have been recycled. */ |
| if (thread_gc_id -> flags & FINISHED) { |
| GC_delete_gc_thread(thread, thread_gc_id); |
| } |
| UNLOCK(); |
| } |
| return result; |
| } |
| |
| void * GC_start_routine(void * arg) |
| { |
| struct start_info * si = arg; |
| void * result; |
| GC_thread me; |
| pthread_t my_pthread; |
| void *(*start)(void *); |
| void *start_arg; |
| |
| my_pthread = pthread_self(); |
| /* If a GC occurs before the thread is registered, that GC will */ |
| /* ignore this thread. That's fine, since it will block trying to */ |
| /* acquire the allocation lock, and won't yet hold interesting */ |
| /* pointers. */ |
| LOCK(); |
| /* We register the thread here instead of in the parent, so that */ |
| /* we don't need to hold the allocation lock during pthread_create. */ |
| /* Holding the allocation lock there would make REDIRECT_MALLOC */ |
| /* impossible. It probably still doesn't work, but we're a little */ |
| /* closer ... */ |
| /* This unfortunately means that we have to be careful the parent */ |
| /* doesn't try to do a pthread_join before we're registered. */ |
| me = GC_new_thread(my_pthread); |
| me -> flags = si -> flags; |
| me -> stack = si -> stack; |
| me -> stack_size = si -> stack_size; |
| me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word); |
| UNLOCK(); |
| start = si -> start_routine; |
| start_arg = si -> arg; |
| sem_post(&(si -> registered)); |
| pthread_cleanup_push(GC_thread_exit_proc, 0); |
| result = (*start)(start_arg); |
| me -> status = result; |
| me -> flags |= FINISHED; |
| pthread_cleanup_pop(1); |
| /* This involves acquiring the lock, ensuring that we can't exit */ |
| /* while a collection that thinks we're alive is trying to stop */ |
| /* us. */ |
| return(result); |
| } |
| |
| # define copy_attr(pa_ptr, source) *(pa_ptr) = *(source) |
| |
| int |
| GC_pthread_create(pthread_t *new_thread, |
| const pthread_attr_t *attr, |
| void *(*start_routine)(void *), void *arg) |
| { |
| int result; |
| GC_thread t; |
| void * stack; |
| size_t stacksize; |
| pthread_attr_t new_attr; |
| int detachstate; |
| word my_flags = 0; |
| struct start_info * si = GC_malloc(sizeof(struct start_info)); |
| /* This is otherwise saved only in an area mmapped by the thread */ |
| /* library, which isn't visible to the collector. */ |
| |
| if (0 == si) return(ENOMEM); |
| if (0 != sem_init(&(si -> registered), 0, 0)) { |
| ABORT("sem_init failed"); |
| } |
| si -> start_routine = start_routine; |
| si -> arg = arg; |
| LOCK(); |
| if (!GC_is_initialized) GC_init(); |
| if (NULL == attr) { |
| stack = 0; |
| (void) pthread_attr_init(&new_attr); |
| } else { |
| copy_attr(&new_attr, attr); |
| pthread_attr_getstackaddr(&new_attr, &stack); |
| } |
| pthread_attr_getstacksize(&new_attr, &stacksize); |
| pthread_attr_getdetachstate(&new_attr, &detachstate); |
| if (stacksize < GC_min_stack_sz) ABORT("Stack too small"); |
| if (0 == stack) { |
| stack = (void *)GC_stack_alloc(&stacksize); |
| if (0 == stack) { |
| UNLOCK(); |
| return(ENOMEM); |
| } |
| pthread_attr_setstackaddr(&new_attr, stack); |
| } else { |
| my_flags |= CLIENT_OWNS_STACK; |
| } |
| if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED; |
| si -> flags = my_flags; |
| si -> stack = stack; |
| si -> stack_size = stacksize; |
| result = pthread_create(new_thread, &new_attr, GC_start_routine, si); |
| if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) { |
| GC_stack_free(stack, stacksize); |
| } |
| UNLOCK(); |
| /* Wait until child has been added to the thread table. */ |
| /* This also ensures that we hold onto si until the child is done */ |
| /* with it. Thus it doesn't matter whether it is otherwise */ |
| /* visible to the collector. */ |
| while (0 != sem_wait(&(si -> registered))) { |
| if (errno != EINTR) { |
| GC_printf1("Sem_wait: errno = %ld\n", (unsigned long) errno); |
| ABORT("sem_wait failed"); |
| } |
| } |
| sem_destroy(&(si -> registered)); |
| pthread_attr_destroy(&new_attr); /* Probably unnecessary under Irix */ |
| return(result); |
| } |
| |
| VOLATILE GC_bool GC_collecting = 0; |
| /* A hint that we're in the collector and */ |
| /* holding the allocation lock for an */ |
| /* extended period. */ |
| |
| /* Reasonably fast spin locks. Basically the same implementation */ |
| /* as STL alloc.h. */ |
| |
| #define SLEEP_THRESHOLD 3 |
| |
| unsigned long GC_allocate_lock = 0; |
| # define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock) |
| # define GC_LOCK_TAKEN GC_allocate_lock |
| |
| void GC_lock() |
| { |
| # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */ |
| # define high_spin_max 1000 /* spin cycles for multiprocessor */ |
| static unsigned spin_max = low_spin_max; |
| unsigned my_spin_max; |
| static unsigned last_spins = 0; |
| unsigned my_last_spins; |
| volatile unsigned junk; |
| # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk |
| int i; |
| |
| if (GC_TRY_LOCK()) { |
| return; |
| } |
| junk = 0; |
| my_spin_max = spin_max; |
| my_last_spins = last_spins; |
| for (i = 0; i < my_spin_max; i++) { |
| if (GC_collecting) goto yield; |
| if (i < my_last_spins/2 || GC_LOCK_TAKEN) { |
| PAUSE; |
| continue; |
| } |
| if (GC_TRY_LOCK()) { |
| /* |
| * got it! |
| * Spinning worked. Thus we're probably not being scheduled |
| * against the other process with which we were contending. |
| * Thus it makes sense to spin longer the next time. |
| */ |
| last_spins = i; |
| spin_max = high_spin_max; |
| return; |
| } |
| } |
| /* We are probably being scheduled against the other process. Sleep. */ |
| spin_max = low_spin_max; |
| yield: |
| for (i = 0;; ++i) { |
| if (GC_TRY_LOCK()) { |
| return; |
| } |
| if (i < SLEEP_THRESHOLD) { |
| sched_yield(); |
| } else { |
| struct timespec ts; |
| |
| if (i > 26) i = 26; |
| /* Don't wait for more than about 60msecs, even */ |
| /* under extreme contention. */ |
| ts.tv_sec = 0; |
| ts.tv_nsec = 1 << i; |
| nanosleep(&ts, 0); |
| } |
| } |
| } |
| |
| # else |
| |
| #ifndef LINT |
| int GC_no_Irix_threads; |
| #endif |
| |
| # endif /* GC_IRIX_THREADS */ |
| |