summaryrefslogtreecommitdiffstats
path: root/nsprpub/pr/src/threads/combined/pruthr.c
diff options
context:
space:
mode:
Diffstat (limited to 'nsprpub/pr/src/threads/combined/pruthr.c')
-rw-r--r--nsprpub/pr/src/threads/combined/pruthr.c1889
1 files changed, 1889 insertions, 0 deletions
diff --git a/nsprpub/pr/src/threads/combined/pruthr.c b/nsprpub/pr/src/threads/combined/pruthr.c
new file mode 100644
index 000000000..4625ab246
--- /dev/null
+++ b/nsprpub/pr/src/threads/combined/pruthr.c
@@ -0,0 +1,1889 @@
+/* -*- 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/. */
+
+#include "primpl.h"
+#include <signal.h>
+#include <string.h>
+
+#if defined(WIN95)
+/*
+** Some local variables report warnings on Win95 because the code paths
+** using them are conditioned on HAVE_CUSTOME_USER_THREADS.
+** The pragma suppresses the warning.
+**
+*/
+#pragma warning(disable : 4101)
+#endif
+
+/* _pr_activeLock protects the following global variables */
+PRLock *_pr_activeLock;
+PRInt32 _pr_primordialExitCount; /* In PR_Cleanup(), the primordial thread
+ * waits until all other user (non-system)
+ * threads have terminated before it exits.
+ * So whenever we decrement _pr_userActive,
+ * it is compared with
+ * _pr_primordialExitCount.
+ * If the primordial thread is a system
+ * thread, then _pr_primordialExitCount
+ * is 0. If the primordial thread is
+ * itself a user thread, then
+ * _pr_primordialThread is 1.
+ */
+PRCondVar *_pr_primordialExitCVar; /* When _pr_userActive is decremented to
+ * _pr_primordialExitCount, this condition
+ * variable is notified.
+ */
+
+PRLock *_pr_deadQLock;
+PRUint32 _pr_numNativeDead;
+PRUint32 _pr_numUserDead;
+PRCList _pr_deadNativeQ;
+PRCList _pr_deadUserQ;
+
+PRUint32 _pr_join_counter;
+
+PRUint32 _pr_local_threads;
+PRUint32 _pr_global_threads;
+
+PRBool suspendAllOn = PR_FALSE;
+PRThread *suspendAllThread = NULL;
+
+extern PRCList _pr_active_global_threadQ;
+extern PRCList _pr_active_local_threadQ;
+
+static void _PR_DecrActiveThreadCount(PRThread *thread);
+static PRThread *_PR_AttachThread(PRThreadType, PRThreadPriority, PRThreadStack *);
+static void _PR_InitializeNativeStack(PRThreadStack *ts);
+static void _PR_InitializeRecycledThread(PRThread *thread);
+static void _PR_UserRunThread(void);
+
+void _PR_InitThreads(PRThreadType type, PRThreadPriority priority,
+ PRUintn maxPTDs)
+{
+ PRThread *thread;
+ PRThreadStack *stack;
+
+ PR_ASSERT(priority == PR_PRIORITY_NORMAL);
+
+ _pr_terminationCVLock = PR_NewLock();
+ _pr_activeLock = PR_NewLock();
+
+#ifndef HAVE_CUSTOM_USER_THREADS
+ stack = PR_NEWZAP(PRThreadStack);
+#ifdef HAVE_STACK_GROWING_UP
+ stack->stackTop = (char*) ((((PRWord)&type) >> _pr_pageShift)
+ << _pr_pageShift);
+#else
+#if defined(SOLARIS) || defined (UNIXWARE) && defined (USR_SVR4_THREADS)
+ stack->stackTop = (char*) &thread;
+#else
+ stack->stackTop = (char*) ((((PRWord)&type + _pr_pageSize - 1)
+ >> _pr_pageShift) << _pr_pageShift);
+#endif
+#endif
+#else
+ /* If stack is NULL, we're using custom user threads like NT fibers. */
+ stack = PR_NEWZAP(PRThreadStack);
+ if (stack) {
+ stack->stackSize = 0;
+ _PR_InitializeNativeStack(stack);
+ }
+#endif /* HAVE_CUSTOM_USER_THREADS */
+
+ thread = _PR_AttachThread(type, priority, stack);
+ if (thread) {
+ _PR_MD_SET_CURRENT_THREAD(thread);
+
+ if (type == PR_SYSTEM_THREAD) {
+ thread->flags = _PR_SYSTEM;
+ _pr_systemActive++;
+ _pr_primordialExitCount = 0;
+ } else {
+ _pr_userActive++;
+ _pr_primordialExitCount = 1;
+ }
+ thread->no_sched = 1;
+ _pr_primordialExitCVar = PR_NewCondVar(_pr_activeLock);
+ }
+
+ if (!thread) PR_Abort();
+#ifdef _PR_LOCAL_THREADS_ONLY
+ thread->flags |= _PR_PRIMORDIAL;
+#else
+ thread->flags |= _PR_PRIMORDIAL | _PR_GLOBAL_SCOPE;
+#endif
+
+ /*
+ * Needs _PR_PRIMORDIAL flag set before calling
+ * _PR_MD_INIT_THREAD()
+ */
+ if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
+ /*
+ * XXX do what?
+ */
+ }
+
+ if (_PR_IS_NATIVE_THREAD(thread)) {
+ PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_GLOBAL_THREADQ());
+ _pr_global_threads++;
+ } else {
+ PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_LOCAL_THREADQ());
+ _pr_local_threads++;
+ }
+
+ _pr_recycleThreads = 0;
+ _pr_deadQLock = PR_NewLock();
+ _pr_numNativeDead = 0;
+ _pr_numUserDead = 0;
+ PR_INIT_CLIST(&_pr_deadNativeQ);
+ PR_INIT_CLIST(&_pr_deadUserQ);
+}
+
+void _PR_CleanupThreads(void)
+{
+ if (_pr_terminationCVLock) {
+ PR_DestroyLock(_pr_terminationCVLock);
+ _pr_terminationCVLock = NULL;
+ }
+ if (_pr_activeLock) {
+ PR_DestroyLock(_pr_activeLock);
+ _pr_activeLock = NULL;
+ }
+ if (_pr_primordialExitCVar) {
+ PR_DestroyCondVar(_pr_primordialExitCVar);
+ _pr_primordialExitCVar = NULL;
+ }
+ /* TODO _pr_dead{Native,User}Q need to be deleted */
+ if (_pr_deadQLock) {
+ PR_DestroyLock(_pr_deadQLock);
+ _pr_deadQLock = NULL;
+ }
+}
+
+/*
+** Initialize a stack for a native thread
+*/
+static void _PR_InitializeNativeStack(PRThreadStack *ts)
+{
+ if( ts && (ts->stackTop == 0) ) {
+ ts->allocSize = ts->stackSize;
+
+ /*
+ ** Setup stackTop and stackBottom values.
+ */
+#ifdef HAVE_STACK_GROWING_UP
+ ts->allocBase = (char*) ((((PRWord)&ts) >> _pr_pageShift)
+ << _pr_pageShift);
+ ts->stackBottom = ts->allocBase + ts->stackSize;
+ ts->stackTop = ts->allocBase;
+#else
+ ts->allocBase = (char*) ((((PRWord)&ts + _pr_pageSize - 1)
+ >> _pr_pageShift) << _pr_pageShift);
+ ts->stackTop = ts->allocBase;
+ ts->stackBottom = ts->allocBase - ts->stackSize;
+#endif
+ }
+}
+
+void _PR_NotifyJoinWaiters(PRThread *thread)
+{
+ /*
+ ** Handle joinable threads. Change the state to waiting for join.
+ ** Remove from our run Q and put it on global waiting to join Q.
+ ** Notify on our "termination" condition variable so that joining
+ ** thread will know about our termination. Switch our context and
+ ** come back later on to continue the cleanup.
+ */
+ PR_ASSERT(thread == _PR_MD_CURRENT_THREAD());
+ if (thread->term != NULL) {
+ PR_Lock(_pr_terminationCVLock);
+ _PR_THREAD_LOCK(thread);
+ thread->state = _PR_JOIN_WAIT;
+ if ( !_PR_IS_NATIVE_THREAD(thread) ) {
+ _PR_MISCQ_LOCK(thread->cpu);
+ _PR_ADD_JOINQ(thread, thread->cpu);
+ _PR_MISCQ_UNLOCK(thread->cpu);
+ }
+ _PR_THREAD_UNLOCK(thread);
+ PR_NotifyCondVar(thread->term);
+ PR_Unlock(_pr_terminationCVLock);
+ _PR_MD_WAIT(thread, PR_INTERVAL_NO_TIMEOUT);
+ PR_ASSERT(thread->state != _PR_JOIN_WAIT);
+ }
+
+}
+
+/*
+ * Zero some of the data members of a recycled thread.
+ *
+ * Note that we can do this either when a dead thread is added to
+ * the dead thread queue or when it is reused. Here, we are doing
+ * this lazily, when the thread is reused in _PR_CreateThread().
+ */
+static void _PR_InitializeRecycledThread(PRThread *thread)
+{
+ /*
+ * Assert that the following data members are already zeroed
+ * by _PR_CleanupThread().
+ */
+#ifdef DEBUG
+ if (thread->privateData) {
+ unsigned int i;
+ for (i = 0; i < thread->tpdLength; i++) {
+ PR_ASSERT(thread->privateData[i] == NULL);
+ }
+ }
+#endif
+ PR_ASSERT(thread->dumpArg == 0 && thread->dump == 0);
+ PR_ASSERT(thread->errorString == 0 && thread->errorStringSize == 0);
+ PR_ASSERT(thread->errorStringLength == 0);
+ PR_ASSERT(thread->name == 0);
+
+ /* Reset data members in thread structure */
+ thread->errorCode = thread->osErrorCode = 0;
+ thread->io_pending = thread->io_suspended = PR_FALSE;
+ thread->environment = 0;
+ PR_INIT_CLIST(&thread->lockList);
+}
+
+PRStatus _PR_RecycleThread(PRThread *thread)
+{
+ if ( _PR_IS_NATIVE_THREAD(thread) &&
+ _PR_NUM_DEADNATIVE < _pr_recycleThreads) {
+ _PR_DEADQ_LOCK;
+ PR_APPEND_LINK(&thread->links, &_PR_DEADNATIVEQ);
+ _PR_INC_DEADNATIVE;
+ _PR_DEADQ_UNLOCK;
+ return (PR_SUCCESS);
+ } else if ( !_PR_IS_NATIVE_THREAD(thread) &&
+ _PR_NUM_DEADUSER < _pr_recycleThreads) {
+ _PR_DEADQ_LOCK;
+ PR_APPEND_LINK(&thread->links, &_PR_DEADUSERQ);
+ _PR_INC_DEADUSER;
+ _PR_DEADQ_UNLOCK;
+ return (PR_SUCCESS);
+ }
+ return (PR_FAILURE);
+}
+
+/*
+ * Decrement the active thread count, either _pr_systemActive or
+ * _pr_userActive, depending on whether the thread is a system thread
+ * or a user thread. If all the user threads, except possibly
+ * the primordial thread, have terminated, we notify the primordial
+ * thread of this condition.
+ *
+ * Since this function will lock _pr_activeLock, do not call this
+ * function while holding the _pr_activeLock lock, as this will result
+ * in a deadlock.
+ */
+
+static void
+_PR_DecrActiveThreadCount(PRThread *thread)
+{
+ PR_Lock(_pr_activeLock);
+ if (thread->flags & _PR_SYSTEM) {
+ _pr_systemActive--;
+ } else {
+ _pr_userActive--;
+ if (_pr_userActive == _pr_primordialExitCount) {
+ PR_NotifyCondVar(_pr_primordialExitCVar);
+ }
+ }
+ PR_Unlock(_pr_activeLock);
+}
+
+/*
+** Detach thread structure
+*/
+static void
+_PR_DestroyThread(PRThread *thread)
+{
+ _PR_MD_FREE_LOCK(&thread->threadLock);
+ PR_DELETE(thread);
+}
+
+void
+_PR_NativeDestroyThread(PRThread *thread)
+{
+ if(thread->term) {
+ PR_DestroyCondVar(thread->term);
+ thread->term = 0;
+ }
+ if (NULL != thread->privateData) {
+ PR_ASSERT(0 != thread->tpdLength);
+ PR_DELETE(thread->privateData);
+ thread->tpdLength = 0;
+ }
+ PR_DELETE(thread->stack);
+ _PR_DestroyThread(thread);
+}
+
+void
+_PR_UserDestroyThread(PRThread *thread)
+{
+ if(thread->term) {
+ PR_DestroyCondVar(thread->term);
+ thread->term = 0;
+ }
+ if (NULL != thread->privateData) {
+ PR_ASSERT(0 != thread->tpdLength);
+ PR_DELETE(thread->privateData);
+ thread->tpdLength = 0;
+ }
+ _PR_MD_FREE_LOCK(&thread->threadLock);
+ if (thread->threadAllocatedOnStack == 1) {
+ _PR_MD_CLEAN_THREAD(thread);
+ /*
+ * Because the no_sched field is set, this thread/stack will
+ * will not be re-used until the flag is cleared by the thread
+ * we will context switch to.
+ */
+ _PR_FreeStack(thread->stack);
+ } else {
+#ifdef WINNT
+ _PR_MD_CLEAN_THREAD(thread);
+#else
+ /*
+ * This assertion does not apply to NT. On NT, every fiber
+ * has its threadAllocatedOnStack equal to 0. Elsewhere,
+ * only the primordial thread has its threadAllocatedOnStack
+ * equal to 0.
+ */
+ PR_ASSERT(thread->flags & _PR_PRIMORDIAL);
+#endif
+ }
+}
+
+
+/*
+** Run a thread's start function. When the start function returns the
+** thread is done executing and no longer needs the CPU. If there are no
+** more user threads running then we can exit the program.
+*/
+void _PR_NativeRunThread(void *arg)
+{
+ PRThread *thread = (PRThread *)arg;
+
+ _PR_MD_SET_CURRENT_THREAD(thread);
+
+ _PR_MD_SET_CURRENT_CPU(NULL);
+
+ /* Set up the thread stack information */
+ _PR_InitializeNativeStack(thread->stack);
+
+ /* Set up the thread md information */
+ if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
+ /*
+ * thread failed to initialize itself, possibly due to
+ * failure to allocate per-thread resources
+ */
+ return;
+ }
+
+ while(1) {
+ thread->state = _PR_RUNNING;
+
+ /*
+ * Add to list of active threads
+ */
+ PR_Lock(_pr_activeLock);
+ PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_GLOBAL_THREADQ());
+ _pr_global_threads++;
+ PR_Unlock(_pr_activeLock);
+
+ (*thread->startFunc)(thread->arg);
+
+ /*
+ * The following two assertions are meant for NT asynch io.
+ *
+ * The thread should have no asynch io in progress when it
+ * exits, otherwise the overlapped buffer, which is part of
+ * the thread structure, would become invalid.
+ */
+ PR_ASSERT(thread->io_pending == PR_FALSE);
+ /*
+ * This assertion enforces the programming guideline that
+ * if an io function times out or is interrupted, the thread
+ * should close the fd to force the asynch io to abort
+ * before it exits. Right now, closing the fd is the only
+ * way to clear the io_suspended flag.
+ */
+ PR_ASSERT(thread->io_suspended == PR_FALSE);
+
+ /*
+ * remove thread from list of active threads
+ */
+ PR_Lock(_pr_activeLock);
+ PR_REMOVE_LINK(&thread->active);
+ _pr_global_threads--;
+ PR_Unlock(_pr_activeLock);
+
+ PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("thread exiting"));
+
+ /* All done, time to go away */
+ _PR_CleanupThread(thread);
+
+ _PR_NotifyJoinWaiters(thread);
+
+ _PR_DecrActiveThreadCount(thread);
+
+ thread->state = _PR_DEAD_STATE;
+
+ if (!_pr_recycleThreads || (_PR_RecycleThread(thread) ==
+ PR_FAILURE)) {
+ /*
+ * thread not recycled
+ * platform-specific thread exit processing
+ * - for stuff like releasing native-thread resources, etc.
+ */
+ _PR_MD_EXIT_THREAD(thread);
+ /*
+ * Free memory allocated for the thread
+ */
+ _PR_NativeDestroyThread(thread);
+ /*
+ * thread gone, cannot de-reference thread now
+ */
+ return;
+ }
+
+ /* Now wait for someone to activate us again... */
+ _PR_MD_WAIT(thread, PR_INTERVAL_NO_TIMEOUT);
+ }
+}
+
+static void _PR_UserRunThread(void)
+{
+ PRThread *thread = _PR_MD_CURRENT_THREAD();
+ PRIntn is;
+
+ if (_MD_LAST_THREAD())
+ _MD_LAST_THREAD()->no_sched = 0;
+
+#ifdef HAVE_CUSTOM_USER_THREADS
+ if (thread->stack == NULL) {
+ thread->stack = PR_NEWZAP(PRThreadStack);
+ _PR_InitializeNativeStack(thread->stack);
+ }
+#endif /* HAVE_CUSTOM_USER_THREADS */
+
+ while(1) {
+ /* Run thread main */
+ if ( !_PR_IS_NATIVE_THREAD(thread)) _PR_MD_SET_INTSOFF(0);
+
+ /*
+ * Add to list of active threads
+ */
+ if (!(thread->flags & _PR_IDLE_THREAD)) {
+ PR_Lock(_pr_activeLock);
+ PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_LOCAL_THREADQ());
+ _pr_local_threads++;
+ PR_Unlock(_pr_activeLock);
+ }
+
+ (*thread->startFunc)(thread->arg);
+
+ /*
+ * The following two assertions are meant for NT asynch io.
+ *
+ * The thread should have no asynch io in progress when it
+ * exits, otherwise the overlapped buffer, which is part of
+ * the thread structure, would become invalid.
+ */
+ PR_ASSERT(thread->io_pending == PR_FALSE);
+ /*
+ * This assertion enforces the programming guideline that
+ * if an io function times out or is interrupted, the thread
+ * should close the fd to force the asynch io to abort
+ * before it exits. Right now, closing the fd is the only
+ * way to clear the io_suspended flag.
+ */
+ PR_ASSERT(thread->io_suspended == PR_FALSE);
+
+ PR_Lock(_pr_activeLock);
+ /*
+ * remove thread from list of active threads
+ */
+ if (!(thread->flags & _PR_IDLE_THREAD)) {
+ PR_REMOVE_LINK(&thread->active);
+ _pr_local_threads--;
+ }
+ PR_Unlock(_pr_activeLock);
+ PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("thread exiting"));
+
+ /* All done, time to go away */
+ _PR_CleanupThread(thread);
+
+ _PR_INTSOFF(is);
+
+ _PR_NotifyJoinWaiters(thread);
+
+ _PR_DecrActiveThreadCount(thread);
+
+ thread->state = _PR_DEAD_STATE;
+
+ if (!_pr_recycleThreads || (_PR_RecycleThread(thread) ==
+ PR_FAILURE)) {
+ /*
+ ** Destroy the thread resources
+ */
+ _PR_UserDestroyThread(thread);
+ }
+
+ /*
+ ** Find another user thread to run. This cpu has finished the
+ ** previous threads main and is now ready to run another thread.
+ */
+ {
+ PRInt32 is;
+ _PR_INTSOFF(is);
+ _PR_MD_SWITCH_CONTEXT(thread);
+ }
+
+ /* Will land here when we get scheduled again if we are recycling... */
+ }
+}
+
+void _PR_SetThreadPriority(PRThread *thread, PRThreadPriority newPri)
+{
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+ PRIntn is;
+
+ if ( _PR_IS_NATIVE_THREAD(thread) ) {
+ _PR_MD_SET_PRIORITY(&(thread->md), newPri);
+ return;
+ }
+
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSOFF(is);
+ _PR_THREAD_LOCK(thread);
+ if (newPri != thread->priority) {
+ _PRCPU *cpu = thread->cpu;
+
+ switch (thread->state) {
+ case _PR_RUNNING:
+ /* Change my priority */
+
+ _PR_RUNQ_LOCK(cpu);
+ thread->priority = newPri;
+ if (_PR_RUNQREADYMASK(cpu) >> (newPri + 1)) {
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_SET_RESCHED_FLAG();
+ }
+ _PR_RUNQ_UNLOCK(cpu);
+ break;
+
+ case _PR_RUNNABLE:
+
+ _PR_RUNQ_LOCK(cpu);
+ /* Move to different runQ */
+ _PR_DEL_RUNQ(thread);
+ thread->priority = newPri;
+ PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
+ _PR_ADD_RUNQ(thread, cpu, newPri);
+ _PR_RUNQ_UNLOCK(cpu);
+
+ if (newPri > me->priority) {
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_SET_RESCHED_FLAG();
+ }
+
+ break;
+
+ case _PR_LOCK_WAIT:
+ case _PR_COND_WAIT:
+ case _PR_IO_WAIT:
+ case _PR_SUSPENDED:
+
+ thread->priority = newPri;
+ break;
+ }
+ }
+ _PR_THREAD_UNLOCK(thread);
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSON(is);
+}
+
+/*
+** Suspend the named thread and copy its gc registers into regBuf
+*/
+static void _PR_Suspend(PRThread *thread)
+{
+ PRIntn is;
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+
+ PR_ASSERT(thread != me);
+ PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread) || (!thread->cpu));
+
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSOFF(is);
+ _PR_THREAD_LOCK(thread);
+ switch (thread->state) {
+ case _PR_RUNNABLE:
+ if (!_PR_IS_NATIVE_THREAD(thread)) {
+ _PR_RUNQ_LOCK(thread->cpu);
+ _PR_DEL_RUNQ(thread);
+ _PR_RUNQ_UNLOCK(thread->cpu);
+
+ _PR_MISCQ_LOCK(thread->cpu);
+ _PR_ADD_SUSPENDQ(thread, thread->cpu);
+ _PR_MISCQ_UNLOCK(thread->cpu);
+ } else {
+ /*
+ * Only LOCAL threads are suspended by _PR_Suspend
+ */
+ PR_ASSERT(0);
+ }
+ thread->state = _PR_SUSPENDED;
+ break;
+
+ case _PR_RUNNING:
+ /*
+ * The thread being suspended should be a LOCAL thread with
+ * _pr_numCPUs == 1. Hence, the thread cannot be in RUNNING state
+ */
+ PR_ASSERT(0);
+ break;
+
+ case _PR_LOCK_WAIT:
+ case _PR_IO_WAIT:
+ case _PR_COND_WAIT:
+ if (_PR_IS_NATIVE_THREAD(thread)) {
+ _PR_MD_SUSPEND_THREAD(thread);
+ }
+ thread->flags |= _PR_SUSPENDING;
+ break;
+
+ default:
+ PR_Abort();
+ }
+ _PR_THREAD_UNLOCK(thread);
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSON(is);
+}
+
+static void _PR_Resume(PRThread *thread)
+{
+ PRThreadPriority pri;
+ PRIntn is;
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSOFF(is);
+ _PR_THREAD_LOCK(thread);
+ switch (thread->state) {
+ case _PR_SUSPENDED:
+ thread->state = _PR_RUNNABLE;
+ thread->flags &= ~_PR_SUSPENDING;
+ if (!_PR_IS_NATIVE_THREAD(thread)) {
+ _PR_MISCQ_LOCK(thread->cpu);
+ _PR_DEL_SUSPENDQ(thread);
+ _PR_MISCQ_UNLOCK(thread->cpu);
+
+ pri = thread->priority;
+
+ _PR_RUNQ_LOCK(thread->cpu);
+ _PR_ADD_RUNQ(thread, thread->cpu, pri);
+ _PR_RUNQ_UNLOCK(thread->cpu);
+
+ if (pri > _PR_MD_CURRENT_THREAD()->priority) {
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_SET_RESCHED_FLAG();
+ }
+ } else {
+ PR_ASSERT(0);
+ }
+ break;
+
+ case _PR_IO_WAIT:
+ case _PR_COND_WAIT:
+ thread->flags &= ~_PR_SUSPENDING;
+/* PR_ASSERT(thread->wait.monitor->stickyCount == 0); */
+ break;
+
+ case _PR_LOCK_WAIT:
+ {
+ PRLock *wLock = thread->wait.lock;
+
+ thread->flags &= ~_PR_SUSPENDING;
+
+ _PR_LOCK_LOCK(wLock);
+ if (thread->wait.lock->owner == 0) {
+ _PR_UnblockLockWaiter(thread->wait.lock);
+ }
+ _PR_LOCK_UNLOCK(wLock);
+ break;
+ }
+ case _PR_RUNNABLE:
+ break;
+ case _PR_RUNNING:
+ /*
+ * The thread being suspended should be a LOCAL thread with
+ * _pr_numCPUs == 1. Hence, the thread cannot be in RUNNING state
+ */
+ PR_ASSERT(0);
+ break;
+
+ default:
+ /*
+ * thread should have been in one of the above-listed blocked states
+ * (_PR_JOIN_WAIT, _PR_IO_WAIT, _PR_UNBORN, _PR_DEAD_STATE)
+ */
+ PR_Abort();
+ }
+ _PR_THREAD_UNLOCK(thread);
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSON(is);
+
+}
+
+#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)
+static PRThread *get_thread(_PRCPU *cpu, PRBool *wakeup_cpus)
+{
+ PRThread *thread;
+ PRIntn pri;
+ PRUint32 r;
+ PRCList *qp;
+ PRIntn priMin, priMax;
+
+ _PR_RUNQ_LOCK(cpu);
+ r = _PR_RUNQREADYMASK(cpu);
+ if (r==0) {
+ priMin = priMax = PR_PRIORITY_FIRST;
+ } else if (r == (1<<PR_PRIORITY_NORMAL) ) {
+ priMin = priMax = PR_PRIORITY_NORMAL;
+ } else {
+ priMin = PR_PRIORITY_FIRST;
+ priMax = PR_PRIORITY_LAST;
+ }
+ thread = NULL;
+ for (pri = priMax; pri >= priMin ; pri-- ) {
+ if (r & (1 << pri)) {
+ for (qp = _PR_RUNQ(cpu)[pri].next;
+ qp != &_PR_RUNQ(cpu)[pri];
+ qp = qp->next) {
+ thread = _PR_THREAD_PTR(qp);
+ /*
+ * skip non-schedulable threads
+ */
+ PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
+ if (thread->no_sched) {
+ thread = NULL;
+ /*
+ * Need to wakeup cpus to avoid missing a
+ * runnable thread
+ * Waking up all CPU's need happen only once.
+ */
+
+ *wakeup_cpus = PR_TRUE;
+ continue;
+ } else if (thread->flags & _PR_BOUND_THREAD) {
+ /*
+ * Thread bound to cpu 0
+ */
+
+ thread = NULL;
+#ifdef IRIX
+ _PR_MD_WAKEUP_PRIMORDIAL_CPU();
+#endif
+ continue;
+ } else if (thread->io_pending == PR_TRUE) {
+ /*
+ * A thread that is blocked for I/O needs to run
+ * on the same cpu on which it was blocked. This is because
+ * the cpu's ioq is accessed without lock protection and scheduling
+ * the thread on a different cpu would preclude this optimization.
+ */
+ thread = NULL;
+ continue;
+ } else {
+ /* Pull thread off of its run queue */
+ _PR_DEL_RUNQ(thread);
+ _PR_RUNQ_UNLOCK(cpu);
+ return(thread);
+ }
+ }
+ }
+ thread = NULL;
+ }
+ _PR_RUNQ_UNLOCK(cpu);
+ return(thread);
+}
+#endif /* !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX) */
+
+/*
+** Schedule this native thread by finding the highest priority nspr
+** thread that is ready to run.
+**
+** Note- everyone really needs to call _PR_MD_SWITCH_CONTEXT (which calls
+** PR_Schedule() rather than calling PR_Schedule. Otherwise if there
+** is initialization required for switching from SWITCH_CONTEXT,
+** it will not get done!
+*/
+void _PR_Schedule(void)
+{
+ PRThread *thread, *me = _PR_MD_CURRENT_THREAD();
+ _PRCPU *cpu = _PR_MD_CURRENT_CPU();
+ PRIntn pri;
+ PRUint32 r;
+ PRCList *qp;
+ PRIntn priMin, priMax;
+#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)
+ PRBool wakeup_cpus;
+#endif
+
+ /* Interrupts must be disabled */
+ PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);
+
+ /* Since we are rescheduling, we no longer want to */
+ _PR_CLEAR_RESCHED_FLAG();
+
+ /*
+ ** Find highest priority thread to run. Bigger priority numbers are
+ ** higher priority threads
+ */
+ _PR_RUNQ_LOCK(cpu);
+ /*
+ * if we are in SuspendAll mode, can schedule only the thread
+ * that called PR_SuspendAll
+ *
+ * The thread may be ready to run now, after completing an I/O
+ * operation, for example
+ */
+ if ((thread = suspendAllThread) != 0) {
+ if ((!(thread->no_sched)) && (thread->state == _PR_RUNNABLE)) {
+ /* Pull thread off of its run queue */
+ _PR_DEL_RUNQ(thread);
+ _PR_RUNQ_UNLOCK(cpu);
+ goto found_thread;
+ } else {
+ thread = NULL;
+ _PR_RUNQ_UNLOCK(cpu);
+ goto idle_thread;
+ }
+ }
+ r = _PR_RUNQREADYMASK(cpu);
+ if (r==0) {
+ priMin = priMax = PR_PRIORITY_FIRST;
+ } else if (r == (1<<PR_PRIORITY_NORMAL) ) {
+ priMin = priMax = PR_PRIORITY_NORMAL;
+ } else {
+ priMin = PR_PRIORITY_FIRST;
+ priMax = PR_PRIORITY_LAST;
+ }
+ thread = NULL;
+ for (pri = priMax; pri >= priMin ; pri-- ) {
+ if (r & (1 << pri)) {
+ for (qp = _PR_RUNQ(cpu)[pri].next;
+ qp != &_PR_RUNQ(cpu)[pri];
+ qp = qp->next) {
+ thread = _PR_THREAD_PTR(qp);
+ /*
+ * skip non-schedulable threads
+ */
+ PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
+ if ((thread->no_sched) && (me != thread)){
+ thread = NULL;
+ continue;
+ } else {
+ /* Pull thread off of its run queue */
+ _PR_DEL_RUNQ(thread);
+ _PR_RUNQ_UNLOCK(cpu);
+ goto found_thread;
+ }
+ }
+ }
+ thread = NULL;
+ }
+ _PR_RUNQ_UNLOCK(cpu);
+
+#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)
+
+ wakeup_cpus = PR_FALSE;
+ _PR_CPU_LIST_LOCK();
+ for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) {
+ if (cpu != _PR_CPU_PTR(qp)) {
+ if ((thread = get_thread(_PR_CPU_PTR(qp), &wakeup_cpus))
+ != NULL) {
+ thread->cpu = cpu;
+ _PR_CPU_LIST_UNLOCK();
+ if (wakeup_cpus == PR_TRUE)
+ _PR_MD_WAKEUP_CPUS();
+ goto found_thread;
+ }
+ }
+ }
+ _PR_CPU_LIST_UNLOCK();
+ if (wakeup_cpus == PR_TRUE)
+ _PR_MD_WAKEUP_CPUS();
+
+#endif /* _PR_LOCAL_THREADS_ONLY */
+
+idle_thread:
+ /*
+ ** There are no threads to run. Switch to the idle thread
+ */
+ PR_LOG(_pr_sched_lm, PR_LOG_MAX, ("pausing"));
+ thread = _PR_MD_CURRENT_CPU()->idle_thread;
+
+found_thread:
+ PR_ASSERT((me == thread) || ((thread->state == _PR_RUNNABLE) &&
+ (!(thread->no_sched))));
+
+ /* Resume the thread */
+ PR_LOG(_pr_sched_lm, PR_LOG_MAX,
+ ("switching to %d[%p]", thread->id, thread));
+ PR_ASSERT(thread->state != _PR_RUNNING);
+ thread->state = _PR_RUNNING;
+
+ /* If we are on the runq, it just means that we went to sleep on some
+ * resource, and by the time we got here another real native thread had
+ * already given us the resource and put us back on the runqueue
+ */
+ PR_ASSERT(thread->cpu == _PR_MD_CURRENT_CPU());
+ if (thread != me)
+ _PR_MD_RESTORE_CONTEXT(thread);
+#if 0
+ /* XXXMB; with setjmp/longjmp it is impossible to land here, but
+ * it is not with fibers... Is this a bad thing? I believe it is
+ * still safe.
+ */
+ PR_NOT_REACHED("impossible return from schedule");
+#endif
+}
+
+/*
+** Attaches a thread.
+** Does not set the _PR_MD_CURRENT_THREAD.
+** Does not specify the scope of the thread.
+*/
+static PRThread *
+_PR_AttachThread(PRThreadType type, PRThreadPriority priority,
+ PRThreadStack *stack)
+{
+ PRThread *thread;
+ char *mem;
+
+ if (priority > PR_PRIORITY_LAST) {
+ priority = PR_PRIORITY_LAST;
+ } else if (priority < PR_PRIORITY_FIRST) {
+ priority = PR_PRIORITY_FIRST;
+ }
+
+ mem = (char*) PR_CALLOC(sizeof(PRThread));
+ if (mem) {
+ thread = (PRThread*) mem;
+ thread->priority = priority;
+ thread->stack = stack;
+ thread->state = _PR_RUNNING;
+ PR_INIT_CLIST(&thread->lockList);
+ if (_PR_MD_NEW_LOCK(&thread->threadLock) == PR_FAILURE) {
+ PR_DELETE(thread);
+ return 0;
+ }
+
+ return thread;
+ }
+ return 0;
+}
+
+
+
+PR_IMPLEMENT(PRThread*)
+_PR_NativeCreateThread(PRThreadType type,
+ void (*start)(void *arg),
+ void *arg,
+ PRThreadPriority priority,
+ PRThreadScope scope,
+ PRThreadState state,
+ PRUint32 stackSize,
+ PRUint32 flags)
+{
+ PRThread *thread;
+
+ thread = _PR_AttachThread(type, priority, NULL);
+
+ if (thread) {
+ PR_Lock(_pr_activeLock);
+ thread->flags = (flags | _PR_GLOBAL_SCOPE);
+ thread->id = ++_pr_utid;
+ if (type == PR_SYSTEM_THREAD) {
+ thread->flags |= _PR_SYSTEM;
+ _pr_systemActive++;
+ } else {
+ _pr_userActive++;
+ }
+ PR_Unlock(_pr_activeLock);
+
+ thread->stack = PR_NEWZAP(PRThreadStack);
+ if (!thread->stack) {
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ goto done;
+ }
+ thread->stack->stackSize = stackSize?stackSize:_MD_DEFAULT_STACK_SIZE;
+ thread->stack->thr = thread;
+ thread->startFunc = start;
+ thread->arg = arg;
+
+ /*
+ Set thread flags related to scope and joinable state. If joinable
+ thread, allocate a "termination" conidition variable.
+ */
+ if (state == PR_JOINABLE_THREAD) {
+ thread->term = PR_NewCondVar(_pr_terminationCVLock);
+ if (thread->term == NULL) {
+ PR_DELETE(thread->stack);
+ goto done;
+ }
+ }
+
+ thread->state = _PR_RUNNING;
+ if (_PR_MD_CREATE_THREAD(thread, _PR_NativeRunThread, priority,
+ scope,state,stackSize) == PR_SUCCESS) {
+ return thread;
+ }
+ if (thread->term) {
+ PR_DestroyCondVar(thread->term);
+ thread->term = NULL;
+ }
+ PR_DELETE(thread->stack);
+ }
+
+done:
+ if (thread) {
+ _PR_DecrActiveThreadCount(thread);
+ _PR_DestroyThread(thread);
+ }
+ return NULL;
+}
+
+/************************************************************************/
+
+PR_IMPLEMENT(PRThread*) _PR_CreateThread(PRThreadType type,
+ void (*start)(void *arg),
+ void *arg,
+ PRThreadPriority priority,
+ PRThreadScope scope,
+ PRThreadState state,
+ PRUint32 stackSize,
+ PRUint32 flags)
+{
+ PRThread *me;
+ PRThread *thread = NULL;
+ PRThreadStack *stack;
+ char *top;
+ PRIntn is;
+ PRIntn native = 0;
+ PRIntn useRecycled = 0;
+ PRBool status;
+
+ /*
+ First, pin down the priority. Not all compilers catch passing out of
+ range enum here. If we let bad values thru, priority queues won't work.
+ */
+ if (priority > PR_PRIORITY_LAST) {
+ priority = PR_PRIORITY_LAST;
+ } else if (priority < PR_PRIORITY_FIRST) {
+ priority = PR_PRIORITY_FIRST;
+ }
+
+ if (!_pr_initialized) _PR_ImplicitInitialization();
+
+ if (! (flags & _PR_IDLE_THREAD))
+ me = _PR_MD_CURRENT_THREAD();
+
+#if defined(_PR_GLOBAL_THREADS_ONLY)
+ /*
+ * can create global threads only
+ */
+ if (scope == PR_LOCAL_THREAD)
+ scope = PR_GLOBAL_THREAD;
+#endif
+
+ if (_native_threads_only)
+ scope = PR_GLOBAL_THREAD;
+
+ native = (((scope == PR_GLOBAL_THREAD)|| (scope == PR_GLOBAL_BOUND_THREAD))
+ && _PR_IS_NATIVE_THREAD_SUPPORTED());
+
+ _PR_ADJUST_STACKSIZE(stackSize);
+
+ if (native) {
+ /*
+ * clear the IDLE_THREAD flag which applies to LOCAL
+ * threads only
+ */
+ flags &= ~_PR_IDLE_THREAD;
+ flags |= _PR_GLOBAL_SCOPE;
+ if (_PR_NUM_DEADNATIVE > 0) {
+ _PR_DEADQ_LOCK;
+
+ if (_PR_NUM_DEADNATIVE == 0) { /* Thread safe check */
+ _PR_DEADQ_UNLOCK;
+ } else {
+ thread = _PR_THREAD_PTR(_PR_DEADNATIVEQ.next);
+ PR_REMOVE_LINK(&thread->links);
+ _PR_DEC_DEADNATIVE;
+ _PR_DEADQ_UNLOCK;
+
+ _PR_InitializeRecycledThread(thread);
+ thread->startFunc = start;
+ thread->arg = arg;
+ thread->flags = (flags | _PR_GLOBAL_SCOPE);
+ if (type == PR_SYSTEM_THREAD)
+ {
+ thread->flags |= _PR_SYSTEM;
+ PR_ATOMIC_INCREMENT(&_pr_systemActive);
+ }
+ else PR_ATOMIC_INCREMENT(&_pr_userActive);
+
+ if (state == PR_JOINABLE_THREAD) {
+ if (!thread->term)
+ thread->term = PR_NewCondVar(_pr_terminationCVLock);
+ }
+ else {
+ if(thread->term) {
+ PR_DestroyCondVar(thread->term);
+ thread->term = 0;
+ }
+ }
+
+ thread->priority = priority;
+ _PR_MD_SET_PRIORITY(&(thread->md), priority);
+ /* XXX what about stackSize? */
+ thread->state = _PR_RUNNING;
+ _PR_MD_WAKEUP_WAITER(thread);
+ return thread;
+ }
+ }
+ thread = _PR_NativeCreateThread(type, start, arg, priority,
+ scope, state, stackSize, flags);
+ } else {
+ if (_PR_NUM_DEADUSER > 0) {
+ _PR_DEADQ_LOCK;
+
+ if (_PR_NUM_DEADUSER == 0) { /* thread safe check */
+ _PR_DEADQ_UNLOCK;
+ } else {
+ PRCList *ptr;
+
+ /* Go down list checking for a recycled thread with a
+ * large enough stack. XXXMB - this has a bad degenerate case.
+ */
+ ptr = _PR_DEADUSERQ.next;
+ while( ptr != &_PR_DEADUSERQ ) {
+ thread = _PR_THREAD_PTR(ptr);
+ if ((thread->stack->stackSize >= stackSize) &&
+ (!thread->no_sched)) {
+ PR_REMOVE_LINK(&thread->links);
+ _PR_DEC_DEADUSER;
+ break;
+ } else {
+ ptr = ptr->next;
+ thread = NULL;
+ }
+ }
+
+ _PR_DEADQ_UNLOCK;
+
+ if (thread) {
+ _PR_InitializeRecycledThread(thread);
+ thread->startFunc = start;
+ thread->arg = arg;
+ thread->priority = priority;
+ if (state == PR_JOINABLE_THREAD) {
+ if (!thread->term)
+ thread->term = PR_NewCondVar(_pr_terminationCVLock);
+ } else {
+ if(thread->term) {
+ PR_DestroyCondVar(thread->term);
+ thread->term = 0;
+ }
+ }
+ useRecycled++;
+ }
+ }
+ }
+ if (thread == NULL) {
+#ifndef HAVE_CUSTOM_USER_THREADS
+ stack = _PR_NewStack(stackSize);
+ if (!stack) {
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ return NULL;
+ }
+
+ /* Allocate thread object and per-thread data off the top of the stack*/
+ top = stack->stackTop;
+#ifdef HAVE_STACK_GROWING_UP
+ thread = (PRThread*) top;
+ top = top + sizeof(PRThread);
+ /*
+ * Make stack 64-byte aligned
+ */
+ if ((PRUptrdiff)top & 0x3f) {
+ top = (char*)(((PRUptrdiff)top + 0x40) & ~0x3f);
+ }
+#else
+ top = top - sizeof(PRThread);
+ thread = (PRThread*) top;
+ /*
+ * Make stack 64-byte aligned
+ */
+ if ((PRUptrdiff)top & 0x3f) {
+ top = (char*)((PRUptrdiff)top & ~0x3f);
+ }
+#endif
+ stack->thr = thread;
+ memset(thread, 0, sizeof(PRThread));
+ thread->threadAllocatedOnStack = 1;
+#else
+ thread = _PR_MD_CREATE_USER_THREAD(stackSize, start, arg);
+ if (!thread) {
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ return NULL;
+ }
+ thread->threadAllocatedOnStack = 0;
+ stack = NULL;
+ top = NULL;
+#endif
+
+ /* Initialize thread */
+ thread->tpdLength = 0;
+ thread->privateData = NULL;
+ thread->stack = stack;
+ thread->priority = priority;
+ thread->startFunc = start;
+ thread->arg = arg;
+ PR_INIT_CLIST(&thread->lockList);
+
+ if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
+ if (thread->threadAllocatedOnStack == 1)
+ _PR_FreeStack(thread->stack);
+ else {
+ PR_DELETE(thread);
+ }
+ PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
+ return NULL;
+ }
+
+ if (_PR_MD_NEW_LOCK(&thread->threadLock) == PR_FAILURE) {
+ if (thread->threadAllocatedOnStack == 1)
+ _PR_FreeStack(thread->stack);
+ else {
+ PR_DELETE(thread->privateData);
+ PR_DELETE(thread);
+ }
+ PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
+ return NULL;
+ }
+
+ _PR_MD_INIT_CONTEXT(thread, top, _PR_UserRunThread, &status);
+
+ if (status == PR_FALSE) {
+ _PR_MD_FREE_LOCK(&thread->threadLock);
+ if (thread->threadAllocatedOnStack == 1)
+ _PR_FreeStack(thread->stack);
+ else {
+ PR_DELETE(thread->privateData);
+ PR_DELETE(thread);
+ }
+ return NULL;
+ }
+
+ /*
+ Set thread flags related to scope and joinable state. If joinable
+ thread, allocate a "termination" condition variable.
+ */
+ if (state == PR_JOINABLE_THREAD) {
+ thread->term = PR_NewCondVar(_pr_terminationCVLock);
+ if (thread->term == NULL) {
+ _PR_MD_FREE_LOCK(&thread->threadLock);
+ if (thread->threadAllocatedOnStack == 1)
+ _PR_FreeStack(thread->stack);
+ else {
+ PR_DELETE(thread->privateData);
+ PR_DELETE(thread);
+ }
+ return NULL;
+ }
+ }
+
+ }
+
+ /* Update thread type counter */
+ PR_Lock(_pr_activeLock);
+ thread->flags = flags;
+ thread->id = ++_pr_utid;
+ if (type == PR_SYSTEM_THREAD) {
+ thread->flags |= _PR_SYSTEM;
+ _pr_systemActive++;
+ } else {
+ _pr_userActive++;
+ }
+
+ /* Make thread runnable */
+ thread->state = _PR_RUNNABLE;
+ /*
+ * Add to list of active threads
+ */
+ PR_Unlock(_pr_activeLock);
+
+ if ((! (thread->flags & _PR_IDLE_THREAD)) && _PR_IS_NATIVE_THREAD(me) )
+ thread->cpu = _PR_GetPrimordialCPU();
+ else
+ thread->cpu = _PR_MD_CURRENT_CPU();
+
+ PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));
+
+ if ((! (thread->flags & _PR_IDLE_THREAD)) && !_PR_IS_NATIVE_THREAD(me)) {
+ _PR_INTSOFF(is);
+ _PR_RUNQ_LOCK(thread->cpu);
+ _PR_ADD_RUNQ(thread, thread->cpu, priority);
+ _PR_RUNQ_UNLOCK(thread->cpu);
+ }
+
+ if (thread->flags & _PR_IDLE_THREAD) {
+ /*
+ ** If the creating thread is a kernel thread, we need to
+ ** awaken the user thread idle thread somehow; potentially
+ ** it could be sleeping in its idle loop, and we need to poke
+ ** it. To do so, wake the idle thread...
+ */
+ _PR_MD_WAKEUP_WAITER(NULL);
+ } else if (_PR_IS_NATIVE_THREAD(me)) {
+ _PR_MD_WAKEUP_WAITER(thread);
+ }
+ if ((! (thread->flags & _PR_IDLE_THREAD)) && !_PR_IS_NATIVE_THREAD(me) )
+ _PR_INTSON(is);
+ }
+
+ return thread;
+}
+
+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, 0);
+}
+
+/*
+** Associate a thread object with an existing native thread.
+** "type" is the type of thread object to attach
+** "priority" is the priority to assign to the thread
+** "stack" defines the shape of the threads stack
+**
+** This can return NULL if some kind of error occurs, or if memory is
+** tight.
+**
+** This call is not normally needed unless you create your own native
+** thread. PR_Init does this automatically for the primordial thread.
+*/
+PRThread* _PRI_AttachThread(PRThreadType type,
+ PRThreadPriority priority, PRThreadStack *stack, PRUint32 flags)
+{
+ PRThread *thread;
+
+ if ((thread = _PR_MD_GET_ATTACHED_THREAD()) != NULL) {
+ return thread;
+ }
+ _PR_MD_SET_CURRENT_THREAD(NULL);
+
+ /* Clear out any state if this thread was attached before */
+ _PR_MD_SET_CURRENT_CPU(NULL);
+
+ thread = _PR_AttachThread(type, priority, stack);
+ if (thread) {
+ PRIntn is;
+
+ _PR_MD_SET_CURRENT_THREAD(thread);
+
+ thread->flags = flags | _PR_GLOBAL_SCOPE | _PR_ATTACHED;
+
+ if (!stack) {
+ thread->stack = PR_NEWZAP(PRThreadStack);
+ if (!thread->stack) {
+ _PR_DestroyThread(thread);
+ return NULL;
+ }
+ thread->stack->stackSize = _MD_DEFAULT_STACK_SIZE;
+ }
+ PR_INIT_CLIST(&thread->links);
+
+ if (_PR_MD_INIT_ATTACHED_THREAD(thread) == PR_FAILURE) {
+ PR_DELETE(thread->stack);
+ _PR_DestroyThread(thread);
+ return NULL;
+ }
+
+ _PR_MD_SET_CURRENT_CPU(NULL);
+
+ if (_PR_MD_CURRENT_CPU()) {
+ _PR_INTSOFF(is);
+ PR_Lock(_pr_activeLock);
+ }
+ if (type == PR_SYSTEM_THREAD) {
+ thread->flags |= _PR_SYSTEM;
+ _pr_systemActive++;
+ } else {
+ _pr_userActive++;
+ }
+ if (_PR_MD_CURRENT_CPU()) {
+ PR_Unlock(_pr_activeLock);
+ _PR_INTSON(is);
+ }
+ }
+ return thread;
+}
+
+PR_IMPLEMENT(PRThread*) PR_AttachThread(PRThreadType type,
+ PRThreadPriority priority, PRThreadStack *stack)
+{
+ return PR_GetCurrentThread();
+}
+
+PR_IMPLEMENT(void) PR_DetachThread(void)
+{
+ /*
+ * On IRIX, Solaris, and Windows, foreign threads are detached when
+ * they terminate.
+ */
+#if !defined(IRIX) && !defined(WIN32) \
+ && !(defined(SOLARIS) && defined(_PR_GLOBAL_THREADS_ONLY))
+ PRThread *me;
+ if (_pr_initialized) {
+ me = _PR_MD_GET_ATTACHED_THREAD();
+ if ((me != NULL) && (me->flags & _PR_ATTACHED))
+ _PRI_DetachThread();
+ }
+#endif
+}
+
+void _PRI_DetachThread(void)
+{
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+
+ if (me->flags & _PR_PRIMORDIAL) {
+ /*
+ * ignore, if primordial thread
+ */
+ return;
+ }
+ PR_ASSERT(me->flags & _PR_ATTACHED);
+ PR_ASSERT(_PR_IS_NATIVE_THREAD(me));
+ _PR_CleanupThread(me);
+ PR_DELETE(me->privateData);
+
+ _PR_DecrActiveThreadCount(me);
+
+ _PR_MD_CLEAN_THREAD(me);
+ _PR_MD_SET_CURRENT_THREAD(NULL);
+ if (!me->threadAllocatedOnStack)
+ PR_DELETE(me->stack);
+ _PR_MD_FREE_LOCK(&me->threadLock);
+ PR_DELETE(me);
+}
+
+/*
+** Wait for thread termination:
+** "thread" is the target thread
+**
+** This can return PR_FAILURE if no joinable thread could be found
+** corresponding to the specified target thread.
+**
+** The calling thread is suspended until the target thread completes.
+** Several threads cannot wait for the same thread to complete; one thread
+** will complete successfully and others will terminate with an error PR_FAILURE.
+** The calling thread will not be blocked if the target thread has already
+** terminated.
+*/
+PR_IMPLEMENT(PRStatus) PR_JoinThread(PRThread *thread)
+{
+ PRIntn is;
+ PRCondVar *term;
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSOFF(is);
+ term = thread->term;
+ /* can't join a non-joinable thread */
+ if (term == NULL) {
+ PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+ goto ErrorExit;
+ }
+
+ /* multiple threads can't wait on the same joinable thread */
+ if (term->condQ.next != &term->condQ) {
+ goto ErrorExit;
+ }
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSON(is);
+
+ /* wait for the target thread's termination cv invariant */
+ PR_Lock (_pr_terminationCVLock);
+ while (thread->state != _PR_JOIN_WAIT) {
+ (void) PR_WaitCondVar(term, PR_INTERVAL_NO_TIMEOUT);
+ }
+ (void) PR_Unlock (_pr_terminationCVLock);
+
+ /*
+ Remove target thread from global waiting to join Q; make it runnable
+ again and put it back on its run Q. When it gets scheduled later in
+ _PR_RunThread code, it will clean up its stack.
+ */
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSOFF(is);
+ thread->state = _PR_RUNNABLE;
+ if ( !_PR_IS_NATIVE_THREAD(thread) ) {
+ _PR_THREAD_LOCK(thread);
+
+ _PR_MISCQ_LOCK(thread->cpu);
+ _PR_DEL_JOINQ(thread);
+ _PR_MISCQ_UNLOCK(thread->cpu);
+
+ _PR_AddThreadToRunQ(me, thread);
+ _PR_THREAD_UNLOCK(thread);
+ }
+ if (!_PR_IS_NATIVE_THREAD(me))
+ _PR_INTSON(is);
+
+ _PR_MD_WAKEUP_WAITER(thread);
+
+ return PR_SUCCESS;
+
+ErrorExit:
+ if ( !_PR_IS_NATIVE_THREAD(me)) _PR_INTSON(is);
+ return PR_FAILURE;
+}
+
+PR_IMPLEMENT(void) PR_SetThreadPriority(PRThread *thread,
+ PRThreadPriority newPri)
+{
+
+ /*
+ First, pin down the priority. Not all compilers catch passing out of
+ range enum here. If we let bad values thru, priority queues won't work.
+ */
+ if ((PRIntn)newPri > (PRIntn)PR_PRIORITY_LAST) {
+ newPri = PR_PRIORITY_LAST;
+ } else if ((PRIntn)newPri < (PRIntn)PR_PRIORITY_FIRST) {
+ newPri = PR_PRIORITY_FIRST;
+ }
+
+ if ( _PR_IS_NATIVE_THREAD(thread) ) {
+ thread->priority = newPri;
+ _PR_MD_SET_PRIORITY(&(thread->md), newPri);
+ } else _PR_SetThreadPriority(thread, newPri);
+}
+
+PR_IMPLEMENT(PRStatus) PR_SetCurrentThreadName(const char *name)
+{
+ PRThread *thread;
+ size_t nameLen;
+
+ 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);
+ _PR_MD_SET_CURRENT_THREAD_NAME(thread->name);
+ return PR_SUCCESS;
+}
+
+PR_IMPLEMENT(const char *) PR_GetThreadName(const PRThread *thread)
+{
+ if (!thread)
+ return NULL;
+ return thread->name;
+}
+
+
+/*
+** This routine prevents all other threads from running. This call is needed by
+** the garbage collector.
+*/
+PR_IMPLEMENT(void) PR_SuspendAll(void)
+{
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+ PRCList *qp;
+
+ /*
+ * Stop all user and native threads which are marked GC able.
+ */
+ PR_Lock(_pr_activeLock);
+ suspendAllOn = PR_TRUE;
+ suspendAllThread = _PR_MD_CURRENT_THREAD();
+ _PR_MD_BEGIN_SUSPEND_ALL();
+ for (qp = _PR_ACTIVE_LOCAL_THREADQ().next;
+ qp != &_PR_ACTIVE_LOCAL_THREADQ(); qp = qp->next) {
+ if ((me != _PR_ACTIVE_THREAD_PTR(qp)) &&
+ _PR_IS_GCABLE_THREAD(_PR_ACTIVE_THREAD_PTR(qp))) {
+ _PR_Suspend(_PR_ACTIVE_THREAD_PTR(qp));
+ PR_ASSERT((_PR_ACTIVE_THREAD_PTR(qp))->state != _PR_RUNNING);
+ }
+ }
+ for (qp = _PR_ACTIVE_GLOBAL_THREADQ().next;
+ qp != &_PR_ACTIVE_GLOBAL_THREADQ(); qp = qp->next) {
+ if ((me != _PR_ACTIVE_THREAD_PTR(qp)) &&
+ _PR_IS_GCABLE_THREAD(_PR_ACTIVE_THREAD_PTR(qp)))
+ /* PR_Suspend(_PR_ACTIVE_THREAD_PTR(qp)); */
+ _PR_MD_SUSPEND_THREAD(_PR_ACTIVE_THREAD_PTR(qp));
+ }
+ _PR_MD_END_SUSPEND_ALL();
+}
+
+/*
+** This routine unblocks all other threads that were suspended from running by
+** PR_SuspendAll(). This call is needed by the garbage collector.
+*/
+PR_IMPLEMENT(void) PR_ResumeAll(void)
+{
+ PRThread *me = _PR_MD_CURRENT_THREAD();
+ PRCList *qp;
+
+ /*
+ * Resume all user and native threads which are marked GC able.
+ */
+ _PR_MD_BEGIN_RESUME_ALL();
+ for (qp = _PR_ACTIVE_LOCAL_THREADQ().next;
+ qp != &_PR_ACTIVE_LOCAL_THREADQ(); qp = qp->next) {
+ if ((me != _PR_ACTIVE_THREAD_PTR(qp)) &&
+ _PR_IS_GCABLE_THREAD(_PR_ACTIVE_THREAD_PTR(qp)))
+ _PR_Resume(_PR_ACTIVE_THREAD_PTR(qp));
+ }
+ for (qp = _PR_ACTIVE_GLOBAL_THREADQ().next;
+ qp != &_PR_ACTIVE_GLOBAL_THREADQ(); qp = qp->next) {
+ if ((me != _PR_ACTIVE_THREAD_PTR(qp)) &&
+ _PR_IS_GCABLE_THREAD(_PR_ACTIVE_THREAD_PTR(qp)))
+ _PR_MD_RESUME_THREAD(_PR_ACTIVE_THREAD_PTR(qp));
+ }
+ _PR_MD_END_RESUME_ALL();
+ suspendAllThread = NULL;
+ suspendAllOn = PR_FALSE;
+ PR_Unlock(_pr_activeLock);
+}
+
+PR_IMPLEMENT(PRStatus) PR_EnumerateThreads(PREnumerator func, void *arg)
+{
+ PRCList *qp, *qp_next;
+ PRIntn i = 0;
+ PRStatus rv = PR_SUCCESS;
+ PRThread* t;
+
+ /*
+ ** Currently Enumerate threads happen only with suspension and
+ ** pr_activeLock held
+ */
+ PR_ASSERT(suspendAllOn);
+
+ /* 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".
+ */
+
+ /*
+ * Traverse the list of local and global threads
+ */
+ for (qp = _PR_ACTIVE_LOCAL_THREADQ().next;
+ qp != &_PR_ACTIVE_LOCAL_THREADQ(); qp = qp_next)
+ {
+ qp_next = qp->next;
+ t = _PR_ACTIVE_THREAD_PTR(qp);
+ if (_PR_IS_GCABLE_THREAD(t))
+ {
+ rv = (*func)(t, i, arg);
+ if (rv != PR_SUCCESS)
+ return rv;
+ i++;
+ }
+ }
+ for (qp = _PR_ACTIVE_GLOBAL_THREADQ().next;
+ qp != &_PR_ACTIVE_GLOBAL_THREADQ(); qp = qp_next)
+ {
+ qp_next = qp->next;
+ t = _PR_ACTIVE_THREAD_PTR(qp);
+ if (_PR_IS_GCABLE_THREAD(t))
+ {
+ rv = (*func)(t, i, arg);
+ if (rv != PR_SUCCESS)
+ return rv;
+ i++;
+ }
+ }
+ return rv;
+}
+
+/* FUNCTION: _PR_AddSleepQ
+** DESCRIPTION:
+** Adds a thread to the sleep/pauseQ.
+** RESTRICTIONS:
+** Caller must have the RUNQ lock.
+** Caller must be a user level thread
+*/
+PR_IMPLEMENT(void)
+_PR_AddSleepQ(PRThread *thread, PRIntervalTime timeout)
+{
+ _PRCPU *cpu = thread->cpu;
+
+ if (timeout == PR_INTERVAL_NO_TIMEOUT) {
+ /* append the thread to the global pause Q */
+ PR_APPEND_LINK(&thread->links, &_PR_PAUSEQ(thread->cpu));
+ thread->flags |= _PR_ON_PAUSEQ;
+ } else {
+ PRIntervalTime sleep;
+ PRCList *q;
+ PRThread *t;
+
+ /* sort onto global sleepQ */
+ sleep = timeout;
+
+ /* Check if we are longest timeout */
+ if (timeout >= _PR_SLEEPQMAX(cpu)) {
+ PR_INSERT_BEFORE(&thread->links, &_PR_SLEEPQ(cpu));
+ thread->sleep = timeout - _PR_SLEEPQMAX(cpu);
+ _PR_SLEEPQMAX(cpu) = timeout;
+ } else {
+ /* Sort thread into global sleepQ at appropriate point */
+ q = _PR_SLEEPQ(cpu).next;
+
+ /* Now scan the list for where to insert this entry */
+ while (q != &_PR_SLEEPQ(cpu)) {
+ t = _PR_THREAD_PTR(q);
+ if (sleep < t->sleep) {
+ /* Found sleeper to insert in front of */
+ break;
+ }
+ sleep -= t->sleep;
+ q = q->next;
+ }
+ thread->sleep = sleep;
+ PR_INSERT_BEFORE(&thread->links, q);
+
+ /*
+ ** Subtract our sleep time from the sleeper that follows us (there
+ ** must be one) so that they remain relative to us.
+ */
+ PR_ASSERT (thread->links.next != &_PR_SLEEPQ(cpu));
+
+ t = _PR_THREAD_PTR(thread->links.next);
+ PR_ASSERT(_PR_THREAD_PTR(t->links.prev) == thread);
+ t->sleep -= sleep;
+ }
+
+ thread->flags |= _PR_ON_SLEEPQ;
+ }
+}
+
+/* FUNCTION: _PR_DelSleepQ
+** DESCRIPTION:
+** Removes a thread from the sleep/pauseQ.
+** INPUTS:
+** If propogate_time is true, then the thread following the deleted
+** thread will be get the time from the deleted thread. This is used
+** when deleting a sleeper that has not timed out.
+** RESTRICTIONS:
+** Caller must have the RUNQ lock.
+** Caller must be a user level thread
+*/
+PR_IMPLEMENT(void)
+_PR_DelSleepQ(PRThread *thread, PRBool propogate_time)
+{
+ _PRCPU *cpu = thread->cpu;
+
+ /* Remove from pauseQ/sleepQ */
+ if (thread->flags & (_PR_ON_PAUSEQ|_PR_ON_SLEEPQ)) {
+ if (thread->flags & _PR_ON_SLEEPQ) {
+ PRCList *q = thread->links.next;
+ if (q != &_PR_SLEEPQ(cpu)) {
+ if (propogate_time == PR_TRUE) {
+ PRThread *after = _PR_THREAD_PTR(q);
+ after->sleep += thread->sleep;
+ } else
+ _PR_SLEEPQMAX(cpu) -= thread->sleep;
+ } else {
+ /* Check if prev is the beggining of the list; if so,
+ * we are the only element on the list.
+ */
+ if (thread->links.prev != &_PR_SLEEPQ(cpu))
+ _PR_SLEEPQMAX(cpu) -= thread->sleep;
+ else
+ _PR_SLEEPQMAX(cpu) = 0;
+ }
+ thread->flags &= ~_PR_ON_SLEEPQ;
+ } else {
+ thread->flags &= ~_PR_ON_PAUSEQ;
+ }
+ PR_REMOVE_LINK(&thread->links);
+ } else
+ PR_ASSERT(0);
+}
+
+void
+_PR_AddThreadToRunQ(
+ PRThread *me, /* the current thread */
+ PRThread *thread) /* the local thread to be added to a run queue */
+{
+ PRThreadPriority pri = thread->priority;
+ _PRCPU *cpu = thread->cpu;
+
+ PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));
+
+#if defined(WINNT)
+ /*
+ * On NT, we can only reliably know that the current CPU
+ * is not idle. We add the awakened thread to the run
+ * queue of its CPU if its CPU is the current CPU.
+ * For any other CPU, we don't really know whether it
+ * is busy or idle. So in all other cases, we just
+ * "post" the awakened thread to the IO completion port
+ * for the next idle CPU to execute (this is done in
+ * _PR_MD_WAKEUP_WAITER).
+ * Threads with a suspended I/O operation remain bound to
+ * the same cpu until I/O is cancelled
+ *
+ * NOTE: the boolean expression below must be the exact
+ * opposite of the corresponding boolean expression in
+ * _PR_MD_WAKEUP_WAITER.
+ */
+ if ((!_PR_IS_NATIVE_THREAD(me) && (cpu == me->cpu)) ||
+ (thread->md.thr_bound_cpu)) {
+ PR_ASSERT(!thread->md.thr_bound_cpu ||
+ (thread->md.thr_bound_cpu == cpu));
+ _PR_RUNQ_LOCK(cpu);
+ _PR_ADD_RUNQ(thread, cpu, pri);
+ _PR_RUNQ_UNLOCK(cpu);
+ }
+#else
+ _PR_RUNQ_LOCK(cpu);
+ _PR_ADD_RUNQ(thread, cpu, pri);
+ _PR_RUNQ_UNLOCK(cpu);
+ if (!_PR_IS_NATIVE_THREAD(me) && (cpu == me->cpu)) {
+ if (pri > me->priority) {
+ _PR_SET_RESCHED_FLAG();
+ }
+ }
+#endif
+}