Back to index

lightning-sunbird  0.9+nobinonly
irix_threads.c
Go to the documentation of this file.
00001 /* 
00002  * Copyright (c) 1994 by Xerox Corporation.  All rights reserved.
00003  * Copyright (c) 1996 by Silicon Graphics.  All rights reserved.
00004  *
00005  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
00006  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
00007  *
00008  * Permission is hereby granted to use or copy this program
00009  * for any purpose,  provided the above notices are retained on all copies.
00010  * Permission to modify the code and to distribute modified code is granted,
00011  * provided the above notices are retained, and a notice that the code was
00012  * modified is included with the above copyright notice.
00013  */
00014 /*
00015  * Support code for Irix (>=6.2) Pthreads.  This relies on properties
00016  * not guaranteed by the Pthread standard.  It may or may not be portable
00017  * to other implementations.
00018  *
00019  * Note that there is a lot of code duplication between linux_threads.c
00020  * and irix_threads.c; any changes made here may need to be reflected
00021  * there too.
00022  */
00023 
00024 # if defined(IRIX_THREADS)
00025 
00026 # include "gc_priv.h"
00027 # include <pthread.h>
00028 # include <semaphore.h>
00029 # include <time.h>
00030 # include <errno.h>
00031 # include <unistd.h>
00032 # include <sys/mman.h>
00033 # include <sys/time.h>
00034 
00035 #undef pthread_create
00036 #undef pthread_sigmask
00037 #undef pthread_join
00038 
00039 void GC_thr_init();
00040 
00041 #if 0
00042 void GC_print_sig_mask()
00043 {
00044     sigset_t blocked;
00045     int i;
00046 
00047     if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
00048        ABORT("pthread_sigmask");
00049     GC_printf0("Blocked: ");
00050     for (i = 1; i <= MAXSIG; i++) {
00051         if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
00052     }
00053     GC_printf0("\n");
00054 }
00055 #endif
00056 
00057 /* We use the allocation lock to protect thread-related data structures. */
00058 
00059 /* The set of all known threads.  We intercept thread creation and    */
00060 /* joins.  We never actually create detached threads.  We allocate all       */
00061 /* new thread stacks ourselves.  These allow us to maintain this      */
00062 /* data structure.                                             */
00063 /* Protected by GC_thr_lock.                                          */
00064 /* Some of this should be declared volatile, but that's incosnsistent */
00065 /* with some library routine declarations.                            */
00066 typedef struct GC_Thread_Rep {
00067     struct GC_Thread_Rep * next;  /* More recently allocated threads  */
00068                               /* with a given pthread id come  */
00069                               /* first.  (All but the first are       */
00070                               /* guaranteed to be dead, but we may  */
00071                               /* not yet have registered the join.) */
00072     pthread_t id;
00073     word stop;
00074 #      define NOT_STOPPED 0
00075 #      define PLEASE_STOP 1
00076 #      define STOPPED 2
00077     word flags;
00078 #      define FINISHED 1    /* Thread has exited.       */
00079 #      define DETACHED 2    /* Thread is intended to be detached.     */
00080 #      define CLIENT_OWNS_STACK    4
00081                             /* Stack was supplied by client.   */
00082     ptr_t stack;
00083     ptr_t stack_ptr;               /* Valid only when stopped. */
00084                             /* But must be within stack region at     */
00085                             /* all times.                      */
00086     size_t stack_size;             /* 0 for original thread.   */
00087     void * status;          /* Used only to avoid premature    */
00088                             /* reclamation of any data it might       */
00089                             /* reference.                      */
00090 } * GC_thread;
00091 
00092 GC_thread GC_lookup_thread(pthread_t id);
00093 
00094 /*
00095  * The only way to suspend threads given the pthread interface is to send
00096  * signals.  Unfortunately, this means we have to reserve
00097  * a signal, and intercept client calls to change the signal mask.
00098  */
00099 # define SIG_SUSPEND (SIGRTMIN + 6)
00100 
00101 pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
00102                             /* Number of threads stopped so far       */
00103 pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER;
00104 pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER;
00105 
00106 void GC_suspend_handler(int sig)
00107 {
00108     int dummy;
00109     GC_thread me;
00110     sigset_t all_sigs;
00111     sigset_t old_sigs;
00112     int i;
00113 
00114     if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
00115     me = GC_lookup_thread(pthread_self());
00116     /* The lookup here is safe, since I'm doing this on behalf  */
00117     /* of a thread which holds the allocation lock in order    */
00118     /* to stop the world.  Thus concurrent modification of the */
00119     /* data structure is impossible.                           */
00120     if (PLEASE_STOP != me -> stop) {
00121        /* Misdirected signal.      */
00122        pthread_mutex_unlock(&GC_suspend_lock);
00123        return;
00124     }
00125     pthread_mutex_lock(&GC_suspend_lock);
00126     me -> stack_ptr = (ptr_t)(&dummy);
00127     me -> stop = STOPPED;
00128     pthread_cond_signal(&GC_suspend_ack_cv);
00129     pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock);
00130     pthread_mutex_unlock(&GC_suspend_lock);
00131     /* GC_printf1("Continuing 0x%x\n", pthread_self()); */
00132 }
00133 
00134 
00135 GC_bool GC_thr_initialized = FALSE;
00136 
00137 size_t GC_min_stack_sz;
00138 
00139 size_t GC_page_sz;
00140 
00141 # define N_FREE_LISTS 25
00142 ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 };
00143               /* GC_stack_free_lists[i] is free list for stacks of    */
00144               /* size GC_min_stack_sz*2**i.                           */
00145               /* Free lists are linked through first word.            */
00146 
00147 /* Return a stack of size at least *stack_size.  *stack_size is       */
00148 /* replaced by the actual stack size.                          */
00149 /* Caller holds allocation lock.                        */
00150 ptr_t GC_stack_alloc(size_t * stack_size)
00151 {
00152     register size_t requested_sz = *stack_size;
00153     register size_t search_sz = GC_min_stack_sz;
00154     register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */
00155     register ptr_t result;
00156     
00157     while (search_sz < requested_sz) {
00158         search_sz *= 2;
00159         index++;
00160     }
00161     if ((result = GC_stack_free_lists[index]) == 0
00162         && (result = GC_stack_free_lists[index+1]) != 0) {
00163         /* Try next size up. */
00164         search_sz *= 2; index++;
00165     }
00166     if (result != 0) {
00167         GC_stack_free_lists[index] = *(ptr_t *)result;
00168     } else {
00169         result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_sz);
00170         result = (ptr_t)(((word)result + GC_page_sz) & ~(GC_page_sz - 1));
00171         /* Protect hottest page to detect overflow. */
00172         /* mprotect(result, GC_page_sz, PROT_NONE); */
00173         result += GC_page_sz;
00174     }
00175     *stack_size = search_sz;
00176     return(result);
00177 }
00178 
00179 /* Caller holds allocation lock.                               */
00180 void GC_stack_free(ptr_t stack, size_t size)
00181 {
00182     register int index = 0;
00183     register size_t search_sz = GC_min_stack_sz;
00184     
00185     while (search_sz < size) {
00186         search_sz *= 2;
00187         index++;
00188     }
00189     if (search_sz != size) ABORT("Bad stack size");
00190     *(ptr_t *)stack = GC_stack_free_lists[index];
00191     GC_stack_free_lists[index] = stack;
00192 }
00193 
00194 
00195 
00196 # define THREAD_TABLE_SZ 128       /* Must be power of 2       */
00197 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
00198 
00199 /* Add a thread to GC_threads.  We assume it wasn't already there.    */
00200 /* Caller holds allocation lock.                               */
00201 GC_thread GC_new_thread(pthread_t id)
00202 {
00203     int hv = ((word)id) % THREAD_TABLE_SZ;
00204     GC_thread result;
00205     static struct GC_Thread_Rep first_thread;
00206     static GC_bool first_thread_used = FALSE;
00207     
00208     if (!first_thread_used) {
00209        result = &first_thread;
00210        first_thread_used = TRUE;
00211        /* Dont acquire allocation lock, since we may already hold it. */
00212     } else {
00213         result = (struct GC_Thread_Rep *)
00214                GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
00215     }
00216     if (result == 0) return(0);
00217     result -> id = id;
00218     result -> next = GC_threads[hv];
00219     GC_threads[hv] = result;
00220     /* result -> flags = 0;     */
00221     /* result -> stop = 0;  */
00222     return(result);
00223 }
00224 
00225 /* Delete a thread from GC_threads.  We assume it is there.    */
00226 /* (The code intentionally traps if it wasn't.)                */
00227 /* Caller holds allocation lock.                        */
00228 void GC_delete_thread(pthread_t id)
00229 {
00230     int hv = ((word)id) % THREAD_TABLE_SZ;
00231     register GC_thread p = GC_threads[hv];
00232     register GC_thread prev = 0;
00233     
00234     while (!pthread_equal(p -> id, id)) {
00235         prev = p;
00236         p = p -> next;
00237     }
00238     if (prev == 0) {
00239         GC_threads[hv] = p -> next;
00240     } else {
00241         prev -> next = p -> next;
00242     }
00243 }
00244 
00245 /* If a thread has been joined, but we have not yet            */
00246 /* been notified, then there may be more than one thread       */
00247 /* in the table with the same pthread id.               */
00248 /* This is OK, but we need a way to delete a specific one.     */
00249 void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
00250 {
00251     int hv = ((word)id) % THREAD_TABLE_SZ;
00252     register GC_thread p = GC_threads[hv];
00253     register GC_thread prev = 0;
00254 
00255     while (p != gc_id) {
00256         prev = p;
00257         p = p -> next;
00258     }
00259     if (prev == 0) {
00260         GC_threads[hv] = p -> next;
00261     } else {
00262         prev -> next = p -> next;
00263     }
00264 }
00265 
00266 /* Return a GC_thread corresponding to a given thread_t.       */
00267 /* Returns 0 if it's not there.                                */
00268 /* Caller holds  allocation lock or otherwise inhibits         */
00269 /* updates.                                             */
00270 /* If there is more than one thread with the given id we       */
00271 /* return the most recent one.                                 */
00272 GC_thread GC_lookup_thread(pthread_t id)
00273 {
00274     int hv = ((word)id) % THREAD_TABLE_SZ;
00275     register GC_thread p = GC_threads[hv];
00276     
00277     while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
00278     return(p);
00279 }
00280 
00281 
00282 /* Caller holds allocation lock.   */
00283 void GC_stop_world()
00284 {
00285     pthread_t my_thread = pthread_self();
00286     register int i;
00287     register GC_thread p;
00288     register int result;
00289     struct timespec timeout;
00290     
00291     for (i = 0; i < THREAD_TABLE_SZ; i++) {
00292       for (p = GC_threads[i]; p != 0; p = p -> next) {
00293         if (p -> id != my_thread) {
00294             if (p -> flags & FINISHED) {
00295               p -> stop = STOPPED;
00296               continue;
00297            }
00298            p -> stop = PLEASE_STOP;
00299             result = pthread_kill(p -> id, SIG_SUSPEND);
00300            /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
00301            switch(result) {
00302                 case ESRCH:
00303                     /* Not really there anymore.  Possible? */
00304                     p -> stop = STOPPED;
00305                     break;
00306                 case 0:
00307                     break;
00308                 default:
00309                     ABORT("pthread_kill failed");
00310             }
00311         }
00312       }
00313     }
00314     pthread_mutex_lock(&GC_suspend_lock);
00315     for (i = 0; i < THREAD_TABLE_SZ; i++) {
00316       for (p = GC_threads[i]; p != 0; p = p -> next) {
00317         while (p -> id != my_thread && p -> stop != STOPPED) {
00318            clock_gettime(CLOCK_REALTIME, &timeout);
00319             timeout.tv_nsec += 50000000; /* 50 msecs */
00320             if (timeout.tv_nsec >= 1000000000) {
00321                 timeout.tv_nsec -= 1000000000;
00322                 ++timeout.tv_sec;
00323             }
00324             result = pthread_cond_timedwait(&GC_suspend_ack_cv,
00325                                        &GC_suspend_lock,
00326                                             &timeout);
00327             if (result == ETIMEDOUT) {
00328                 /* Signal was lost or misdirected.  Try again.      */
00329                 /* Duplicate signals should be benign.              */
00330                 result = pthread_kill(p -> id, SIG_SUSPEND);
00331            }
00332        }
00333       }
00334     }
00335     pthread_mutex_unlock(&GC_suspend_lock);
00336     /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
00337 }
00338 
00339 /* Caller holds allocation lock.   */
00340 void GC_start_world()
00341 {
00342     GC_thread p;
00343     unsigned i;
00344 
00345     /* GC_printf0("World starting\n"); */
00346     for (i = 0; i < THREAD_TABLE_SZ; i++) {
00347       for (p = GC_threads[i]; p != 0; p = p -> next) {
00348        p -> stop = NOT_STOPPED;
00349       }
00350     }
00351     pthread_mutex_lock(&GC_suspend_lock);
00352     /* All other threads are at pthread_cond_wait in signal handler.  */
00353     /* Otherwise we couldn't have acquired the lock.                  */
00354     pthread_mutex_unlock(&GC_suspend_lock);
00355     pthread_cond_broadcast(&GC_continue_cv);
00356 }
00357 
00358 # ifdef MMAP_STACKS
00359 --> not really supported yet.
00360 int GC_is_thread_stack(ptr_t addr)
00361 {
00362     register int i;
00363     register GC_thread p;
00364 
00365     for (i = 0; i < THREAD_TABLE_SZ; i++) {
00366       for (p = GC_threads[i]; p != 0; p = p -> next) {
00367         if (p -> stack_size != 0) {
00368             if (p -> stack <= addr &&
00369                 addr < p -> stack + p -> stack_size)
00370                    return 1;
00371        }
00372       }
00373     }
00374     return 0;
00375 }
00376 # endif
00377 
00378 /* We hold allocation lock.  We assume the world is stopped.   */
00379 void GC_push_all_stacks()
00380 {
00381     register int i;
00382     register GC_thread p;
00383     register ptr_t sp = GC_approx_sp();
00384     register ptr_t lo, hi;
00385     pthread_t me = pthread_self();
00386     
00387     if (!GC_thr_initialized) GC_thr_init();
00388     /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
00389     for (i = 0; i < THREAD_TABLE_SZ; i++) {
00390       for (p = GC_threads[i]; p != 0; p = p -> next) {
00391         if (p -> flags & FINISHED) continue;
00392         if (pthread_equal(p -> id, me)) {
00393            lo = GC_approx_sp();
00394        } else {
00395            lo = p -> stack_ptr;
00396        }
00397         if (p -> stack_size != 0) {
00398             hi = p -> stack + p -> stack_size;
00399         } else {
00400             /* The original stack. */
00401             hi = GC_stackbottom;
00402         }
00403         GC_push_all_stack(lo, hi);
00404       }
00405     }
00406 }
00407 
00408 
00409 /* We hold the allocation lock.    */
00410 void GC_thr_init()
00411 {
00412     GC_thread t;
00413     struct sigaction act;
00414 
00415     if (GC_thr_initialized) return;
00416     GC_thr_initialized = TRUE;
00417     GC_min_stack_sz = HBLKSIZE;
00418     GC_page_sz = sysconf(_SC_PAGESIZE);
00419     (void) sigaction(SIG_SUSPEND, 0, &act);
00420     if (act.sa_handler != SIG_DFL)
00421        ABORT("Previously installed SIG_SUSPEND handler");
00422     /* Install handler.     */
00423        act.sa_handler = GC_suspend_handler;
00424        act.sa_flags = SA_RESTART;
00425        (void) sigemptyset(&act.sa_mask);
00426         if (0 != sigaction(SIG_SUSPEND, &act, 0))
00427            ABORT("Failed to install SIG_SUSPEND handler");
00428     /* Add the initial thread, so we can stop it.       */
00429       t = GC_new_thread(pthread_self());
00430       t -> stack_size = 0;
00431       t -> stack_ptr = (ptr_t)(&t);
00432       t -> flags = DETACHED;
00433 }
00434 
00435 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
00436 {
00437     sigset_t fudged_set;
00438     
00439     if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
00440         fudged_set = *set;
00441         sigdelset(&fudged_set, SIG_SUSPEND);
00442         set = &fudged_set;
00443     }
00444     return(pthread_sigmask(how, set, oset));
00445 }
00446 
00447 struct start_info {
00448     void *(*start_routine)(void *);
00449     void *arg;
00450     word flags;
00451     ptr_t stack;
00452     size_t stack_size;
00453     sem_t registered;       /* 1 ==> in our thread table, but  */
00454                             /* parent hasn't yet noticed.             */
00455 };
00456 
00457 void GC_thread_exit_proc(void *arg)
00458 {
00459     GC_thread me;
00460 
00461     LOCK();
00462     me = GC_lookup_thread(pthread_self());
00463     if (me -> flags & DETACHED) {
00464        GC_delete_thread(pthread_self());
00465     } else {
00466        me -> flags |= FINISHED;
00467     }
00468     UNLOCK();
00469 }
00470 
00471 int GC_pthread_join(pthread_t thread, void **retval)
00472 {
00473     int result;
00474     GC_thread thread_gc_id;
00475     
00476     LOCK();
00477     thread_gc_id = GC_lookup_thread(thread);
00478     /* This is guaranteed to be the intended one, since the thread id */
00479     /* cant have been recycled by pthreads.                           */
00480     UNLOCK();
00481     result = pthread_join(thread, retval);
00482     /* Some versions of the Irix pthreads library can erroneously     */
00483     /* return EINTR when the call succeeds.                           */
00484        if (EINTR == result) result = 0;
00485     LOCK();
00486     /* Here the pthread thread id may have been recycled. */
00487     GC_delete_gc_thread(thread, thread_gc_id);
00488     UNLOCK();
00489     return result;
00490 }
00491 
00492 void * GC_start_routine(void * arg)
00493 {
00494     struct start_info * si = arg;
00495     void * result;
00496     GC_thread me;
00497     pthread_t my_pthread;
00498     void *(*start)(void *);
00499     void *start_arg;
00500 
00501     my_pthread = pthread_self();
00502     /* If a GC occurs before the thread is registered, that GC will   */
00503     /* ignore this thread.  That's fine, since it will block trying to  */
00504     /* acquire the allocation lock, and won't yet hold interesting    */
00505     /* pointers.                                               */
00506     LOCK();
00507     /* We register the thread here instead of in the parent, so that  */
00508     /* we don't need to hold the allocation lock during pthread_create. */
00509     /* Holding the allocation lock there would make REDIRECT_MALLOC   */
00510     /* impossible.  It probably still doesn't work, but we're a little  */
00511     /* closer ...                                              */
00512     /* This unfortunately means that we have to be careful the parent */
00513     /* doesn't try to do a pthread_join before we're registered.      */
00514     me = GC_new_thread(my_pthread);
00515     me -> flags = si -> flags;
00516     me -> stack = si -> stack;
00517     me -> stack_size = si -> stack_size;
00518     me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word);
00519     UNLOCK();
00520     start = si -> start_routine;
00521     start_arg = si -> arg;
00522     sem_post(&(si -> registered));
00523     pthread_cleanup_push(GC_thread_exit_proc, 0);
00524     result = (*start)(start_arg);
00525     me -> status = result;
00526     me -> flags |= FINISHED;
00527     pthread_cleanup_pop(1);
00528        /* This involves acquiring the lock, ensuring that we can't exit */
00529        /* while a collection that thinks we're alive is trying to stop  */
00530        /* us.                                                   */
00531     return(result);
00532 }
00533 
00534 int
00535 GC_pthread_create(pthread_t *new_thread,
00536                 const pthread_attr_t *attr,
00537                   void *(*start_routine)(void *), void *arg)
00538 {
00539     int result;
00540     GC_thread t;
00541     void * stack;
00542     size_t stacksize;
00543     pthread_attr_t new_attr;
00544     int detachstate;
00545     word my_flags = 0;
00546     struct start_info * si = GC_malloc(sizeof(struct start_info)); 
00547        /* This is otherwise saved only in an area mmapped by the thread */
00548        /* library, which isn't visible to the collector.               */
00549 
00550     if (0 == si) return(ENOMEM);
00551     sem_init(&(si -> registered), 0, 0);
00552     si -> start_routine = start_routine;
00553     si -> arg = arg;
00554     LOCK();
00555     if (!GC_thr_initialized) GC_thr_init();
00556     if (NULL == attr) {
00557         stack = 0;
00558        (void) pthread_attr_init(&new_attr);
00559     } else {
00560         new_attr = *attr;
00561        pthread_attr_getstackaddr(&new_attr, &stack);
00562     }
00563     pthread_attr_getstacksize(&new_attr, &stacksize);
00564     pthread_attr_getdetachstate(&new_attr, &detachstate);
00565     if (stacksize < GC_min_stack_sz) ABORT("Stack too small");
00566     if (0 == stack) {
00567        stack = (void *)GC_stack_alloc(&stacksize);
00568        if (0 == stack) {
00569            UNLOCK();
00570            return(ENOMEM);
00571        }
00572        pthread_attr_setstackaddr(&new_attr, stack);
00573     } else {
00574        my_flags |= CLIENT_OWNS_STACK;
00575     }
00576     if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
00577     si -> flags = my_flags;
00578     si -> stack = stack;
00579     si -> stack_size = stacksize;
00580     result = pthread_create(new_thread, &new_attr, GC_start_routine, si);
00581     if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) {
00582        GC_stack_free(stack, stacksize);
00583     }        
00584     UNLOCK();  
00585     /* Wait until child has been added to the thread table.           */
00586     /* This also ensures that we hold onto si until the child is done */
00587     /* with it.  Thus it doesn't matter whether it is otherwise              */
00588     /* visible to the collector.                               */
00589         if (0 != sem_wait(&(si -> registered))) ABORT("sem_wait failed");
00590         sem_destroy(&(si -> registered));
00591     /* pthread_attr_destroy(&new_attr); */
00592     return(result);
00593 }
00594 
00595 GC_bool GC_collecting = 0; /* A hint that we're in the collector and       */
00596                         /* holding the allocation lock for an           */
00597                         /* extended period.                             */
00598 
00599 /* Reasonably fast spin locks.  Basically the same implementation */
00600 /* as STL alloc.h.  This isn't really the right way to do this.   */
00601 /* but until the POSIX scheduling mess gets straightened out ...  */
00602 
00603 unsigned long GC_allocate_lock = 0;
00604 
00605 #define SLEEP_THRESHOLD 3
00606 
00607 void GC_lock()
00608 {
00609 #   define low_spin_max 30  /* spin cycles if we suspect uniprocessor */
00610 #   define high_spin_max 1000 /* spin cycles for multiprocessor */
00611     static unsigned spin_max = low_spin_max;
00612     unsigned my_spin_max;
00613     static unsigned last_spins = 0;
00614     unsigned my_last_spins;
00615     volatile unsigned junk;
00616 #   define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
00617     int i;
00618 
00619     if (!GC_test_and_set(&GC_allocate_lock, 1)) {
00620         return;
00621     }
00622     junk = 0;
00623     my_spin_max = spin_max;
00624     my_last_spins = last_spins;
00625     for (i = 0; i < my_spin_max; i++) {
00626         if (GC_collecting) goto yield;
00627         if (i < my_last_spins/2 || GC_allocate_lock) {
00628             PAUSE; 
00629             continue;
00630         }
00631         if (!GC_test_and_set(&GC_allocate_lock, 1)) {
00632            /*
00633              * got it!
00634              * Spinning worked.  Thus we're probably not being scheduled
00635              * against the other process with which we were contending.
00636              * Thus it makes sense to spin longer the next time.
00637             */
00638             last_spins = i;
00639             spin_max = high_spin_max;
00640             return;
00641         }
00642     }
00643     /* We are probably being scheduled against the other process.  Sleep. */
00644     spin_max = low_spin_max;
00645 yield:
00646     for (i = 0;; ++i) {
00647         if (!GC_test_and_set(&GC_allocate_lock, 1)) {
00648             return;
00649         }
00650         if (i < SLEEP_THRESHOLD) {
00651             sched_yield();
00652        } else {
00653            struct timespec ts;
00654        
00655            if (i > 26) i = 26;
00656                      /* Don't wait for more than about 60msecs, even  */
00657                      /* under extreme contention.                     */
00658            ts.tv_sec = 0;
00659            ts.tv_nsec = 1 << i;
00660            nanosleep(&ts, 0);
00661        }
00662     }
00663 }
00664 
00665 
00666 
00667 # else
00668 
00669 #ifndef LINT
00670   int GC_no_Irix_threads;
00671 #endif
00672 
00673 # endif /* IRIX_THREADS */
00674