Annotation of OpenXM_contrib/gc/linux_threads.c, Revision 1.1
1.1 ! maekawa 1: /*
! 2: * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
! 3: * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
! 4: * Copyright (c) 1998 by Fergus Henderson. All rights reserved.
! 5: *
! 6: * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
! 7: * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
! 8: *
! 9: * Permission is hereby granted to use or copy this program
! 10: * for any purpose, provided the above notices are retained on all copies.
! 11: * Permission to modify the code and to distribute modified code is granted,
! 12: * provided the above notices are retained, and a notice that the code was
! 13: * modified is included with the above copyright notice.
! 14: */
! 15: /*
! 16: * Support code for LinuxThreads, the clone()-based kernel
! 17: * thread package for Linux which is included in libc6.
! 18: *
! 19: * This code relies on implementation details of LinuxThreads,
! 20: * (i.e. properties not guaranteed by the Pthread standard):
! 21: *
! 22: * - the function GC_linux_thread_top_of_stack(void)
! 23: * relies on the way LinuxThreads lays out thread stacks
! 24: * in the address space.
! 25: *
! 26: * Note that there is a lot of code duplication between linux_threads.c
! 27: * and irix_threads.c; any changes made here may need to be reflected
! 28: * there too.
! 29: */
! 30:
! 31: /* #define DEBUG_THREADS 1 */
! 32:
! 33: /* ANSI C requires that a compilation unit contains something */
! 34: # include "gc_priv.h"
! 35:
! 36: # if defined(LINUX_THREADS)
! 37:
! 38: # include <pthread.h>
! 39: # include <time.h>
! 40: # include <errno.h>
! 41: # include <unistd.h>
! 42: # include <sys/mman.h>
! 43: # include <sys/time.h>
! 44: # include <semaphore.h>
! 45:
! 46: #undef pthread_create
! 47: #undef pthread_sigmask
! 48: #undef pthread_join
! 49:
! 50: void GC_thr_init();
! 51:
! 52: #if 0
! 53: void GC_print_sig_mask()
! 54: {
! 55: sigset_t blocked;
! 56: int i;
! 57:
! 58: if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
! 59: ABORT("pthread_sigmask");
! 60: GC_printf0("Blocked: ");
! 61: for (i = 1; i <= MAXSIG; i++) {
! 62: if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
! 63: }
! 64: GC_printf0("\n");
! 65: }
! 66: #endif
! 67:
! 68: /* We use the allocation lock to protect thread-related data structures. */
! 69:
! 70: /* The set of all known threads. We intercept thread creation and */
! 71: /* joins. We never actually create detached threads. We allocate all */
! 72: /* new thread stacks ourselves. These allow us to maintain this */
! 73: /* data structure. */
! 74: /* Protected by GC_thr_lock. */
! 75: /* Some of this should be declared volatile, but that's incosnsistent */
! 76: /* with some library routine declarations. */
! 77: typedef struct GC_Thread_Rep {
! 78: struct GC_Thread_Rep * next; /* More recently allocated threads */
! 79: /* with a given pthread id come */
! 80: /* first. (All but the first are */
! 81: /* guaranteed to be dead, but we may */
! 82: /* not yet have registered the join.) */
! 83: pthread_t id;
! 84: word flags;
! 85: # define FINISHED 1 /* Thread has exited. */
! 86: # define DETACHED 2 /* Thread is intended to be detached. */
! 87: # define MAIN_THREAD 4 /* True for the original thread only. */
! 88:
! 89: ptr_t stack_end;
! 90: ptr_t stack_ptr; /* Valid only when stopped. */
! 91: int signal;
! 92: void * status; /* The value returned from the thread. */
! 93: /* Used only to avoid premature */
! 94: /* reclamation of any data it might */
! 95: /* reference. */
! 96: } * GC_thread;
! 97:
! 98: GC_thread GC_lookup_thread(pthread_t id);
! 99:
! 100: /*
! 101: * The only way to suspend threads given the pthread interface is to send
! 102: * signals. We can't use SIGSTOP directly, because we need to get the
! 103: * thread to save its stack pointer in the GC thread table before
! 104: * suspending. So we have to reserve a signal of our own for this.
! 105: * This means we have to intercept client calls to change the signal mask.
! 106: * The linuxthreads package already uses SIGUSR1 and SIGUSR2,
! 107: * so we need to reuse something else. I chose SIGPWR.
! 108: * (Perhaps SIGUNUSED would be a better choice.)
! 109: */
! 110: #define SIG_SUSPEND SIGPWR
! 111:
! 112: #define SIG_RESTART SIGXCPU
! 113:
! 114: sem_t GC_suspend_ack_sem;
! 115:
! 116: /*
! 117: GC_linux_thread_top_of_stack() relies on implementation details of
! 118: LinuxThreads, namely that thread stacks are allocated on 2M boundaries
! 119: and grow to no more than 2M.
! 120: To make sure that we're using LinuxThreads and not some other thread
! 121: package, we generate a dummy reference to `__pthread_initial_thread_bos',
! 122: which is a symbol defined in LinuxThreads, but (hopefully) not in other
! 123: thread packages.
! 124: */
! 125: extern char * __pthread_initial_thread_bos;
! 126: char **dummy_var_to_force_linux_threads = &__pthread_initial_thread_bos;
! 127:
! 128: #define LINUX_THREADS_STACK_SIZE (2 * 1024 * 1024)
! 129:
! 130: static inline ptr_t GC_linux_thread_top_of_stack(void)
! 131: {
! 132: char *sp = GC_approx_sp();
! 133: ptr_t tos = (ptr_t) (((unsigned long)sp | (LINUX_THREADS_STACK_SIZE - 1)) + 1);
! 134: #if DEBUG_THREADS
! 135: GC_printf1("SP = %lx\n", (unsigned long)sp);
! 136: GC_printf1("TOS = %lx\n", (unsigned long)tos);
! 137: #endif
! 138: return tos;
! 139: }
! 140:
! 141: void GC_suspend_handler(int sig)
! 142: {
! 143: int dummy;
! 144: pthread_t my_thread = pthread_self();
! 145: GC_thread me;
! 146: sigset_t all_sigs;
! 147: sigset_t old_sigs;
! 148: int i;
! 149: sigset_t mask;
! 150:
! 151: if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
! 152:
! 153: #if DEBUG_THREADS
! 154: GC_printf1("Suspending 0x%x\n", my_thread);
! 155: #endif
! 156:
! 157: me = GC_lookup_thread(my_thread);
! 158: /* The lookup here is safe, since I'm doing this on behalf */
! 159: /* of a thread which holds the allocation lock in order */
! 160: /* to stop the world. Thus concurrent modification of the */
! 161: /* data structure is impossible. */
! 162: me -> stack_ptr = (ptr_t)(&dummy);
! 163: me -> stack_end = GC_linux_thread_top_of_stack();
! 164:
! 165: /* Tell the thread that wants to stop the world that this */
! 166: /* thread has been stopped. Note that sem_post() is */
! 167: /* the only async-signal-safe primitive in LinuxThreads. */
! 168: sem_post(&GC_suspend_ack_sem);
! 169:
! 170: /* Wait until that thread tells us to restart by sending */
! 171: /* this thread a SIG_RESTART signal. */
! 172: /* SIG_RESTART should be masked at this point. Thus there */
! 173: /* is no race. */
! 174: if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
! 175: if (sigdelset(&mask, SIG_RESTART) != 0) ABORT("sigdelset() failed");
! 176: do {
! 177: me->signal = 0;
! 178: sigsuspend(&mask); /* Wait for signal */
! 179: } while (me->signal != SIG_RESTART);
! 180:
! 181: #if DEBUG_THREADS
! 182: GC_printf1("Continuing 0x%x\n", my_thread);
! 183: #endif
! 184: }
! 185:
! 186: void GC_restart_handler(int sig)
! 187: {
! 188: GC_thread me;
! 189:
! 190: if (sig != SIG_RESTART) ABORT("Bad signal in suspend_handler");
! 191:
! 192: /* Let the GC_suspend_handler() know that we got a SIG_RESTART. */
! 193: /* The lookup here is safe, since I'm doing this on behalf */
! 194: /* of a thread which holds the allocation lock in order */
! 195: /* to stop the world. Thus concurrent modification of the */
! 196: /* data structure is impossible. */
! 197: me = GC_lookup_thread(pthread_self());
! 198: me->signal = SIG_RESTART;
! 199:
! 200: /*
! 201: ** Note: even if we didn't do anything useful here,
! 202: ** it would still be necessary to have a signal handler,
! 203: ** rather than ignoring the signals, otherwise
! 204: ** the signals will not be delivered at all, and
! 205: ** will thus not interrupt the sigsuspend() above.
! 206: */
! 207:
! 208: #if DEBUG_THREADS
! 209: GC_printf1("In GC_restart_handler for 0x%x\n", pthread_self());
! 210: #endif
! 211: }
! 212:
! 213: GC_bool GC_thr_initialized = FALSE;
! 214:
! 215: # define THREAD_TABLE_SZ 128 /* Must be power of 2 */
! 216: volatile GC_thread GC_threads[THREAD_TABLE_SZ];
! 217:
! 218: /* Add a thread to GC_threads. We assume it wasn't already there. */
! 219: /* Caller holds allocation lock. */
! 220: GC_thread GC_new_thread(pthread_t id)
! 221: {
! 222: int hv = ((word)id) % THREAD_TABLE_SZ;
! 223: GC_thread result;
! 224: static struct GC_Thread_Rep first_thread;
! 225: static GC_bool first_thread_used = FALSE;
! 226:
! 227: if (!first_thread_used) {
! 228: result = &first_thread;
! 229: first_thread_used = TRUE;
! 230: /* Dont acquire allocation lock, since we may already hold it. */
! 231: } else {
! 232: result = (struct GC_Thread_Rep *)
! 233: GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
! 234: }
! 235: if (result == 0) return(0);
! 236: result -> id = id;
! 237: result -> next = GC_threads[hv];
! 238: GC_threads[hv] = result;
! 239: /* result -> flags = 0; */
! 240: return(result);
! 241: }
! 242:
! 243: /* Delete a thread from GC_threads. We assume it is there. */
! 244: /* (The code intentionally traps if it wasn't.) */
! 245: /* Caller holds allocation lock. */
! 246: void GC_delete_thread(pthread_t id)
! 247: {
! 248: int hv = ((word)id) % THREAD_TABLE_SZ;
! 249: register GC_thread p = GC_threads[hv];
! 250: register GC_thread prev = 0;
! 251:
! 252: while (!pthread_equal(p -> id, id)) {
! 253: prev = p;
! 254: p = p -> next;
! 255: }
! 256: if (prev == 0) {
! 257: GC_threads[hv] = p -> next;
! 258: } else {
! 259: prev -> next = p -> next;
! 260: }
! 261: }
! 262:
! 263: /* If a thread has been joined, but we have not yet */
! 264: /* been notified, then there may be more than one thread */
! 265: /* in the table with the same pthread id. */
! 266: /* This is OK, but we need a way to delete a specific one. */
! 267: void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
! 268: {
! 269: int hv = ((word)id) % THREAD_TABLE_SZ;
! 270: register GC_thread p = GC_threads[hv];
! 271: register GC_thread prev = 0;
! 272:
! 273: while (p != gc_id) {
! 274: prev = p;
! 275: p = p -> next;
! 276: }
! 277: if (prev == 0) {
! 278: GC_threads[hv] = p -> next;
! 279: } else {
! 280: prev -> next = p -> next;
! 281: }
! 282: }
! 283:
! 284: /* Return a GC_thread corresponding to a given thread_t. */
! 285: /* Returns 0 if it's not there. */
! 286: /* Caller holds allocation lock or otherwise inhibits */
! 287: /* updates. */
! 288: /* If there is more than one thread with the given id we */
! 289: /* return the most recent one. */
! 290: GC_thread GC_lookup_thread(pthread_t id)
! 291: {
! 292: int hv = ((word)id) % THREAD_TABLE_SZ;
! 293: register GC_thread p = GC_threads[hv];
! 294:
! 295: while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
! 296: return(p);
! 297: }
! 298:
! 299: /* Caller holds allocation lock. */
! 300: void GC_stop_world()
! 301: {
! 302: pthread_t my_thread = pthread_self();
! 303: register int i;
! 304: register GC_thread p;
! 305: register int n_live_threads = 0;
! 306: register int result;
! 307:
! 308: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 309: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 310: if (p -> id != my_thread) {
! 311: if (p -> flags & FINISHED) continue;
! 312: n_live_threads++;
! 313: #if DEBUG_THREADS
! 314: GC_printf1("Sending suspend signal to 0x%x\n", p -> id);
! 315: #endif
! 316: result = pthread_kill(p -> id, SIG_SUSPEND);
! 317: switch(result) {
! 318: case ESRCH:
! 319: /* Not really there anymore. Possible? */
! 320: n_live_threads--;
! 321: break;
! 322: case 0:
! 323: break;
! 324: default:
! 325: ABORT("pthread_kill failed");
! 326: }
! 327: }
! 328: }
! 329: }
! 330: for (i = 0; i < n_live_threads; i++) {
! 331: sem_wait(&GC_suspend_ack_sem);
! 332: }
! 333: #if DEBUG_THREADS
! 334: GC_printf1("World stopped 0x%x\n", pthread_self());
! 335: #endif
! 336: }
! 337:
! 338: /* Caller holds allocation lock. */
! 339: void GC_start_world()
! 340: {
! 341: pthread_t my_thread = pthread_self();
! 342: register int i;
! 343: register GC_thread p;
! 344: register int n_live_threads = 0;
! 345: register int result;
! 346:
! 347: # if DEBUG_THREADS
! 348: GC_printf0("World starting\n");
! 349: # endif
! 350:
! 351: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 352: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 353: if (p -> id != my_thread) {
! 354: if (p -> flags & FINISHED) continue;
! 355: n_live_threads++;
! 356: #if DEBUG_THREADS
! 357: GC_printf1("Sending restart signal to 0x%x\n", p -> id);
! 358: #endif
! 359: result = pthread_kill(p -> id, SIG_RESTART);
! 360: switch(result) {
! 361: case ESRCH:
! 362: /* Not really there anymore. Possible? */
! 363: n_live_threads--;
! 364: break;
! 365: case 0:
! 366: break;
! 367: default:
! 368: ABORT("pthread_kill failed");
! 369: }
! 370: }
! 371: }
! 372: }
! 373: #if DEBUG_THREADS
! 374: GC_printf0("World started\n");
! 375: #endif
! 376: }
! 377:
! 378: /* We hold allocation lock. We assume the world is stopped. */
! 379: void GC_push_all_stacks()
! 380: {
! 381: register int i;
! 382: register GC_thread p;
! 383: register ptr_t sp = GC_approx_sp();
! 384: register ptr_t lo, hi;
! 385: pthread_t me = pthread_self();
! 386:
! 387: if (!GC_thr_initialized) GC_thr_init();
! 388: #if DEBUG_THREADS
! 389: GC_printf1("Pushing stacks from thread 0x%lx\n", (unsigned long) me);
! 390: #endif
! 391: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 392: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 393: if (p -> flags & FINISHED) continue;
! 394: if (pthread_equal(p -> id, me)) {
! 395: lo = GC_approx_sp();
! 396: } else {
! 397: lo = p -> stack_ptr;
! 398: }
! 399: if ((p -> flags & MAIN_THREAD) == 0) {
! 400: if (pthread_equal(p -> id, me)) {
! 401: hi = GC_linux_thread_top_of_stack();
! 402: } else {
! 403: hi = p -> stack_end;
! 404: }
! 405: } else {
! 406: /* The original stack. */
! 407: hi = GC_stackbottom;
! 408: }
! 409: #if DEBUG_THREADS
! 410: GC_printf3("Stack for thread 0x%lx = [%lx,%lx)\n",
! 411: (unsigned long) p -> id,
! 412: (unsigned long) lo, (unsigned long) hi);
! 413: #endif
! 414: GC_push_all_stack(lo, hi);
! 415: }
! 416: }
! 417: }
! 418:
! 419:
! 420: /* We hold the allocation lock. */
! 421: void GC_thr_init()
! 422: {
! 423: GC_thread t;
! 424: struct sigaction act;
! 425:
! 426: if (GC_thr_initialized) return;
! 427: GC_thr_initialized = TRUE;
! 428:
! 429: if (sem_init(&GC_suspend_ack_sem, 0, 0) != 0)
! 430: ABORT("sem_init failed");
! 431:
! 432: act.sa_flags = SA_RESTART;
! 433: if (sigfillset(&act.sa_mask) != 0) {
! 434: ABORT("sigfillset() failed");
! 435: }
! 436: /* SIG_RESTART is unmasked by the handler when necessary. */
! 437: act.sa_handler = GC_suspend_handler;
! 438: if (sigaction(SIG_SUSPEND, &act, NULL) != 0) {
! 439: ABORT("Cannot set SIG_SUSPEND handler");
! 440: }
! 441:
! 442: act.sa_handler = GC_restart_handler;
! 443: if (sigaction(SIG_RESTART, &act, NULL) != 0) {
! 444: ABORT("Cannot set SIG_SUSPEND handler");
! 445: }
! 446:
! 447: /* Add the initial thread, so we can stop it. */
! 448: t = GC_new_thread(pthread_self());
! 449: t -> stack_ptr = 0;
! 450: t -> flags = DETACHED | MAIN_THREAD;
! 451: }
! 452:
! 453: int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
! 454: {
! 455: sigset_t fudged_set;
! 456:
! 457: if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
! 458: fudged_set = *set;
! 459: sigdelset(&fudged_set, SIG_SUSPEND);
! 460: set = &fudged_set;
! 461: }
! 462: return(pthread_sigmask(how, set, oset));
! 463: }
! 464:
! 465: struct start_info {
! 466: void *(*start_routine)(void *);
! 467: void *arg;
! 468: word flags;
! 469: sem_t registered; /* 1 ==> in our thread table, but */
! 470: /* parent hasn't yet noticed. */
! 471: };
! 472:
! 473:
! 474: void GC_thread_exit_proc(void *arg)
! 475: {
! 476: GC_thread me;
! 477: struct start_info * si = arg;
! 478:
! 479: LOCK();
! 480: me = GC_lookup_thread(pthread_self());
! 481: if (me -> flags & DETACHED) {
! 482: GC_delete_thread(pthread_self());
! 483: } else {
! 484: me -> flags |= FINISHED;
! 485: }
! 486: UNLOCK();
! 487: }
! 488:
! 489: int GC_pthread_join(pthread_t thread, void **retval)
! 490: {
! 491: int result;
! 492: GC_thread thread_gc_id;
! 493:
! 494: LOCK();
! 495: thread_gc_id = GC_lookup_thread(thread);
! 496: /* This is guaranteed to be the intended one, since the thread id */
! 497: /* cant have been recycled by pthreads. */
! 498: UNLOCK();
! 499: result = pthread_join(thread, retval);
! 500: LOCK();
! 501: /* Here the pthread thread id may have been recycled. */
! 502: GC_delete_gc_thread(thread, thread_gc_id);
! 503: UNLOCK();
! 504: return result;
! 505: }
! 506:
! 507: void * GC_start_routine(void * arg)
! 508: {
! 509: struct start_info * si = arg;
! 510: void * result;
! 511: GC_thread me;
! 512: pthread_t my_pthread;
! 513: void *(*start)(void *);
! 514: void *start_arg;
! 515:
! 516: my_pthread = pthread_self();
! 517: LOCK();
! 518: me = GC_new_thread(my_pthread);
! 519: me -> flags = si -> flags;
! 520: me -> stack_ptr = 0;
! 521: me -> stack_end = 0;
! 522: UNLOCK();
! 523: start = si -> start_routine;
! 524: start_arg = si -> arg;
! 525: sem_post(&(si -> registered));
! 526: pthread_cleanup_push(GC_thread_exit_proc, si);
! 527: # ifdef DEBUG_THREADS
! 528: GC_printf1("Starting thread 0x%lx\n", pthread_self());
! 529: GC_printf1("pid = %ld\n", (long) getpid());
! 530: GC_printf1("sp = 0x%lx\n", (long) &arg);
! 531: GC_printf1("start_routine = 0x%lx\n", start);
! 532: # endif
! 533: result = (*start)(start_arg);
! 534: #if DEBUG_THREADS
! 535: GC_printf1("Finishing thread 0x%x\n", pthread_self());
! 536: #endif
! 537: me -> status = result;
! 538: me -> flags |= FINISHED;
! 539: pthread_cleanup_pop(1);
! 540: /* Cleanup acquires lock, ensuring that we can't exit */
! 541: /* while a collection that thinks we're alive is trying to stop */
! 542: /* us. */
! 543: return(result);
! 544: }
! 545:
! 546: int
! 547: GC_pthread_create(pthread_t *new_thread,
! 548: const pthread_attr_t *attr,
! 549: void *(*start_routine)(void *), void *arg)
! 550: {
! 551: int result;
! 552: GC_thread t;
! 553: pthread_t my_new_thread;
! 554: void * stack;
! 555: size_t stacksize;
! 556: pthread_attr_t new_attr;
! 557: int detachstate;
! 558: word my_flags = 0;
! 559: struct start_info * si = GC_malloc(sizeof(struct start_info));
! 560: /* This is otherwise saved only in an area mmapped by the thread */
! 561: /* library, which isn't visible to the collector. */
! 562:
! 563: if (0 == si) return(ENOMEM);
! 564: sem_init(&(si -> registered), 0, 0);
! 565: si -> start_routine = start_routine;
! 566: si -> arg = arg;
! 567: LOCK();
! 568: if (!GC_thr_initialized) GC_thr_init();
! 569: if (NULL == attr) {
! 570: stack = 0;
! 571: (void) pthread_attr_init(&new_attr);
! 572: } else {
! 573: new_attr = *attr;
! 574: }
! 575: pthread_attr_getdetachstate(&new_attr, &detachstate);
! 576: if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
! 577: si -> flags = my_flags;
! 578: UNLOCK();
! 579: result = pthread_create(new_thread, &new_attr, GC_start_routine, si);
! 580: /* Wait until child has been added to the thread table. */
! 581: /* This also ensures that we hold onto si until the child is done */
! 582: /* with it. Thus it doesn't matter whether it is otherwise */
! 583: /* visible to the collector. */
! 584: if (0 != sem_wait(&(si -> registered))) ABORT("sem_wait failed");
! 585: sem_destroy(&(si -> registered));
! 586: /* pthread_attr_destroy(&new_attr); */
! 587: /* pthread_attr_destroy(&new_attr); */
! 588: return(result);
! 589: }
! 590:
! 591: GC_bool GC_collecting = 0;
! 592: /* A hint that we're in the collector and */
! 593: /* holding the allocation lock for an */
! 594: /* extended period. */
! 595:
! 596: /* Reasonably fast spin locks. Basically the same implementation */
! 597: /* as STL alloc.h. This isn't really the right way to do this. */
! 598: /* but until the POSIX scheduling mess gets straightened out ... */
! 599:
! 600: volatile unsigned int GC_allocate_lock = 0;
! 601:
! 602:
! 603: void GC_lock()
! 604: {
! 605: # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */
! 606: # define high_spin_max 1000 /* spin cycles for multiprocessor */
! 607: static unsigned spin_max = low_spin_max;
! 608: unsigned my_spin_max;
! 609: static unsigned last_spins = 0;
! 610: unsigned my_last_spins;
! 611: volatile unsigned junk;
! 612: # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
! 613: int i;
! 614:
! 615: if (!GC_test_and_set(&GC_allocate_lock)) {
! 616: return;
! 617: }
! 618: junk = 0;
! 619: my_spin_max = spin_max;
! 620: my_last_spins = last_spins;
! 621: for (i = 0; i < my_spin_max; i++) {
! 622: if (GC_collecting) goto yield;
! 623: if (i < my_last_spins/2 || GC_allocate_lock) {
! 624: PAUSE;
! 625: continue;
! 626: }
! 627: if (!GC_test_and_set(&GC_allocate_lock)) {
! 628: /*
! 629: * got it!
! 630: * Spinning worked. Thus we're probably not being scheduled
! 631: * against the other process with which we were contending.
! 632: * Thus it makes sense to spin longer the next time.
! 633: */
! 634: last_spins = i;
! 635: spin_max = high_spin_max;
! 636: return;
! 637: }
! 638: }
! 639: /* We are probably being scheduled against the other process. Sleep. */
! 640: spin_max = low_spin_max;
! 641: yield:
! 642: for (i = 0;; ++i) {
! 643: if (!GC_test_and_set(&GC_allocate_lock)) {
! 644: return;
! 645: }
! 646: # define SLEEP_THRESHOLD 12
! 647: /* nanosleep(<= 2ms) just spins under Linux. We */
! 648: /* want to be careful to avoid that behavior. */
! 649: if (i < SLEEP_THRESHOLD) {
! 650: sched_yield();
! 651: } else {
! 652: struct timespec ts;
! 653:
! 654: if (i > 26) i = 26;
! 655: /* Don't wait for more than about 60msecs, even */
! 656: /* under extreme contention. */
! 657: ts.tv_sec = 0;
! 658: ts.tv_nsec = 1 << i;
! 659: nanosleep(&ts, 0);
! 660: }
! 661: }
! 662: }
! 663:
! 664: # endif /* LINUX_THREADS */
! 665:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>