/* -*- 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 #include #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<= 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; 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<= 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 Solaris, and Windows, foreign threads are detached when * they terminate. */ #if !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 }