pr/src/threads/combined/pruthr.c
author wtc
Tue, 28 Apr 1998 00:25:01 +0000
branchNSPRPUB_19980421_BRANCH
changeset 67 b12f221b00a521e716e3795cd8e6952971daf357
parent 55 96763b5d836a4ec73a6081b2c300e2ed192ff8f5
permissions -rw-r--r--
Backed out a change that broke Win16 and Mac.

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include "primpl.h"
#include <signal.h>
#include <string.h>

/* _pr_activeLock protects the following global variables */
PRLock *_pr_activeLock;
PRUintn _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;
extern _PRCPU  *_pr_primordialCPU;

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)
{
#if defined(XP_MAC)
#pragma unused (maxPTDs)
#endif

    PRThread *thread;
    PRThreadStack *stack;

    _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*) ((((long)&type) >> _pr_pageShift)
				  << _pr_pageShift);
#else
#if defined(SOLARIS) || defined (UNIXWARE) && defined (USR_SVR4_THREADS)
    stack->stackTop = (char*) &thread;
#else
    stack->stackTop = (char*) ((((long)&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_GLOBAL_THREADS_ONLY
    thread->flags |= _PR_PRIMORDIAL | _PR_GLOBAL_SCOPE;
#else
    thread->flags |= _PR_PRIMORDIAL;
#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);
}

/*
** 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*) ((((long)&ts) >> _pr_pageShift)
				  << _pr_pageShift);
        ts->stackBottom = ts->allocBase + ts->stackSize;
        ts->stackTop = ts->allocBase;
#else
    	ts->allocBase = (char*) ((((long)&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->numExits == 0 && thread->ptes == 0);
    PR_ASSERT(thread->errorString == 0 && thread->errorStringSize == 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_DetachThread(PRThread *thread)
{
    _MD_FREE_LOCK(&thread->threadLock);
    PR_DELETE(thread);
}

void
_PR_NativeDestroyThread(PRThread *thread)
{
    if(thread->term) {
    	PR_DestroyCondVar(thread->term);
        thread->term = 0;
    }

    PR_DELETE(thread->stack);
    _PR_DetachThread(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->privateData = NULL;
		thread->tpdLength = 0;
	}
    _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,
         * including the primordial thread, 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;

        if ( !_PR_IS_NATIVE_THREAD(thread)) _PR_SET_INTSOFF(0);

		/*
		 * 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_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) {
	PRUintn oldPri;
        _PRCPU *cpu = thread->cpu;

	oldPri = thread->priority;
	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_AssignLock(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->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 = me->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
                */
#if !defined(XP_MAC)
                PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
#endif
                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 
     */
    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)
{
#if defined(XP_MAC)
#pragma unused (type)
#endif

    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 (_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)
{
#if defined(XP_MAC)
#pragma unused (scope)
#endif

    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_DetachThread(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)
    scope = PR_GLOBAL_THREAD;
#endif

    native = ((scope == PR_GLOBAL_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;
        	PR_Lock(_pr_activeLock);
		    thread->flags = (flags | _PR_GLOBAL_SCOPE);
        	if (type == PR_SYSTEM_THREAD) {
            		thread->flags |= _PR_SYSTEM;
            		_pr_systemActive++;
        	} else {
            		_pr_userActive++;
        	}
        	PR_Unlock(_pr_activeLock);

        	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 0;
            }
            stack->thr = thread;

            /* 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
            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;
#endif

            /* Initialize thread */
            if (stack)
                stack->thr = 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 (_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) {
		_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) {
			_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();

        if ((! (thread->flags & _PR_IDLE_THREAD)) && !_PR_IS_NATIVE_THREAD(me))
        	_PR_INTSOFF(is);
        if ((! (thread->flags & _PR_IDLE_THREAD)) && !_PR_IS_NATIVE_THREAD(thread)) {
            _PR_RUNQ_LOCK(thread->cpu);
            _PR_ADD_RUNQ(thread, thread->cpu, priority);
            _PR_RUNQ_UNLOCK(thread->cpu);
        }

        if ((thread->flags & _PR_IDLE_THREAD) || _PR_IS_NATIVE_THREAD(me)) {
            /*
            ** 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);
        }
        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.
*/
PR_IMPLEMENT(PRThread*) _PRI_AttachThread(PRThreadType type,
    PRThreadPriority priority, PRThreadStack *stack, PRUint32 flags)
{
    PRThread *thread;

    if ((thread = _PR_MD_GET_ATTACHED_THREAD()) != NULL) {
        return thread;
    }

    /* 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_DetachThread(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_DetachThread(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 _PRI_AttachThread(type, priority, stack, 0);
}

/*
** Detach the nspr thread from the currently executing native thread.
** The thread object will be destroyed and all related data attached
** to it. The exit procs will be invoked.
**
** This call is not normally needed unless you create your own native
** thread. PR_Exit will automatially detach the nspr thread object
** created by PR_Init for the primordial thread.
**
** This call returns after the nspr thread object is destroyed.
*/
PR_IMPLEMENT(void) PR_DetachThread(void)
{
    PRIntn is;
    PRThread *me = _PR_MD_CURRENT_THREAD();

    PR_ASSERT(me->flags & _PR_ATTACHED);
    _PR_CleanupThread(me);
	PR_DELETE(me->privateData);
    _PR_MD_CLEAN_THREAD(me);

    /*XXX we need DetachCPU */
     if ( !_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
    _PR_DecrActiveThreadCount(me);
    if ( !_PR_IS_NATIVE_THREAD(me)) _PR_FAST_INTSON(is);
    _PR_MD_SET_CURRENT_THREAD(NULL);
    if (!me->threadAllocatedOnStack) 
        PR_DELETE(me->stack);
    _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) {
        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 (newPri > PR_PRIORITY_LAST) {
        newPri = PR_PRIORITY_LAST;
    } else if (newPri < 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);
}


/*
** 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_ACTIVE_THREAD_PTR(qp)->flags & _PR_GCABLE_THREAD)) {
	    	_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_ACTIVE_THREAD_PTR(qp)->flags & _PR_GCABLE_THREAD))
	    	/* 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_ACTIVE_THREAD_PTR(qp)->flags & _PR_GCABLE_THREAD))
	    	_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_ACTIVE_THREAD_PTR(qp)->flags & _PR_GCABLE_THREAD))
                _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 (t->flags & _PR_GCABLE_THREAD)
        {
            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 (t->flags & _PR_GCABLE_THREAD)
        {
            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).
     */
    if (!_PR_IS_NATIVE_THREAD(me) && (cpu == me->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
}