diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /nsprpub/pr/src/pthreads/ptthread.c | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'nsprpub/pr/src/pthreads/ptthread.c')
-rw-r--r-- | nsprpub/pr/src/pthreads/ptthread.c | 1749 |
1 files changed, 1749 insertions, 0 deletions
diff --git a/nsprpub/pr/src/pthreads/ptthread.c b/nsprpub/pr/src/pthreads/ptthread.c new file mode 100644 index 0000000000..92c9c7889e --- /dev/null +++ b/nsprpub/pr/src/pthreads/ptthread.c @@ -0,0 +1,1749 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +** File: ptthread.c +** Descritpion: Implemenation for threds using pthreds +** Exports: ptthread.h +*/ + +#if defined(_PR_PTHREADS) + +#include "prlog.h" +#include "primpl.h" +#include "prpdce.h" + +#include <pthread.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <dlfcn.h> + +#if defined(OPENBSD) || defined(FREEBSD) || defined(DRAGONFLY) +#include <pthread_np.h> +#endif + +#if defined(ANDROID) +#include <sys/prctl.h> +#endif + +#ifdef _PR_NICE_PRIORITY_SCHEDULING +#undef _POSIX_THREAD_PRIORITY_SCHEDULING +#include <sys/resource.h> +#ifndef HAVE_GETTID +#define gettid() (syscall(SYS_gettid)) +#endif +#endif + +/* + * Record whether or not we have the privilege to set the scheduling + * policy and priority of threads. 0 means that privilege is available. + * EPERM means that privilege is not available. + */ + +static PRIntn pt_schedpriv = 0; +extern PRLock *_pr_sleeplock; + +static struct _PT_Bookeeping +{ + PRLock *ml; /* a lock to protect ourselves */ + PRCondVar *cv; /* used to signal global things */ + PRInt32 system, user; /* a count of the two different types */ + PRUintn this_many; /* number of threads allowed for exit */ + pthread_key_t key; /* thread private data key */ + PRBool keyCreated; /* whether 'key' should be deleted */ + PRThread *first, *last; /* list of threads we know about */ +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + PRInt32 minPrio, maxPrio; /* range of scheduling priorities */ +#endif +} pt_book = {0}; + +static void _pt_thread_death(void *arg); +static void _pt_thread_death_internal(void *arg, PRBool callDestructors); +static void init_pthread_gc_support(void); + +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 +static PRIntn pt_PriorityMap(PRThreadPriority pri) +{ +#ifdef NTO + /* This priority algorithm causes lots of problems on Neutrino + * for now I have just hard coded everything to run at priority 10 + * until I can come up with a new algorithm. + * Jerry.Kirk@Nexwarecorp.com + */ + return 10; +#else + return pt_book.minPrio + + pri * (pt_book.maxPrio - pt_book.minPrio) / PR_PRIORITY_LAST; +#endif +} +#elif defined(_PR_NICE_PRIORITY_SCHEDULING) +/* + * This functions maps higher priorities to lower nice values relative to the + * nice value specified in the |nice| parameter. The corresponding relative + * adjustments are: + * + * PR_PRIORITY_LOW +1 + * PR_PRIORITY_NORMAL 0 + * PR_PRIORITY_HIGH -1 + * PR_PRIORITY_URGENT -2 + */ +static int pt_RelativePriority(int nice, PRThreadPriority pri) +{ + return nice + (1 - pri); +} +#endif + +/* +** Initialize a stack for a native pthread thread +*/ +static void _PR_InitializeStack(PRThreadStack *ts) +{ + if( ts && (ts->stackTop == 0) ) { + ts->allocBase = (char *) &ts; + ts->allocSize = ts->stackSize; + + /* + ** Setup stackTop and stackBottom values. + */ +#ifdef HAVE_STACK_GROWING_UP + ts->stackBottom = ts->allocBase + ts->stackSize; + ts->stackTop = ts->allocBase; +#else + ts->stackTop = ts->allocBase; + ts->stackBottom = ts->allocBase - ts->stackSize; +#endif + } +} + +static void *_pt_root(void *arg) +{ + PRIntn rv; + PRThread *thred = (PRThread*)arg; + PRBool detached = (thred->state & PT_THREAD_DETACHED) ? PR_TRUE : PR_FALSE; + pthread_t id = pthread_self(); +#ifdef _PR_NICE_PRIORITY_SCHEDULING + pid_t tid; +#endif + +#ifdef _PR_NICE_PRIORITY_SCHEDULING + /* + * We need to know the kernel thread ID of each thread in order to + * set its nice value hence we do it here instead of at creation time. + */ + tid = gettid(); + errno = 0; + rv = getpriority(PRIO_PROCESS, 0); + + /* If we cannot read the main thread's nice value don't try to change the + * new thread's nice value. */ + if (errno == 0) { + setpriority(PRIO_PROCESS, tid, + pt_RelativePriority(rv, thred->priority)); + } +#endif + + /* Set up the thread stack information */ + _PR_InitializeStack(thred->stack); + + /* + * Set within the current thread the pointer to our object. + * This object will be deleted when the thread termintates, + * whether in a join or detached (see _PR_InitThreads()). + */ + rv = pthread_setspecific(pt_book.key, thred); + PR_ASSERT(0 == rv); + + /* make the thread visible to the rest of the runtime */ + PR_Lock(pt_book.ml); + /* + * Both the parent thread and this new thread set thred->id. + * The new thread must ensure that thred->id is set before + * it executes its startFunc. The parent thread must ensure + * that thred->id is set before PR_CreateThread() returns. + * Both threads set thred->id while holding pt_book.ml and + * use thred->idSet to ensure thred->id is written only once. + */ + if (!thred->idSet) + { + thred->id = id; + thred->idSet = PR_TRUE; + } + else + { + PR_ASSERT(pthread_equal(thred->id, id)); + } + +#ifdef _PR_NICE_PRIORITY_SCHEDULING + thred->tid = tid; + PR_NotifyAllCondVar(pt_book.cv); +#endif + + /* If this is a GCABLE thread, set its state appropriately */ + if (thred->suspend & PT_THREAD_SETGCABLE) { + thred->state |= PT_THREAD_GCABLE; + } + thred->suspend = 0; + + thred->prev = pt_book.last; + if (pt_book.last) { + pt_book.last->next = thred; + } + else { + pt_book.first = thred; + } + thred->next = NULL; + pt_book.last = thred; + PR_Unlock(pt_book.ml); + + thred->startFunc(thred->arg); /* make visible to the client */ + + /* unhook the thread from the runtime */ + PR_Lock(pt_book.ml); + /* + * At this moment, PR_CreateThread() may not have set thred->id yet. + * It is safe for a detached thread to free thred only after + * PR_CreateThread() has accessed thred->id and thred->idSet. + */ + if (detached) + { + while (!thred->okToDelete) { + PR_WaitCondVar(pt_book.cv, PR_INTERVAL_NO_TIMEOUT); + } + } + + if (thred->state & PT_THREAD_SYSTEM) { + pt_book.system -= 1; + } + else if (--pt_book.user == pt_book.this_many) { + PR_NotifyAllCondVar(pt_book.cv); + } + if (NULL == thred->prev) { + pt_book.first = thred->next; + } + else { + thred->prev->next = thred->next; + } + if (NULL == thred->next) { + pt_book.last = thred->prev; + } + else { + thred->next->prev = thred->prev; + } + PR_Unlock(pt_book.ml); + + /* + * Here we set the pthread's backpointer to the PRThread to NULL. + * Otherwise the destructor would get called eagerly as the thread + * returns to the pthread runtime. The joining thread would them be + * the proud possessor of a dangling reference. However, this is the + * last chance to delete the object if the thread is detached, so + * just let the destructor do the work. + */ + if (PR_FALSE == detached) + { + /* Call TPD destructors on this thread. */ + _PR_DestroyThreadPrivate(thred); + rv = pthread_setspecific(pt_book.key, NULL); + PR_ASSERT(0 == rv); + } + + return NULL; +} /* _pt_root */ + +static PRThread* pt_AttachThread(void) +{ + PRThread *thred = NULL; + + /* + * NSPR must have been initialized when PR_AttachThread is called. + * We cannot have PR_AttachThread call implicit initialization + * because if multiple threads call PR_AttachThread simultaneously, + * NSPR may be initialized more than once. + * We can't call any function that calls PR_GetCurrentThread() + * either (e.g., PR_SetError()) as that will result in infinite + * recursion. + */ + if (!_pr_initialized) { + return NULL; + } + + /* PR_NEWZAP must not call PR_GetCurrentThread() */ + thred = PR_NEWZAP(PRThread); + if (NULL != thred) + { + int rv; + + thred->priority = PR_PRIORITY_NORMAL; + thred->id = pthread_self(); + thred->idSet = PR_TRUE; +#ifdef _PR_NICE_PRIORITY_SCHEDULING + thred->tid = gettid(); +#endif + rv = pthread_setspecific(pt_book.key, thred); + PR_ASSERT(0 == rv); + + thred->state = PT_THREAD_GLOBAL | PT_THREAD_FOREIGN; + PR_Lock(pt_book.ml); + + /* then put it into the list */ + thred->prev = pt_book.last; + if (pt_book.last) { + pt_book.last->next = thred; + } + else { + pt_book.first = thred; + } + thred->next = NULL; + pt_book.last = thred; + PR_Unlock(pt_book.ml); + + } + return thred; /* may be NULL */ +} /* pt_AttachThread */ + +static PRThread* _PR_CreateThread( + PRThreadType type, void (*start)(void *arg), + void *arg, PRThreadPriority priority, PRThreadScope scope, + PRThreadState state, PRUint32 stackSize, PRBool isGCAble) +{ + int rv; + PRThread *thred; + pthread_attr_t tattr; + + if (!_pr_initialized) { + _PR_ImplicitInitialization(); + } + + if ((PRIntn)PR_PRIORITY_FIRST > (PRIntn)priority) { + priority = PR_PRIORITY_FIRST; + } + else if ((PRIntn)PR_PRIORITY_LAST < (PRIntn)priority) { + priority = PR_PRIORITY_LAST; + } + + rv = _PT_PTHREAD_ATTR_INIT(&tattr); + PR_ASSERT(0 == rv); + + if (EPERM != pt_schedpriv) + { +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + struct sched_param schedule; +#endif + +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + rv = pthread_attr_setinheritsched(&tattr, PTHREAD_EXPLICIT_SCHED); + PR_ASSERT(0 == rv); +#endif + + /* Use the default scheduling policy */ + +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + rv = pthread_attr_getschedparam(&tattr, &schedule); + PR_ASSERT(0 == rv); + schedule.sched_priority = pt_PriorityMap(priority); + rv = pthread_attr_setschedparam(&tattr, &schedule); + PR_ASSERT(0 == rv); +#ifdef NTO + rv = pthread_attr_setschedpolicy(&tattr, SCHED_RR); /* Round Robin */ + PR_ASSERT(0 == rv); +#endif +#endif /* _POSIX_THREAD_PRIORITY_SCHEDULING > 0 */ + } + + rv = pthread_attr_setdetachstate(&tattr, + ((PR_JOINABLE_THREAD == state) ? + PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED)); + PR_ASSERT(0 == rv); + + /* + * If stackSize is 0, we use the default pthread stack size. + */ + if (stackSize) + { +#ifdef _MD_MINIMUM_STACK_SIZE + if (stackSize < _MD_MINIMUM_STACK_SIZE) { + stackSize = _MD_MINIMUM_STACK_SIZE; + } +#endif + rv = pthread_attr_setstacksize(&tattr, stackSize); + PR_ASSERT(0 == rv); + } + + thred = PR_NEWZAP(PRThread); + if (NULL == thred) + { + PR_SetError(PR_OUT_OF_MEMORY_ERROR, errno); + goto done; + } + else + { + pthread_t id; + + thred->arg = arg; + thred->startFunc = start; + thred->priority = priority; + if (PR_UNJOINABLE_THREAD == state) { + thred->state |= PT_THREAD_DETACHED; + } + + if (PR_LOCAL_THREAD == scope) { + scope = PR_GLOBAL_THREAD; + } + + if (PR_GLOBAL_BOUND_THREAD == scope) { +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + rv = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM); + if (rv) { + /* + * system scope not supported + */ + scope = PR_GLOBAL_THREAD; + /* + * reset scope + */ + rv = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_PROCESS); + PR_ASSERT(0 == rv); + } +#endif + } + if (PR_GLOBAL_THREAD == scope) { + thred->state |= PT_THREAD_GLOBAL; + } + else if (PR_GLOBAL_BOUND_THREAD == scope) { + thred->state |= (PT_THREAD_GLOBAL | PT_THREAD_BOUND); + } + else { /* force it global */ + thred->state |= PT_THREAD_GLOBAL; + } + if (PR_SYSTEM_THREAD == type) { + thred->state |= PT_THREAD_SYSTEM; + } + + thred->suspend =(isGCAble) ? PT_THREAD_SETGCABLE : 0; + + thred->stack = PR_NEWZAP(PRThreadStack); + if (thred->stack == NULL) { + PRIntn oserr = errno; + PR_Free(thred); /* all that work ... poof! */ + PR_SetError(PR_OUT_OF_MEMORY_ERROR, oserr); + thred = NULL; /* and for what? */ + goto done; + } + thred->stack->stackSize = stackSize; + thred->stack->thr = thred; + +#ifdef PT_NO_SIGTIMEDWAIT + pthread_mutex_init(&thred->suspendResumeMutex,NULL); + pthread_cond_init(&thred->suspendResumeCV,NULL); +#endif + + /* make the thread counted to the rest of the runtime */ + PR_Lock(pt_book.ml); + if (PR_SYSTEM_THREAD == type) { + pt_book.system += 1; + } + else { + pt_book.user += 1; + } + PR_Unlock(pt_book.ml); + + /* + * We pass a pointer to a local copy (instead of thred->id) + * to pthread_create() because who knows what wacky things + * pthread_create() may be doing to its argument. + */ + rv = _PT_PTHREAD_CREATE(&id, tattr, _pt_root, thred); + + if (EPERM == rv) + { + /* Remember that we don't have thread scheduling privilege. */ + pt_schedpriv = EPERM; + PR_LOG(_pr_thread_lm, PR_LOG_MIN, + ("_PR_CreateThread: no thread scheduling privilege")); + /* Try creating the thread again without setting priority. */ +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + rv = pthread_attr_setinheritsched(&tattr, PTHREAD_INHERIT_SCHED); + PR_ASSERT(0 == rv); +#endif + rv = _PT_PTHREAD_CREATE(&id, tattr, _pt_root, thred); + } + + if (0 != rv) + { + PRIntn oserr = rv; + PR_Lock(pt_book.ml); + if (thred->state & PT_THREAD_SYSTEM) { + pt_book.system -= 1; + } + else if (--pt_book.user == pt_book.this_many) { + PR_NotifyAllCondVar(pt_book.cv); + } + PR_Unlock(pt_book.ml); + + PR_Free(thred->stack); + PR_Free(thred); /* all that work ... poof! */ + PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, oserr); + thred = NULL; /* and for what? */ + goto done; + } + + PR_Lock(pt_book.ml); + /* + * Both the parent thread and this new thread set thred->id. + * The parent thread must ensure that thred->id is set before + * PR_CreateThread() returns. (See comments in _pt_root().) + */ + if (!thred->idSet) + { + thred->id = id; + thred->idSet = PR_TRUE; + } + else + { + PR_ASSERT(pthread_equal(thred->id, id)); + } + + /* + * If the new thread is detached, tell it that PR_CreateThread() has + * accessed thred->id and thred->idSet so it's ok to delete thred. + */ + if (PR_UNJOINABLE_THREAD == state) + { + thred->okToDelete = PR_TRUE; + PR_NotifyAllCondVar(pt_book.cv); + } + PR_Unlock(pt_book.ml); + } + +done: + rv = _PT_PTHREAD_ATTR_DESTROY(&tattr); + PR_ASSERT(0 == rv); + + return thred; +} /* _PR_CreateThread */ + +PR_IMPLEMENT(PRThread*) PR_CreateThread( + PRThreadType type, void (*start)(void *arg), void *arg, + PRThreadPriority priority, PRThreadScope scope, + PRThreadState state, PRUint32 stackSize) +{ + return _PR_CreateThread( + type, start, arg, priority, scope, state, stackSize, PR_FALSE); +} /* PR_CreateThread */ + +PR_IMPLEMENT(PRThread*) PR_CreateThreadGCAble( + PRThreadType type, void (*start)(void *arg), void *arg, + PRThreadPriority priority, PRThreadScope scope, + PRThreadState state, PRUint32 stackSize) +{ + return _PR_CreateThread( + type, start, arg, priority, scope, state, stackSize, PR_TRUE); +} /* PR_CreateThreadGCAble */ + +PR_IMPLEMENT(void*) GetExecutionEnvironment(PRThread *thred) +{ + return thred->environment; +} /* GetExecutionEnvironment */ + +PR_IMPLEMENT(void) SetExecutionEnvironment(PRThread *thred, void *env) +{ + thred->environment = env; +} /* SetExecutionEnvironment */ + +PR_IMPLEMENT(PRThread*) PR_AttachThread( + PRThreadType type, PRThreadPriority priority, PRThreadStack *stack) +{ + return PR_GetCurrentThread(); +} /* PR_AttachThread */ + + +PR_IMPLEMENT(PRStatus) PR_JoinThread(PRThread *thred) +{ + int rv = -1; + void *result = NULL; + PR_ASSERT(thred != NULL); + + if ((0xafafafaf == thred->state) + || (PT_THREAD_DETACHED == (PT_THREAD_DETACHED & thred->state)) + || (PT_THREAD_FOREIGN == (PT_THREAD_FOREIGN & thred->state))) + { + /* + * This might be a bad address, but if it isn't, the state should + * either be an unjoinable thread or it's already had the object + * deleted. However, the client that called join on a detached + * thread deserves all the rath I can muster.... + */ + PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); + PR_LogPrint( + "PR_JoinThread: %p not joinable | already smashed\n", thred); + } + else + { + pthread_t id = thred->id; + rv = pthread_join(id, &result); + PR_ASSERT(rv == 0 && result == NULL); + if (0 == rv) + { + /* + * PR_FALSE, because the thread already called the TPD + * destructors before exiting _pt_root. + */ + _pt_thread_death_internal(thred, PR_FALSE); + } + else + { + PRErrorCode prerror; + switch (rv) + { + case EINVAL: /* not a joinable thread */ + case ESRCH: /* no thread with given ID */ + prerror = PR_INVALID_ARGUMENT_ERROR; + break; + case EDEADLK: /* a thread joining with itself */ + prerror = PR_DEADLOCK_ERROR; + break; + default: + prerror = PR_UNKNOWN_ERROR; + break; + } + PR_SetError(prerror, rv); + } + } + return (0 == rv) ? PR_SUCCESS : PR_FAILURE; +} /* PR_JoinThread */ + +PR_IMPLEMENT(void) PR_DetachThread(void) +{ + void *thred; + int rv; + + _PT_PTHREAD_GETSPECIFIC(pt_book.key, thred); + if (NULL == thred) { + return; + } + _pt_thread_death(thred); + rv = pthread_setspecific(pt_book.key, NULL); + PR_ASSERT(0 == rv); +} /* PR_DetachThread */ + +PR_IMPLEMENT(PRThread*) PR_GetCurrentThread(void) +{ + void *thred; + + if (!_pr_initialized) { + _PR_ImplicitInitialization(); + } + + _PT_PTHREAD_GETSPECIFIC(pt_book.key, thred); + if (NULL == thred) { + thred = pt_AttachThread(); + } + PR_ASSERT(NULL != thred); + return (PRThread*)thred; +} /* PR_GetCurrentThread */ + +PR_IMPLEMENT(PRThreadScope) PR_GetThreadScope(const PRThread *thred) +{ + return (thred->state & PT_THREAD_BOUND) ? + PR_GLOBAL_BOUND_THREAD : PR_GLOBAL_THREAD; +} /* PR_GetThreadScope() */ + +PR_IMPLEMENT(PRThreadType) PR_GetThreadType(const PRThread *thred) +{ + return (thred->state & PT_THREAD_SYSTEM) ? + PR_SYSTEM_THREAD : PR_USER_THREAD; +} + +PR_IMPLEMENT(PRThreadState) PR_GetThreadState(const PRThread *thred) +{ + return (thred->state & PT_THREAD_DETACHED) ? + PR_UNJOINABLE_THREAD : PR_JOINABLE_THREAD; +} /* PR_GetThreadState */ + +PR_IMPLEMENT(PRThreadPriority) PR_GetThreadPriority(const PRThread *thred) +{ + PR_ASSERT(thred != NULL); + return thred->priority; +} /* PR_GetThreadPriority */ + +PR_IMPLEMENT(void) PR_SetThreadPriority(PRThread *thred, PRThreadPriority newPri) +{ + PRIntn rv; + + PR_ASSERT(NULL != thred); + + if ((PRIntn)PR_PRIORITY_FIRST > (PRIntn)newPri) { + newPri = PR_PRIORITY_FIRST; + } + else if ((PRIntn)PR_PRIORITY_LAST < (PRIntn)newPri) { + newPri = PR_PRIORITY_LAST; + } + +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 + if (EPERM != pt_schedpriv) + { + int policy; + struct sched_param schedule; + + rv = pthread_getschedparam(thred->id, &policy, &schedule); + if(0 == rv) { + schedule.sched_priority = pt_PriorityMap(newPri); + rv = pthread_setschedparam(thred->id, policy, &schedule); + if (EPERM == rv) + { + pt_schedpriv = EPERM; + PR_LOG(_pr_thread_lm, PR_LOG_MIN, + ("PR_SetThreadPriority: no thread scheduling privilege")); + } + } + if (rv != 0) { + rv = -1; + } + } +#elif defined(_PR_NICE_PRIORITY_SCHEDULING) + PR_Lock(pt_book.ml); + while (thred->tid == 0) { + PR_WaitCondVar(pt_book.cv, PR_INTERVAL_NO_TIMEOUT); + } + PR_Unlock(pt_book.ml); + + errno = 0; + rv = getpriority(PRIO_PROCESS, 0); + + /* Do not proceed unless we know the main thread's nice value. */ + if (errno == 0) { + rv = setpriority(PRIO_PROCESS, thred->tid, + pt_RelativePriority(rv, newPri)); + + if (rv == -1) + { + /* We don't set pt_schedpriv to EPERM in case errno == EPERM + * because adjusting the nice value might be permitted for certain + * ranges but not for others. */ + PR_LOG(_pr_thread_lm, PR_LOG_MIN, + ("PR_SetThreadPriority: setpriority failed with error %d", + errno)); + } + } +#else + (void)rv; /* rv is unused */ +#endif + + thred->priority = newPri; +} /* PR_SetThreadPriority */ + +PR_IMPLEMENT(PRStatus) PR_Interrupt(PRThread *thred) +{ + /* + ** If the target thread indicates that it's waiting, + ** find the condition and broadcast to it. Broadcast + ** since we don't know which thread (if there are more + ** than one). This sounds risky, but clients must + ** test their invariants when resumed from a wait and + ** I don't expect very many threads to be waiting on + ** a single condition and I don't expect interrupt to + ** be used very often. + ** + ** I don't know why I thought this would work. Must have + ** been one of those weaker momements after I'd been + ** smelling the vapors. + ** + ** Even with the followng changes it is possible that + ** the pointer to the condition variable is pointing + ** at a bogus value. Will the unerlying code detect + ** that? + */ + PRCondVar *cv; + PR_ASSERT(NULL != thred); + if (NULL == thred) { + return PR_FAILURE; + } + + thred->state |= PT_THREAD_ABORTED; + + cv = thred->waiting; + if ((NULL != cv) && !thred->interrupt_blocked) + { + PRIntn rv; + (void)PR_ATOMIC_INCREMENT(&cv->notify_pending); + rv = pthread_cond_broadcast(&cv->cv); + PR_ASSERT(0 == rv); + if (0 > PR_ATOMIC_DECREMENT(&cv->notify_pending)) { + PR_DestroyCondVar(cv); + } + } + return PR_SUCCESS; +} /* PR_Interrupt */ + +PR_IMPLEMENT(void) PR_ClearInterrupt(void) +{ + PRThread *me = PR_GetCurrentThread(); + me->state &= ~PT_THREAD_ABORTED; +} /* PR_ClearInterrupt */ + +PR_IMPLEMENT(void) PR_BlockInterrupt(void) +{ + PRThread *me = PR_GetCurrentThread(); + _PT_THREAD_BLOCK_INTERRUPT(me); +} /* PR_BlockInterrupt */ + +PR_IMPLEMENT(void) PR_UnblockInterrupt(void) +{ + PRThread *me = PR_GetCurrentThread(); + _PT_THREAD_UNBLOCK_INTERRUPT(me); +} /* PR_UnblockInterrupt */ + +PR_IMPLEMENT(PRStatus) PR_Yield(void) +{ + static PRBool warning = PR_TRUE; + if (warning) warning = _PR_Obsolete( + "PR_Yield()", "PR_Sleep(PR_INTERVAL_NO_WAIT)"); + return PR_Sleep(PR_INTERVAL_NO_WAIT); +} + +PR_IMPLEMENT(PRStatus) PR_Sleep(PRIntervalTime ticks) +{ + PRStatus rv = PR_SUCCESS; + + if (!_pr_initialized) { + _PR_ImplicitInitialization(); + } + + if (PR_INTERVAL_NO_WAIT == ticks) + { + _PT_PTHREAD_YIELD(); + } + else + { + PRCondVar *cv; + PRIntervalTime timein; + + timein = PR_IntervalNow(); + cv = PR_NewCondVar(_pr_sleeplock); + PR_ASSERT(cv != NULL); + PR_Lock(_pr_sleeplock); + do + { + PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime delta = now - timein; + if (delta > ticks) { + break; + } + rv = PR_WaitCondVar(cv, ticks - delta); + } while (PR_SUCCESS == rv); + PR_Unlock(_pr_sleeplock); + PR_DestroyCondVar(cv); + } + return rv; +} /* PR_Sleep */ + +static void _pt_thread_death(void *arg) +{ + void *thred; + int rv; + + _PT_PTHREAD_GETSPECIFIC(pt_book.key, thred); + if (NULL == thred) + { + /* + * Have PR_GetCurrentThread return the expected value to the + * destructors. + */ + rv = pthread_setspecific(pt_book.key, arg); + PR_ASSERT(0 == rv); + } + + /* PR_TRUE for: call destructors */ + _pt_thread_death_internal(arg, PR_TRUE); + + if (NULL == thred) + { + rv = pthread_setspecific(pt_book.key, NULL); + PR_ASSERT(0 == rv); + } +} + +static void _pt_thread_death_internal(void *arg, PRBool callDestructors) +{ + PRThread *thred = (PRThread*)arg; + + if (thred->state & (PT_THREAD_FOREIGN|PT_THREAD_PRIMORD)) + { + PR_Lock(pt_book.ml); + if (NULL == thred->prev) { + pt_book.first = thred->next; + } + else { + thred->prev->next = thred->next; + } + if (NULL == thred->next) { + pt_book.last = thred->prev; + } + else { + thred->next->prev = thred->prev; + } + PR_Unlock(pt_book.ml); + } + if (callDestructors) { + _PR_DestroyThreadPrivate(thred); + } + PR_Free(thred->privateData); + if (NULL != thred->errorString) { + PR_Free(thred->errorString); + } + if (NULL != thred->name) { + PR_Free(thred->name); + } + PR_Free(thred->stack); + if (NULL != thred->syspoll_list) { + PR_Free(thred->syspoll_list); + } +#if defined(_PR_POLL_WITH_SELECT) + if (NULL != thred->selectfd_list) { + PR_Free(thred->selectfd_list); + } +#endif +#if defined(DEBUG) + memset(thred, 0xaf, sizeof(PRThread)); +#endif /* defined(DEBUG) */ + PR_Free(thred); +} /* _pt_thread_death */ + +void _PR_InitThreads( + PRThreadType type, PRThreadPriority priority, PRUintn maxPTDs) +{ + int rv; + PRThread *thred; + + PR_ASSERT(priority == PR_PRIORITY_NORMAL); + +#ifdef _PR_NEED_PTHREAD_INIT + /* + * On BSD/OS (3.1 and 4.0), the pthread subsystem is lazily + * initialized, but pthread_self() fails to initialize + * pthreads and hence returns a null thread ID if invoked + * by the primordial thread before any other pthread call. + * So we explicitly initialize pthreads here. + */ + pthread_init(); +#endif + +#if _POSIX_THREAD_PRIORITY_SCHEDULING > 0 +#if defined(FREEBSD) + { + pthread_attr_t attr; + int policy; + /* get the min and max priorities of the default policy */ + pthread_attr_init(&attr); + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_getschedpolicy(&attr, &policy); + pt_book.minPrio = sched_get_priority_min(policy); + PR_ASSERT(-1 != pt_book.minPrio); + pt_book.maxPrio = sched_get_priority_max(policy); + PR_ASSERT(-1 != pt_book.maxPrio); + pthread_attr_destroy(&attr); + } +#else + /* + ** These might be function evaluations + */ + pt_book.minPrio = PT_PRIO_MIN; + pt_book.maxPrio = PT_PRIO_MAX; +#endif +#endif + + PR_ASSERT(NULL == pt_book.ml); + pt_book.ml = PR_NewLock(); + PR_ASSERT(NULL != pt_book.ml); + pt_book.cv = PR_NewCondVar(pt_book.ml); + PR_ASSERT(NULL != pt_book.cv); + thred = PR_NEWZAP(PRThread); + PR_ASSERT(NULL != thred); + thred->arg = NULL; + thred->startFunc = NULL; + thred->priority = priority; + thred->id = pthread_self(); + thred->idSet = PR_TRUE; +#ifdef _PR_NICE_PRIORITY_SCHEDULING + thred->tid = gettid(); +#endif + + thred->state = (PT_THREAD_DETACHED | PT_THREAD_PRIMORD); + if (PR_SYSTEM_THREAD == type) + { + thred->state |= PT_THREAD_SYSTEM; + pt_book.system += 1; + pt_book.this_many = 0; + } + else + { + pt_book.user += 1; + pt_book.this_many = 1; + } + thred->next = thred->prev = NULL; + pt_book.first = pt_book.last = thred; + + thred->stack = PR_NEWZAP(PRThreadStack); + PR_ASSERT(thred->stack != NULL); + thred->stack->stackSize = 0; + thred->stack->thr = thred; + _PR_InitializeStack(thred->stack); + + /* + * Create a key for our use to store a backpointer in the pthread + * to our PRThread object. This object gets deleted when the thread + * returns from its root in the case of a detached thread. Other + * threads delete the objects in Join. + * + * NB: The destructor logic seems to have a bug so it isn't used. + * NBB: Oh really? I'm going to give it a spin - AOF 19 June 1998. + * More info - the problem is that pthreads calls the destructor + * eagerly as the thread returns from its root, rather than lazily + * after the thread is joined. Therefore, threads that are joining + * and holding PRThread references are actually holding pointers to + * nothing. + */ + rv = _PT_PTHREAD_KEY_CREATE(&pt_book.key, _pt_thread_death); + if (0 != rv) { + PR_Assert("0 == rv", __FILE__, __LINE__); + } + pt_book.keyCreated = PR_TRUE; + rv = pthread_setspecific(pt_book.key, thred); + PR_ASSERT(0 == rv); +} /* _PR_InitThreads */ + +#ifdef __GNUC__ +/* + * GCC supports the constructor and destructor attributes as of + * version 2.5. + */ +#if defined(DARWIN) +/* + * The dynamic linker on OSX doesn't execute __attribute__((destructor)) + * destructors in the right order wrt non-__attribute((destructor)) destructors + * in other libraries. So use atexit() instead, which does. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1399746#c99 + */ +static void _PR_Fini(void); + +__attribute__ ((constructor)) +static void _register_PR_Fini() { + atexit(_PR_Fini); +} +#else +static void _PR_Fini(void) __attribute__ ((destructor)); +#endif + +#elif defined(__SUNPRO_C) +/* + * Sun Studio compiler + */ +#pragma fini(_PR_Fini) +static void _PR_Fini(void); +#elif defined(HPUX) +/* + * Current versions of HP C compiler define __HP_cc. + * HP C compiler A.11.01.20 doesn't define __HP_cc. + */ +#if defined(__ia64) || defined(_LP64) +#pragma FINI "_PR_Fini" +static void _PR_Fini(void); +#else +/* + * Only HP-UX 10.x style initializers are supported in 32-bit links. + * Need to use the +I PR_HPUX10xInit linker option. + */ +#include <dl.h> + +static void _PR_Fini(void); + +void PR_HPUX10xInit(shl_t handle, int loading) +{ + /* + * This function is called when a shared library is loaded as well + * as when the shared library is unloaded. Note that it may not + * be called when the user's program terminates. + * + * handle is the shl_load API handle for the shared library being + * initialized. + * + * loading is non-zero at startup and zero at termination. + */ + if (loading) { + /* ... do some initializations ... */ + } else { + _PR_Fini(); + } +} +#endif +#elif defined(AIX) +/* Need to use the -binitfini::_PR_Fini linker option. */ +#endif + +void _PR_Fini(void) +{ + void *thred; + int rv; + + if (!_pr_initialized) { + /* Either NSPR was never successfully initialized or + * PR_Cleanup has been called already. */ + if (pt_book.keyCreated) + { + rv = pthread_key_delete(pt_book.key); + PR_ASSERT(0 == rv); + pt_book.keyCreated = PR_FALSE; + } + return; + } + + _PT_PTHREAD_GETSPECIFIC(pt_book.key, thred); + if (NULL != thred) + { + /* + * PR_FALSE, because it is unsafe to call back to the + * thread private data destructors at final cleanup. + */ + _pt_thread_death_internal(thred, PR_FALSE); + rv = pthread_setspecific(pt_book.key, NULL); + PR_ASSERT(0 == rv); + } + rv = pthread_key_delete(pt_book.key); + PR_ASSERT(0 == rv); + pt_book.keyCreated = PR_FALSE; + /* TODO: free other resources used by NSPR */ + /* _pr_initialized = PR_FALSE; */ +} /* _PR_Fini */ + +PR_IMPLEMENT(PRStatus) PR_Cleanup(void) +{ + PRThread *me = PR_GetCurrentThread(); + int rv; + PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("PR_Cleanup: shutting down NSPR")); + PR_ASSERT(me->state & PT_THREAD_PRIMORD); + if (me->state & PT_THREAD_PRIMORD) + { + PR_Lock(pt_book.ml); + while (pt_book.user > pt_book.this_many) { + PR_WaitCondVar(pt_book.cv, PR_INTERVAL_NO_TIMEOUT); + } + if (me->state & PT_THREAD_SYSTEM) { + pt_book.system -= 1; + } + else { + pt_book.user -= 1; + } + PR_Unlock(pt_book.ml); + + _PR_MD_EARLY_CLEANUP(); + + _PR_CleanupMW(); + _PR_CleanupTime(); + _PR_CleanupDtoa(); + _PR_CleanupCallOnce(); + _PR_ShutdownLinker(); + _PR_LogCleanup(); + _PR_CleanupNet(); + /* Close all the fd's before calling _PR_CleanupIO */ + _PR_CleanupIO(); + _PR_CleanupCMon(); + + _pt_thread_death(me); + rv = pthread_setspecific(pt_book.key, NULL); + PR_ASSERT(0 == rv); + /* + * I am not sure if it's safe to delete the cv and lock here, + * since there may still be "system" threads around. If this + * call isn't immediately prior to exiting, then there's a + * problem. + */ + if (0 == pt_book.system) + { + PR_DestroyCondVar(pt_book.cv); pt_book.cv = NULL; + PR_DestroyLock(pt_book.ml); pt_book.ml = NULL; + } + PR_DestroyLock(_pr_sleeplock); + _pr_sleeplock = NULL; + _PR_CleanupLayerCache(); + _PR_CleanupEnv(); +#ifdef _PR_ZONE_ALLOCATOR + _PR_DestroyZones(); +#endif + _pr_initialized = PR_FALSE; + return PR_SUCCESS; + } + return PR_FAILURE; +} /* PR_Cleanup */ + +PR_IMPLEMENT(void) PR_ProcessExit(PRIntn status) +{ + _exit(status); +} + +PR_IMPLEMENT(PRUint32) PR_GetThreadID(PRThread *thred) +{ + return (PRUint32)thred->id; /* and I don't know what they will do with it */ +} + +/* + * $$$ + * The following two thread-to-processor affinity functions are not + * yet implemented for pthreads. By the way, these functions should return + * PRStatus rather than PRInt32 to indicate the success/failure status. + * $$$ + */ + +PR_IMPLEMENT(PRInt32) PR_GetThreadAffinityMask(PRThread *thread, PRUint32 *mask) +{ + return 0; /* not implemented */ +} + +PR_IMPLEMENT(PRInt32) PR_SetThreadAffinityMask(PRThread *thread, PRUint32 mask ) +{ + return 0; /* not implemented */ +} + +PR_IMPLEMENT(void) +PR_SetThreadDumpProc(PRThread* thread, PRThreadDumpProc dump, void *arg) +{ + thread->dump = dump; + thread->dumpArg = arg; +} + +/* + * Garbage collection support follows. + */ + +/* a bogus signal mask for forcing a timed wait */ +/* Not so bogus in AIX as we really do a sigwait */ +static sigset_t sigwait_set; + +static struct timespec onemillisec = {0, 1000000L}; +#ifndef PT_NO_SIGTIMEDWAIT +static struct timespec hundredmillisec = {0, 100000000L}; +#endif + +static void suspend_signal_handler(PRIntn sig); + +#ifdef PT_NO_SIGTIMEDWAIT +static void null_signal_handler(PRIntn sig); +#endif + +/* + * Linux pthreads use SIGUSR1 and SIGUSR2 internally, which + * conflict with the use of these two signals in our GC support. + * So we don't know how to support GC on Linux pthreads. + */ +static void init_pthread_gc_support(void) +{ + PRIntn rv; + + { + struct sigaction sigact_usr2; + + sigact_usr2.sa_handler = suspend_signal_handler; + sigact_usr2.sa_flags = SA_RESTART; + sigemptyset (&sigact_usr2.sa_mask); + + rv = sigaction (SIGUSR2, &sigact_usr2, NULL); + PR_ASSERT(0 == rv); + + sigemptyset (&sigwait_set); +#if defined(PT_NO_SIGTIMEDWAIT) + sigaddset (&sigwait_set, SIGUSR1); +#else + sigaddset (&sigwait_set, SIGUSR2); +#endif /* defined(PT_NO_SIGTIMEDWAIT) */ + } +#if defined(PT_NO_SIGTIMEDWAIT) + { + struct sigaction sigact_null; + sigact_null.sa_handler = null_signal_handler; + sigact_null.sa_flags = SA_RESTART; + sigemptyset (&sigact_null.sa_mask); + rv = sigaction (SIGUSR1, &sigact_null, NULL); + PR_ASSERT(0 ==rv); + } +#endif /* defined(PT_NO_SIGTIMEDWAIT) */ +} + +PR_IMPLEMENT(void) PR_SetThreadGCAble(void) +{ + PR_Lock(pt_book.ml); + PR_GetCurrentThread()->state |= PT_THREAD_GCABLE; + PR_Unlock(pt_book.ml); +} + +PR_IMPLEMENT(void) PR_ClearThreadGCAble(void) +{ + PR_Lock(pt_book.ml); + PR_GetCurrentThread()->state &= (~PT_THREAD_GCABLE); + PR_Unlock(pt_book.ml); +} + +#if defined(DEBUG) +static PRBool suspendAllOn = PR_FALSE; +#endif + +static PRBool suspendAllSuspended = PR_FALSE; + +PR_IMPLEMENT(PRStatus) PR_EnumerateThreads(PREnumerator func, void *arg) +{ + PRIntn count = 0; + PRStatus rv = PR_SUCCESS; + PRThread* thred = pt_book.first; + +#if defined(DEBUG) || defined(FORCE_PR_ASSERT) + PRThread *me = PR_GetCurrentThread(); +#endif + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_EnumerateThreads\n")); + /* + * $$$ + * Need to suspend all threads other than me before doing this. + * This is really a gross and disgusting thing to do. The only + * good thing is that since all other threads are suspended, holding + * the lock during a callback seems like child's play. + * $$$ + */ + PR_ASSERT(suspendAllOn); + + while (thred != NULL) + { + /* Steve Morse, 4-23-97: Note that we can't walk a queue by taking + * qp->next after applying the function "func". In particular, "func" + * might remove the thread from the queue and put it into another one in + * which case qp->next no longer points to the next entry in the original + * queue. + * + * To get around this problem, we save qp->next in qp_next before applying + * "func" and use that saved value as the next value after applying "func". + */ + PRThread* next = thred->next; + + if (_PT_IS_GCABLE_THREAD(thred)) + { + PR_ASSERT((thred == me) || (thred->suspend & PT_THREAD_SUSPENDED)); + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("In PR_EnumerateThreads callback thread %p thid = %X\n", + thred, thred->id)); + + rv = func(thred, count++, arg); + if (rv != PR_SUCCESS) { + return rv; + } + } + thred = next; + } + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("End PR_EnumerateThreads count = %d \n", count)); + return rv; +} /* PR_EnumerateThreads */ + +/* + * PR_SuspendAll and PR_ResumeAll are called during garbage collection. The strategy + * we use is to send a SIGUSR2 signal to every gc able thread that we intend to suspend. + * The signal handler will record the stack pointer and will block until resumed by + * the resume call. Since the signal handler is the last routine called for the + * suspended thread, the stack pointer will also serve as a place where all the + * registers have been saved on the stack for the previously executing routines. + * + * Through global variables, we also make sure that PR_Suspend and PR_Resume does not + * proceed until the thread is suspended or resumed. + */ + +/* + * In the signal handler, we can not use condition variable notify or wait. + * This does not work consistently across all pthread platforms. We also can not + * use locking since that does not seem to work reliably across platforms. + * Only thing we can do is yielding while testing for a global condition + * to change. This does work on pthread supported platforms. We may have + * to play with priortities if there are any problems detected. + */ + +/* + * In AIX, you cannot use ANY pthread calls in the signal handler except perhaps + * pthread_yield. But that is horribly inefficient. Hence we use only sigwait, no + * sigtimedwait is available. We need to use another user signal, SIGUSR1. Actually + * SIGUSR1 is also used by exec in Java. So our usage here breaks the exec in Java, + * for AIX. You cannot use pthread_cond_wait or pthread_delay_np in the signal + * handler as all synchronization mechanisms just break down. + */ + +#if defined(PT_NO_SIGTIMEDWAIT) +static void null_signal_handler(PRIntn sig) +{ + return; +} +#endif + +static void suspend_signal_handler(PRIntn sig) +{ + PRThread *me = PR_GetCurrentThread(); + + PR_ASSERT(me != NULL); + PR_ASSERT(_PT_IS_GCABLE_THREAD(me)); + PR_ASSERT((me->suspend & PT_THREAD_SUSPENDED) == 0); + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("Begin suspend_signal_handler thred %p thread id = %X\n", + me, me->id)); + + /* + * save stack pointer + */ + me->sp = &me; + + /* + At this point, the thread's stack pointer has been saved, + And it is going to enter a wait loop until it is resumed. + So it is _really_ suspended + */ + + me->suspend |= PT_THREAD_SUSPENDED; + + /* + * now, block current thread + */ +#if defined(PT_NO_SIGTIMEDWAIT) + pthread_cond_signal(&me->suspendResumeCV); + while (me->suspend & PT_THREAD_SUSPENDED) + { +#if !defined(FREEBSD) && !defined(NETBSD) && !defined(OPENBSD) \ + && !defined(BSDI) && !defined(UNIXWARE) \ + && !defined(DARWIN) && !defined(RISCOS) + PRIntn rv; + sigwait(&sigwait_set, &rv); +#endif + } + me->suspend |= PT_THREAD_RESUMED; + pthread_cond_signal(&me->suspendResumeCV); +#else /* defined(PT_NO_SIGTIMEDWAIT) */ + while (me->suspend & PT_THREAD_SUSPENDED) + { + PRIntn rv = sigtimedwait(&sigwait_set, NULL, &hundredmillisec); + PR_ASSERT(-1 == rv); + } + me->suspend |= PT_THREAD_RESUMED; +#endif + + /* + * At this point, thread has been resumed, so set a global condition. + * The ResumeAll needs to know that this has really been resumed. + * So the signal handler sets a flag which PR_ResumeAll will reset. + * The PR_ResumeAll must reset this flag ... + */ + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("End suspend_signal_handler thred = %p tid = %X\n", me, me->id)); +} /* suspend_signal_handler */ + +static void pt_SuspendSet(PRThread *thred) +{ + PRIntn rv; + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("pt_SuspendSet thred %p thread id = %X\n", thred, thred->id)); + + + /* + * Check the thread state and signal the thread to suspend + */ + + PR_ASSERT((thred->suspend & PT_THREAD_SUSPENDED) == 0); + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("doing pthread_kill in pt_SuspendSet thred %p tid = %X\n", + thred, thred->id)); + rv = pthread_kill (thred->id, SIGUSR2); + PR_ASSERT(0 == rv); +} + +static void pt_SuspendTest(PRThread *thred) +{ + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("Begin pt_SuspendTest thred %p thread id = %X\n", thred, thred->id)); + + + /* + * Wait for the thread to be really suspended. This happens when the + * suspend signal handler stores the stack pointer and sets the state + * to suspended. + */ + +#if defined(PT_NO_SIGTIMEDWAIT) + pthread_mutex_lock(&thred->suspendResumeMutex); + while ((thred->suspend & PT_THREAD_SUSPENDED) == 0) + { + pthread_cond_timedwait( + &thred->suspendResumeCV, &thred->suspendResumeMutex, &onemillisec); + } + pthread_mutex_unlock(&thred->suspendResumeMutex); +#else + while ((thred->suspend & PT_THREAD_SUSPENDED) == 0) + { + PRIntn rv = sigtimedwait(&sigwait_set, NULL, &onemillisec); + PR_ASSERT(-1 == rv); + } +#endif + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("End pt_SuspendTest thred %p tid %X\n", thred, thred->id)); +} /* pt_SuspendTest */ + +static void pt_ResumeSet(PRThread *thred) +{ + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("pt_ResumeSet thred %p thread id = %X\n", thred, thred->id)); + + /* + * Clear the global state and set the thread state so that it will + * continue past yield loop in the suspend signal handler + */ + + PR_ASSERT(thred->suspend & PT_THREAD_SUSPENDED); + + + thred->suspend &= ~PT_THREAD_SUSPENDED; + +#if defined(PT_NO_SIGTIMEDWAIT) + pthread_kill(thred->id, SIGUSR1); +#endif + +} /* pt_ResumeSet */ + +static void pt_ResumeTest(PRThread *thred) +{ + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("Begin pt_ResumeTest thred %p thread id = %X\n", thred, thred->id)); + + /* + * Wait for the threads resume state to change + * to indicate it is really resumed + */ +#if defined(PT_NO_SIGTIMEDWAIT) + pthread_mutex_lock(&thred->suspendResumeMutex); + while ((thred->suspend & PT_THREAD_RESUMED) == 0) + { + pthread_cond_timedwait( + &thred->suspendResumeCV, &thred->suspendResumeMutex, &onemillisec); + } + pthread_mutex_unlock(&thred->suspendResumeMutex); +#else + while ((thred->suspend & PT_THREAD_RESUMED) == 0) { + PRIntn rv = sigtimedwait(&sigwait_set, NULL, &onemillisec); + PR_ASSERT(-1 == rv); + } +#endif + + thred->suspend &= ~PT_THREAD_RESUMED; + + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ( + "End pt_ResumeTest thred %p tid %X\n", thred, thred->id)); +} /* pt_ResumeTest */ + +static pthread_once_t pt_gc_support_control = PTHREAD_ONCE_INIT; + +PR_IMPLEMENT(void) PR_SuspendAll(void) +{ +#ifdef DEBUG + PRIntervalTime stime, etime; +#endif + PRThread* thred = pt_book.first; + PRThread *me = PR_GetCurrentThread(); + int rv; + + rv = pthread_once(&pt_gc_support_control, init_pthread_gc_support); + PR_ASSERT(0 == rv); + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_SuspendAll\n")); + /* + * Stop all threads which are marked GC able. + */ + PR_Lock(pt_book.ml); +#ifdef DEBUG + suspendAllOn = PR_TRUE; + stime = PR_IntervalNow(); +#endif + while (thred != NULL) + { + if ((thred != me) && _PT_IS_GCABLE_THREAD(thred)) { + pt_SuspendSet(thred); + } + thred = thred->next; + } + + /* Wait till they are really suspended */ + thred = pt_book.first; + while (thred != NULL) + { + if ((thred != me) && _PT_IS_GCABLE_THREAD(thred)) { + pt_SuspendTest(thred); + } + thred = thred->next; + } + + suspendAllSuspended = PR_TRUE; + +#ifdef DEBUG + etime = PR_IntervalNow(); + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS,\ + ("End PR_SuspendAll (time %dms)\n", + PR_IntervalToMilliseconds(etime - stime))); +#endif +} /* PR_SuspendAll */ + +PR_IMPLEMENT(void) PR_ResumeAll(void) +{ +#ifdef DEBUG + PRIntervalTime stime, etime; +#endif + PRThread* thred = pt_book.first; + PRThread *me = PR_GetCurrentThread(); + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, ("Begin PR_ResumeAll\n")); + /* + * Resume all previously suspended GC able threads. + */ + suspendAllSuspended = PR_FALSE; +#ifdef DEBUG + stime = PR_IntervalNow(); +#endif + + while (thred != NULL) + { + if ((thred != me) && _PT_IS_GCABLE_THREAD(thred)) { + pt_ResumeSet(thred); + } + thred = thred->next; + } + + thred = pt_book.first; + while (thred != NULL) + { + if ((thred != me) && _PT_IS_GCABLE_THREAD(thred)) { + pt_ResumeTest(thred); + } + thred = thred->next; + } + + PR_Unlock(pt_book.ml); +#ifdef DEBUG + suspendAllOn = PR_FALSE; + etime = PR_IntervalNow(); + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("End PR_ResumeAll (time %dms)\n", + PR_IntervalToMilliseconds(etime - stime))); +#endif +} /* PR_ResumeAll */ + +/* Return the stack pointer for the given thread- used by the GC */ +PR_IMPLEMENT(void *)PR_GetSP(PRThread *thred) +{ + PR_LOG(_pr_gc_lm, PR_LOG_ALWAYS, + ("in PR_GetSP thred %p thid = %X, sp = %p\n", + thred, thred->id, thred->sp)); + return thred->sp; +} /* PR_GetSP */ + +PR_IMPLEMENT(PRStatus) PR_SetCurrentThreadName(const char *name) +{ + PRThread *thread; + size_t nameLen; + int result = 0; + + if (!name) { + PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); + return PR_FAILURE; + } + + thread = PR_GetCurrentThread(); + if (!thread) { + return PR_FAILURE; + } + + PR_Free(thread->name); + nameLen = strlen(name); + thread->name = (char *)PR_Malloc(nameLen + 1); + if (!thread->name) { + return PR_FAILURE; + } + memcpy(thread->name, name, nameLen + 1); + +#if defined(OPENBSD) || defined(FREEBSD) || defined(DRAGONFLY) + pthread_set_name_np(thread->id, name); +#elif defined(ANDROID) + prctl(PR_SET_NAME, (unsigned long)(name)); +#elif defined(NETBSD) + result = pthread_setname_np(thread->id, "%s", (void *)name); +#else /* not BSD */ + /* + * On OSX, pthread_setname_np is only available in 10.6 or later, so test + * for it at runtime. It also may not be available on all linux distros. + */ +#if defined(DARWIN) + int (*dynamic_pthread_setname_np)(const char*); +#else + int (*dynamic_pthread_setname_np)(pthread_t, const char*); +#endif + + *(void**)(&dynamic_pthread_setname_np) = + dlsym(RTLD_DEFAULT, "pthread_setname_np"); + if (!dynamic_pthread_setname_np) { + return PR_SUCCESS; + } + +#if defined(DARWIN) + /* Mac OS X has a length limit of 63 characters, but there is no API + * exposing it. + */ +#define SETNAME_LENGTH_CONSTRAINT 63 +#else + /* + * The 15-character name length limit is an experimentally determined + * length of a null-terminated string that most linux distros accept + * as an argument to pthread_setname_np. Otherwise the E2BIG + * error is returned by the function. + */ +#define SETNAME_LENGTH_CONSTRAINT 15 +#endif +#define SETNAME_FRAGMENT1_LENGTH (SETNAME_LENGTH_CONSTRAINT >> 1) +#define SETNAME_FRAGMENT2_LENGTH \ + (SETNAME_LENGTH_CONSTRAINT - SETNAME_FRAGMENT1_LENGTH - 1) + char name_dup[SETNAME_LENGTH_CONSTRAINT + 1]; + if (nameLen > SETNAME_LENGTH_CONSTRAINT) { + memcpy(name_dup, name, SETNAME_FRAGMENT1_LENGTH); + name_dup[SETNAME_FRAGMENT1_LENGTH] = '~'; + /* Note that this also copies the null terminator. */ + memcpy(name_dup + SETNAME_FRAGMENT1_LENGTH + 1, + name + nameLen - SETNAME_FRAGMENT2_LENGTH, + SETNAME_FRAGMENT2_LENGTH + 1); + name = name_dup; + } + +#if defined(DARWIN) + result = dynamic_pthread_setname_np(name); +#else + result = dynamic_pthread_setname_np(thread->id, name); +#endif +#endif /* not BSD */ + + if (result) { + PR_SetError(PR_UNKNOWN_ERROR, result); + return PR_FAILURE; + } + return PR_SUCCESS; +} + +PR_IMPLEMENT(const char *) PR_GetThreadName(const PRThread *thread) +{ + if (!thread) { + return NULL; + } + return thread->name; +} + +#endif /* defined(_PR_PTHREADS) */ + +/* ptthread.c */ |