/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

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

#if defined(WIN95)
/*
** Some local variables report warnings on Win95 because the code paths
** using them are conditioned on HAVE_CUSTOME_USER_THREADS.
** The pragma suppresses the warning.
**
*/
#  pragma warning(disable : 4101)
#endif

/* _pr_activeLock protects the following global variables */
PRLock* _pr_activeLock;
PRInt32 _pr_primordialExitCount;   /* In PR_Cleanup(), the primordial thread
                                    * waits until all other user (non-system)
                                    * threads have terminated before it exits.
                                    * So whenever we decrement _pr_userActive,
                                    * it is compared with
                                    * _pr_primordialExitCount.
                                    * If the primordial thread is a system
                                    * thread, then _pr_primordialExitCount
                                    * is 0.  If the primordial thread is
                                    * itself a user thread, then
                                    * _pr_primordialThread is 1.
                                    */
PRCondVar* _pr_primordialExitCVar; /* When _pr_userActive is decremented to
                                    * _pr_primordialExitCount, this condition
                                    * variable is notified.
                                    */

PRLock* _pr_deadQLock;
PRUint32 _pr_numNativeDead;
PRUint32 _pr_numUserDead;
PRCList _pr_deadNativeQ;
PRCList _pr_deadUserQ;

PRUint32 _pr_join_counter;

PRUint32 _pr_local_threads;
PRUint32 _pr_global_threads;

PRBool suspendAllOn = PR_FALSE;
PRThread* suspendAllThread = NULL;

extern PRCList _pr_active_global_threadQ;
extern PRCList _pr_active_local_threadQ;

static void _PR_DecrActiveThreadCount(PRThread* thread);
static PRThread* _PR_AttachThread(PRThreadType, PRThreadPriority,
                                  PRThreadStack*);
static void _PR_InitializeNativeStack(PRThreadStack* ts);
static void _PR_InitializeRecycledThread(PRThread* thread);
static void _PR_UserRunThread(void);

void _PR_InitThreads(PRThreadType type, PRThreadPriority priority,
                     PRUintn maxPTDs) {
  PRThread* thread;
  PRThreadStack* stack;

  PR_ASSERT(priority == PR_PRIORITY_NORMAL);

  _pr_terminationCVLock = PR_NewLock();
  _pr_activeLock = PR_NewLock();

#ifndef HAVE_CUSTOM_USER_THREADS
  stack = PR_NEWZAP(PRThreadStack);
#  ifdef HAVE_STACK_GROWING_UP
  stack->stackTop =
      (char*)((((PRWord)&type) >> _pr_pageShift) << _pr_pageShift);
#  else
#    if defined(SOLARIS)
  stack->stackTop = (char*)&thread;
#    else
  stack->stackTop =
      (char*)((((PRWord)&type + _pr_pageSize - 1) >> _pr_pageShift)
              << _pr_pageShift);
#    endif
#  endif
#else
  /* If stack is NULL, we're using custom user threads like NT fibers. */
  stack = PR_NEWZAP(PRThreadStack);
  if (stack) {
    stack->stackSize = 0;
    _PR_InitializeNativeStack(stack);
  }
#endif /* HAVE_CUSTOM_USER_THREADS */

  thread = _PR_AttachThread(type, priority, stack);
  if (thread) {
    _PR_MD_SET_CURRENT_THREAD(thread);

    if (type == PR_SYSTEM_THREAD) {
      thread->flags = _PR_SYSTEM;
      _pr_systemActive++;
      _pr_primordialExitCount = 0;
    } else {
      _pr_userActive++;
      _pr_primordialExitCount = 1;
    }
    thread->no_sched = 1;
    _pr_primordialExitCVar = PR_NewCondVar(_pr_activeLock);
  }

  if (!thread) {
    PR_Abort();
  }
#ifdef _PR_LOCAL_THREADS_ONLY
  thread->flags |= _PR_PRIMORDIAL;
#else
  thread->flags |= _PR_PRIMORDIAL | _PR_GLOBAL_SCOPE;
#endif

  /*
   * Needs _PR_PRIMORDIAL flag set before calling
   * _PR_MD_INIT_THREAD()
   */
  if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
    /*
     * XXX do what?
     */
  }

  if (_PR_IS_NATIVE_THREAD(thread)) {
    PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_GLOBAL_THREADQ());
    _pr_global_threads++;
  } else {
    PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_LOCAL_THREADQ());
    _pr_local_threads++;
  }

  _pr_recycleThreads = 0;
  _pr_deadQLock = PR_NewLock();
  _pr_numNativeDead = 0;
  _pr_numUserDead = 0;
  PR_INIT_CLIST(&_pr_deadNativeQ);
  PR_INIT_CLIST(&_pr_deadUserQ);
}

void _PR_CleanupThreads(void) {
  if (_pr_terminationCVLock) {
    PR_DestroyLock(_pr_terminationCVLock);
    _pr_terminationCVLock = NULL;
  }
  if (_pr_activeLock) {
    PR_DestroyLock(_pr_activeLock);
    _pr_activeLock = NULL;
  }
  if (_pr_primordialExitCVar) {
    PR_DestroyCondVar(_pr_primordialExitCVar);
    _pr_primordialExitCVar = NULL;
  }
  /* TODO _pr_dead{Native,User}Q need to be deleted */
  if (_pr_deadQLock) {
    PR_DestroyLock(_pr_deadQLock);
    _pr_deadQLock = NULL;
  }
}

/*
** Initialize a stack for a native thread
*/
static void _PR_InitializeNativeStack(PRThreadStack* ts) {
  if (ts && (ts->stackTop == 0)) {
    ts->allocSize = ts->stackSize;

    /*
    ** Setup stackTop and stackBottom values.
    */
#ifdef HAVE_STACK_GROWING_UP
    ts->allocBase = (char*)((((PRWord)&ts) >> _pr_pageShift) << _pr_pageShift);
    ts->stackBottom = ts->allocBase + ts->stackSize;
    ts->stackTop = ts->allocBase;
#else
    ts->allocBase = (char*)((((PRWord)&ts + _pr_pageSize - 1) >> _pr_pageShift)
                            << _pr_pageShift);
    ts->stackTop = ts->allocBase;
    ts->stackBottom = ts->allocBase - ts->stackSize;
#endif
  }
}

void _PR_NotifyJoinWaiters(PRThread* thread) {
  /*
  ** Handle joinable threads.  Change the state to waiting for join.
  ** Remove from our run Q and put it on global waiting to join Q.
  ** Notify on our "termination" condition variable so that joining
  ** thread will know about our termination.  Switch our context and
  ** come back later on to continue the cleanup.
  */
  PR_ASSERT(thread == _PR_MD_CURRENT_THREAD());
  if (thread->term != NULL) {
    PR_Lock(_pr_terminationCVLock);
    _PR_THREAD_LOCK(thread);
    thread->state = _PR_JOIN_WAIT;
    if (!_PR_IS_NATIVE_THREAD(thread)) {
      _PR_MISCQ_LOCK(thread->cpu);
      _PR_ADD_JOINQ(thread, thread->cpu);
      _PR_MISCQ_UNLOCK(thread->cpu);
    }
    _PR_THREAD_UNLOCK(thread);
    PR_NotifyCondVar(thread->term);
    PR_Unlock(_pr_terminationCVLock);
    _PR_MD_WAIT(thread, PR_INTERVAL_NO_TIMEOUT);
    PR_ASSERT(thread->state != _PR_JOIN_WAIT);
  }
}

/*
 * Zero some of the data members of a recycled thread.
 *
 * Note that we can do this either when a dead thread is added to
 * the dead thread queue or when it is reused.  Here, we are doing
 * this lazily, when the thread is reused in _PR_CreateThread().
 */
static void _PR_InitializeRecycledThread(PRThread* thread) {
  /*
   * Assert that the following data members are already zeroed
   * by _PR_CleanupThread().
   */
#ifdef DEBUG
  if (thread->privateData) {
    unsigned int i;
    for (i = 0; i < thread->tpdLength; i++) {
      PR_ASSERT(thread->privateData[i] == NULL);
    }
  }
#endif
  PR_ASSERT(thread->dumpArg == 0 && thread->dump == 0);
  PR_ASSERT(thread->errorString == 0 && thread->errorStringSize == 0);
  PR_ASSERT(thread->errorStringLength == 0);
  PR_ASSERT(thread->name == 0);

  /* Reset data members in thread structure */
  thread->errorCode = thread->osErrorCode = 0;
  thread->io_pending = thread->io_suspended = PR_FALSE;
  thread->environment = 0;
  PR_INIT_CLIST(&thread->lockList);
}

PRStatus _PR_RecycleThread(PRThread* thread) {
  if (_PR_IS_NATIVE_THREAD(thread) && _PR_NUM_DEADNATIVE < _pr_recycleThreads) {
    _PR_DEADQ_LOCK;
    PR_APPEND_LINK(&thread->links, &_PR_DEADNATIVEQ);
    _PR_INC_DEADNATIVE;
    _PR_DEADQ_UNLOCK;
    return (PR_SUCCESS);
  } else if (!_PR_IS_NATIVE_THREAD(thread) &&
             _PR_NUM_DEADUSER < _pr_recycleThreads) {
    _PR_DEADQ_LOCK;
    PR_APPEND_LINK(&thread->links, &_PR_DEADUSERQ);
    _PR_INC_DEADUSER;
    _PR_DEADQ_UNLOCK;
    return (PR_SUCCESS);
  }
  return (PR_FAILURE);
}

/*
 * Decrement the active thread count, either _pr_systemActive or
 * _pr_userActive, depending on whether the thread is a system thread
 * or a user thread.  If all the user threads, except possibly
 * the primordial thread, have terminated, we notify the primordial
 * thread of this condition.
 *
 * Since this function will lock _pr_activeLock, do not call this
 * function while holding the _pr_activeLock lock, as this will result
 * in a deadlock.
 */

static void _PR_DecrActiveThreadCount(PRThread* thread) {
  PR_Lock(_pr_activeLock);
  if (thread->flags & _PR_SYSTEM) {
    _pr_systemActive--;
  } else {
    _pr_userActive--;
    if (_pr_userActive == _pr_primordialExitCount) {
      PR_NotifyCondVar(_pr_primordialExitCVar);
    }
  }
  PR_Unlock(_pr_activeLock);
}

/*
** Detach thread structure
*/
static void _PR_DestroyThread(PRThread* thread) {
  _PR_MD_FREE_LOCK(&thread->threadLock);
  PR_DELETE(thread);
}

void _PR_NativeDestroyThread(PRThread* thread) {
  if (thread->term) {
    PR_DestroyCondVar(thread->term);
    thread->term = 0;
  }
  if (NULL != thread->privateData) {
    PR_ASSERT(0 != thread->tpdLength);
    PR_DELETE(thread->privateData);
    thread->tpdLength = 0;
  }
  PR_DELETE(thread->stack);
  _PR_DestroyThread(thread);
}

void _PR_UserDestroyThread(PRThread* thread) {
  if (thread->term) {
    PR_DestroyCondVar(thread->term);
    thread->term = 0;
  }
  if (NULL != thread->privateData) {
    PR_ASSERT(0 != thread->tpdLength);
    PR_DELETE(thread->privateData);
    thread->tpdLength = 0;
  }
  _PR_MD_FREE_LOCK(&thread->threadLock);
  if (thread->threadAllocatedOnStack == 1) {
    _PR_MD_CLEAN_THREAD(thread);
    /*
     *  Because the no_sched field is set, this thread/stack will
     *  will not be re-used until the flag is cleared by the thread
     *  we will context switch to.
     */
    _PR_FreeStack(thread->stack);
  } else {
#ifdef WINNT
    _PR_MD_CLEAN_THREAD(thread);
#else
    /*
     * This assertion does not apply to NT.  On NT, every fiber
     * has its threadAllocatedOnStack equal to 0.  Elsewhere,
     * only the primordial thread has its threadAllocatedOnStack
     * equal to 0.
     */
    PR_ASSERT(thread->flags & _PR_PRIMORDIAL);
#endif
  }
}

/*
** Run a thread's start function. When the start function returns the
** thread is done executing and no longer needs the CPU. If there are no
** more user threads running then we can exit the program.
*/
void _PR_NativeRunThread(void* arg) {
  PRThread* thread = (PRThread*)arg;

  _PR_MD_SET_CURRENT_THREAD(thread);

  _PR_MD_SET_CURRENT_CPU(NULL);

  /* Set up the thread stack information */
  _PR_InitializeNativeStack(thread->stack);

  /* Set up the thread md information */
  if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
    /*
     * thread failed to initialize itself, possibly due to
     * failure to allocate per-thread resources
     */
    return;
  }

  while (1) {
    thread->state = _PR_RUNNING;

    /*
     * Add to list of active threads
     */
    PR_Lock(_pr_activeLock);
    PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_GLOBAL_THREADQ());
    _pr_global_threads++;
    PR_Unlock(_pr_activeLock);

    (*thread->startFunc)(thread->arg);

    /*
     * The following two assertions are meant for NT asynch io.
     *
     * The thread should have no asynch io in progress when it
     * exits, otherwise the overlapped buffer, which is part of
     * the thread structure, would become invalid.
     */
    PR_ASSERT(thread->io_pending == PR_FALSE);
    /*
     * This assertion enforces the programming guideline that
     * if an io function times out or is interrupted, the thread
     * should close the fd to force the asynch io to abort
     * before it exits.  Right now, closing the fd is the only
     * way to clear the io_suspended flag.
     */
    PR_ASSERT(thread->io_suspended == PR_FALSE);

    /*
     * remove thread from list of active threads
     */
    PR_Lock(_pr_activeLock);
    PR_REMOVE_LINK(&thread->active);
    _pr_global_threads--;
    PR_Unlock(_pr_activeLock);

    PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("thread exiting"));

    /* All done, time to go away */
    _PR_CleanupThread(thread);

    _PR_NotifyJoinWaiters(thread);

    _PR_DecrActiveThreadCount(thread);

    thread->state = _PR_DEAD_STATE;

    if (!_pr_recycleThreads || (_PR_RecycleThread(thread) == PR_FAILURE)) {
      /*
       * thread not recycled
       * platform-specific thread exit processing
       *        - for stuff like releasing native-thread resources, etc.
       */
      _PR_MD_EXIT_THREAD(thread);
      /*
       * Free memory allocated for the thread
       */
      _PR_NativeDestroyThread(thread);
      /*
       * thread gone, cannot de-reference thread now
       */
      return;
    }

    /* Now wait for someone to activate us again... */
    _PR_MD_WAIT(thread, PR_INTERVAL_NO_TIMEOUT);
  }
}

static void _PR_UserRunThread(void) {
  PRThread* thread = _PR_MD_CURRENT_THREAD();
  PRIntn is;

  if (_MD_LAST_THREAD()) {
    _MD_LAST_THREAD()->no_sched = 0;
  }

#ifdef HAVE_CUSTOM_USER_THREADS
  if (thread->stack == NULL) {
    thread->stack = PR_NEWZAP(PRThreadStack);
    _PR_InitializeNativeStack(thread->stack);
  }
#endif /* HAVE_CUSTOM_USER_THREADS */

  while (1) {
    /* Run thread main */
    if (!_PR_IS_NATIVE_THREAD(thread)) {
      _PR_MD_SET_INTSOFF(0);
    }

    /*
     * Add to list of active threads
     */
    if (!(thread->flags & _PR_IDLE_THREAD)) {
      PR_Lock(_pr_activeLock);
      PR_APPEND_LINK(&thread->active, &_PR_ACTIVE_LOCAL_THREADQ());
      _pr_local_threads++;
      PR_Unlock(_pr_activeLock);
    }

    (*thread->startFunc)(thread->arg);

    /*
     * The following two assertions are meant for NT asynch io.
     *
     * The thread should have no asynch io in progress when it
     * exits, otherwise the overlapped buffer, which is part of
     * the thread structure, would become invalid.
     */
    PR_ASSERT(thread->io_pending == PR_FALSE);
    /*
     * This assertion enforces the programming guideline that
     * if an io function times out or is interrupted, the thread
     * should close the fd to force the asynch io to abort
     * before it exits.  Right now, closing the fd is the only
     * way to clear the io_suspended flag.
     */
    PR_ASSERT(thread->io_suspended == PR_FALSE);

    PR_Lock(_pr_activeLock);
    /*
     * remove thread from list of active threads
     */
    if (!(thread->flags & _PR_IDLE_THREAD)) {
      PR_REMOVE_LINK(&thread->active);
      _pr_local_threads--;
    }
    PR_Unlock(_pr_activeLock);
    PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("thread exiting"));

    /* All done, time to go away */
    _PR_CleanupThread(thread);

    _PR_INTSOFF(is);

    _PR_NotifyJoinWaiters(thread);

    _PR_DecrActiveThreadCount(thread);

    thread->state = _PR_DEAD_STATE;

    if (!_pr_recycleThreads || (_PR_RecycleThread(thread) == PR_FAILURE)) {
      /*
      ** Destroy the thread resources
      */
      _PR_UserDestroyThread(thread);
    }

    /*
    ** Find another user thread to run. This cpu has finished the
    ** previous threads main and is now ready to run another thread.
    */
    {
      PRInt32 is;
      _PR_INTSOFF(is);
      _PR_MD_SWITCH_CONTEXT(thread);
    }

    /* Will land here when we get scheduled again if we are recycling... */
  }
}

void _PR_SetThreadPriority(PRThread* thread, PRThreadPriority newPri) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRIntn is;

  if (_PR_IS_NATIVE_THREAD(thread)) {
    _PR_MD_SET_PRIORITY(&(thread->md), newPri);
    return;
  }

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  _PR_THREAD_LOCK(thread);
  if (newPri != thread->priority) {
    _PRCPU* cpu = thread->cpu;

    switch (thread->state) {
      case _PR_RUNNING:
        /* Change my priority */

        _PR_RUNQ_LOCK(cpu);
        thread->priority = newPri;
        if (_PR_RUNQREADYMASK(cpu) >> (newPri + 1)) {
          if (!_PR_IS_NATIVE_THREAD(me)) {
            _PR_SET_RESCHED_FLAG();
          }
        }
        _PR_RUNQ_UNLOCK(cpu);
        break;

      case _PR_RUNNABLE:

        _PR_RUNQ_LOCK(cpu);
        /* Move to different runQ */
        _PR_DEL_RUNQ(thread);
        thread->priority = newPri;
        PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
        _PR_ADD_RUNQ(thread, cpu, newPri);
        _PR_RUNQ_UNLOCK(cpu);

        if (newPri > me->priority) {
          if (!_PR_IS_NATIVE_THREAD(me)) {
            _PR_SET_RESCHED_FLAG();
          }
        }

        break;

      case _PR_LOCK_WAIT:
      case _PR_COND_WAIT:
      case _PR_IO_WAIT:
      case _PR_SUSPENDED:

        thread->priority = newPri;
        break;
    }
  }
  _PR_THREAD_UNLOCK(thread);
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }
}

/*
** Suspend the named thread and copy its gc registers into regBuf
*/
static void _PR_Suspend(PRThread* thread) {
  PRIntn is;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  PR_ASSERT(thread != me);
  PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread) || (!thread->cpu));

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  _PR_THREAD_LOCK(thread);
  switch (thread->state) {
    case _PR_RUNNABLE:
      if (!_PR_IS_NATIVE_THREAD(thread)) {
        _PR_RUNQ_LOCK(thread->cpu);
        _PR_DEL_RUNQ(thread);
        _PR_RUNQ_UNLOCK(thread->cpu);

        _PR_MISCQ_LOCK(thread->cpu);
        _PR_ADD_SUSPENDQ(thread, thread->cpu);
        _PR_MISCQ_UNLOCK(thread->cpu);
      } else {
        /*
         * Only LOCAL threads are suspended by _PR_Suspend
         */
        PR_ASSERT(0);
      }
      thread->state = _PR_SUSPENDED;
      break;

    case _PR_RUNNING:
      /*
       * The thread being suspended should be a LOCAL thread with
       * _pr_numCPUs == 1. Hence, the thread cannot be in RUNNING state
       */
      PR_ASSERT(0);
      break;

    case _PR_LOCK_WAIT:
    case _PR_IO_WAIT:
    case _PR_COND_WAIT:
      if (_PR_IS_NATIVE_THREAD(thread)) {
        _PR_MD_SUSPEND_THREAD(thread);
      }
      thread->flags |= _PR_SUSPENDING;
      break;

    default:
      PR_Abort();
  }
  _PR_THREAD_UNLOCK(thread);
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }
}

static void _PR_Resume(PRThread* thread) {
  PRThreadPriority pri;
  PRIntn is;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  _PR_THREAD_LOCK(thread);
  switch (thread->state) {
    case _PR_SUSPENDED:
      thread->state = _PR_RUNNABLE;
      thread->flags &= ~_PR_SUSPENDING;
      if (!_PR_IS_NATIVE_THREAD(thread)) {
        _PR_MISCQ_LOCK(thread->cpu);
        _PR_DEL_SUSPENDQ(thread);
        _PR_MISCQ_UNLOCK(thread->cpu);

        pri = thread->priority;

        _PR_RUNQ_LOCK(thread->cpu);
        _PR_ADD_RUNQ(thread, thread->cpu, pri);
        _PR_RUNQ_UNLOCK(thread->cpu);

        if (pri > _PR_MD_CURRENT_THREAD()->priority) {
          if (!_PR_IS_NATIVE_THREAD(me)) {
            _PR_SET_RESCHED_FLAG();
          }
        }
      } else {
        PR_ASSERT(0);
      }
      break;

    case _PR_IO_WAIT:
    case _PR_COND_WAIT:
      thread->flags &= ~_PR_SUSPENDING;
      /*      PR_ASSERT(thread->wait.monitor->stickyCount == 0); */
      break;

    case _PR_LOCK_WAIT: {
      PRLock* wLock = thread->wait.lock;

      thread->flags &= ~_PR_SUSPENDING;

      _PR_LOCK_LOCK(wLock);
      if (thread->wait.lock->owner == 0) {
        _PR_UnblockLockWaiter(thread->wait.lock);
      }
      _PR_LOCK_UNLOCK(wLock);
      break;
    }
    case _PR_RUNNABLE:
      break;
    case _PR_RUNNING:
      /*
       * The thread being suspended should be a LOCAL thread with
       * _pr_numCPUs == 1. Hence, the thread cannot be in RUNNING state
       */
      PR_ASSERT(0);
      break;

    default:
      /*
       * thread should have been in one of the above-listed blocked states
       * (_PR_JOIN_WAIT, _PR_IO_WAIT, _PR_UNBORN, _PR_DEAD_STATE)
       */
      PR_Abort();
  }
  _PR_THREAD_UNLOCK(thread);
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }
}

#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)
static PRThread* get_thread(_PRCPU* cpu, PRBool* wakeup_cpus) {
  PRThread* thread;
  PRIntn pri;
  PRUint32 r;
  PRCList* qp;
  PRIntn priMin, priMax;

  _PR_RUNQ_LOCK(cpu);
  r = _PR_RUNQREADYMASK(cpu);
  if (r == 0) {
    priMin = priMax = PR_PRIORITY_FIRST;
  } else if (r == (1 << PR_PRIORITY_NORMAL)) {
    priMin = priMax = PR_PRIORITY_NORMAL;
  } else {
    priMin = PR_PRIORITY_FIRST;
    priMax = PR_PRIORITY_LAST;
  }
  thread = NULL;
  for (pri = priMax; pri >= priMin; pri--) {
    if (r & (1 << pri)) {
      for (qp = _PR_RUNQ(cpu)[pri].next; qp != &_PR_RUNQ(cpu)[pri];
           qp = qp->next) {
        thread = _PR_THREAD_PTR(qp);
        /*
         * skip non-schedulable threads
         */
        PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
        if (thread->no_sched) {
          thread = NULL;
          /*
           * Need to wakeup cpus to avoid missing a
           * runnable thread
           * Waking up all CPU's need happen only once.
           */

          *wakeup_cpus = PR_TRUE;
          continue;
        } else if (thread->flags & _PR_BOUND_THREAD) {
          /*
           * Thread bound to cpu 0
           */

          thread = NULL;
          continue;
        } else if (thread->io_pending == PR_TRUE) {
          /*
           * A thread that is blocked for I/O needs to run
           * on the same cpu on which it was blocked. This is because
           * the cpu's ioq is accessed without lock protection and scheduling
           * the thread on a different cpu would preclude this optimization.
           */
          thread = NULL;
          continue;
        } else {
          /* Pull thread off of its run queue */
          _PR_DEL_RUNQ(thread);
          _PR_RUNQ_UNLOCK(cpu);
          return (thread);
        }
      }
    }
    thread = NULL;
  }
  _PR_RUNQ_UNLOCK(cpu);
  return (thread);
}
#endif /* !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX) */

/*
** Schedule this native thread by finding the highest priority nspr
** thread that is ready to run.
**
** Note- everyone really needs to call _PR_MD_SWITCH_CONTEXT (which calls
**       PR_Schedule() rather than calling PR_Schedule.  Otherwise if there
**       is initialization required for switching from SWITCH_CONTEXT,
**       it will not get done!
*/
void _PR_Schedule(void) {
  PRThread *thread, *me = _PR_MD_CURRENT_THREAD();
  _PRCPU* cpu = _PR_MD_CURRENT_CPU();
  PRIntn pri;
  PRUint32 r;
  PRCList* qp;
  PRIntn priMin, priMax;
#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)
  PRBool wakeup_cpus;
#endif

  /* Interrupts must be disabled */
  PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);

  /* Since we are rescheduling, we no longer want to */
  _PR_CLEAR_RESCHED_FLAG();

  /*
  ** Find highest priority thread to run. Bigger priority numbers are
  ** higher priority threads
  */
  _PR_RUNQ_LOCK(cpu);
  /*
   *  if we are in SuspendAll mode, can schedule only the thread
   *    that called PR_SuspendAll
   *
   *  The thread may be ready to run now, after completing an I/O
   *  operation, for example
   */
  if ((thread = suspendAllThread) != 0) {
    if ((!(thread->no_sched)) && (thread->state == _PR_RUNNABLE)) {
      /* Pull thread off of its run queue */
      _PR_DEL_RUNQ(thread);
      _PR_RUNQ_UNLOCK(cpu);
      goto found_thread;
    } else {
      thread = NULL;
      _PR_RUNQ_UNLOCK(cpu);
      goto idle_thread;
    }
  }
  r = _PR_RUNQREADYMASK(cpu);
  if (r == 0) {
    priMin = priMax = PR_PRIORITY_FIRST;
  } else if (r == (1 << PR_PRIORITY_NORMAL)) {
    priMin = priMax = PR_PRIORITY_NORMAL;
  } else {
    priMin = PR_PRIORITY_FIRST;
    priMax = PR_PRIORITY_LAST;
  }
  thread = NULL;
  for (pri = priMax; pri >= priMin; pri--) {
    if (r & (1 << pri)) {
      for (qp = _PR_RUNQ(cpu)[pri].next; qp != &_PR_RUNQ(cpu)[pri];
           qp = qp->next) {
        thread = _PR_THREAD_PTR(qp);
        /*
         * skip non-schedulable threads
         */
        PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
        if ((thread->no_sched) && (me != thread)) {
          thread = NULL;
          continue;
        } else {
          /* Pull thread off of its run queue */
          _PR_DEL_RUNQ(thread);
          _PR_RUNQ_UNLOCK(cpu);
          goto found_thread;
        }
      }
    }
    thread = NULL;
  }
  _PR_RUNQ_UNLOCK(cpu);

#if !defined(_PR_LOCAL_THREADS_ONLY) && defined(XP_UNIX)

  wakeup_cpus = PR_FALSE;
  _PR_CPU_LIST_LOCK();
  for (qp = _PR_CPUQ().next; qp != &_PR_CPUQ(); qp = qp->next) {
    if (cpu != _PR_CPU_PTR(qp)) {
      if ((thread = get_thread(_PR_CPU_PTR(qp), &wakeup_cpus)) != NULL) {
        thread->cpu = cpu;
        _PR_CPU_LIST_UNLOCK();
        if (wakeup_cpus == PR_TRUE) {
          _PR_MD_WAKEUP_CPUS();
        }
        goto found_thread;
      }
    }
  }
  _PR_CPU_LIST_UNLOCK();
  if (wakeup_cpus == PR_TRUE) {
    _PR_MD_WAKEUP_CPUS();
  }

#endif /* _PR_LOCAL_THREADS_ONLY */

idle_thread:
  /*
   ** There are no threads to run. Switch to the idle thread
   */
  PR_LOG(_pr_sched_lm, PR_LOG_MAX, ("pausing"));
  thread = _PR_MD_CURRENT_CPU()->idle_thread;

found_thread:
  PR_ASSERT((me == thread) ||
            ((thread->state == _PR_RUNNABLE) && (!(thread->no_sched))));

  /* Resume the thread */
  PR_LOG(_pr_sched_lm, PR_LOG_MAX, ("switching to %d[%p]", thread->id, thread));
  PR_ASSERT(thread->state != _PR_RUNNING);
  thread->state = _PR_RUNNING;

  /* If we are on the runq, it just means that we went to sleep on some
   * resource, and by the time we got here another real native thread had
   * already given us the resource and put us back on the runqueue
   */
  PR_ASSERT(thread->cpu == _PR_MD_CURRENT_CPU());
  if (thread != me) {
    _PR_MD_RESTORE_CONTEXT(thread);
  }
#if 0
    /* XXXMB; with setjmp/longjmp it is impossible to land here, but
     * it is not with fibers... Is this a bad thing?  I believe it is
     * still safe.
     */
    PR_NOT_REACHED("impossible return from schedule");
#endif
}

/*
** Attaches a thread.
** Does not set the _PR_MD_CURRENT_THREAD.
** Does not specify the scope of the thread.
*/
static PRThread* _PR_AttachThread(PRThreadType type, PRThreadPriority priority,
                                  PRThreadStack* stack) {
  PRThread* thread;
  char* mem;

  if (priority > PR_PRIORITY_LAST) {
    priority = PR_PRIORITY_LAST;
  } else if (priority < PR_PRIORITY_FIRST) {
    priority = PR_PRIORITY_FIRST;
  }

  mem = (char*)PR_CALLOC(sizeof(PRThread));
  if (mem) {
    thread = (PRThread*)mem;
    thread->priority = priority;
    thread->stack = stack;
    thread->state = _PR_RUNNING;
    PR_INIT_CLIST(&thread->lockList);
    if (_PR_MD_NEW_LOCK(&thread->threadLock) == PR_FAILURE) {
      PR_DELETE(thread);
      return 0;
    }

    return thread;
  }
  return 0;
}

PR_IMPLEMENT(PRThread*)
_PR_NativeCreateThread(PRThreadType type, void (*start)(void* arg), void* arg,
                       PRThreadPriority priority, PRThreadScope scope,
                       PRThreadState state, PRUint32 stackSize,
                       PRUint32 flags) {
  PRThread* thread;

  thread = _PR_AttachThread(type, priority, NULL);

  if (thread) {
    PR_Lock(_pr_activeLock);
    thread->flags = (flags | _PR_GLOBAL_SCOPE);
    thread->id = ++_pr_utid;
    if (type == PR_SYSTEM_THREAD) {
      thread->flags |= _PR_SYSTEM;
      _pr_systemActive++;
    } else {
      _pr_userActive++;
    }
    PR_Unlock(_pr_activeLock);

    thread->stack = PR_NEWZAP(PRThreadStack);
    if (!thread->stack) {
      PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
      goto done;
    }
    thread->stack->stackSize = stackSize ? stackSize : _MD_DEFAULT_STACK_SIZE;
    thread->stack->thr = thread;
    thread->startFunc = start;
    thread->arg = arg;

    /*
      Set thread flags related to scope and joinable state. If joinable
      thread, allocate a "termination" conidition variable.
     */
    if (state == PR_JOINABLE_THREAD) {
      thread->term = PR_NewCondVar(_pr_terminationCVLock);
      if (thread->term == NULL) {
        PR_DELETE(thread->stack);
        goto done;
      }
    }

    thread->state = _PR_RUNNING;
    if (_PR_MD_CREATE_THREAD(thread, _PR_NativeRunThread, priority, scope,
                             state, stackSize) == PR_SUCCESS) {
      return thread;
    }
    if (thread->term) {
      PR_DestroyCondVar(thread->term);
      thread->term = NULL;
    }
    PR_DELETE(thread->stack);
  }

done:
  if (thread) {
    _PR_DecrActiveThreadCount(thread);
    _PR_DestroyThread(thread);
  }
  return NULL;
}

/************************************************************************/

PR_IMPLEMENT(PRThread*)
_PR_CreateThread(PRThreadType type, void (*start)(void* arg), void* arg,
                 PRThreadPriority priority, PRThreadScope scope,
                 PRThreadState state, PRUint32 stackSize, PRUint32 flags) {
  PRThread* me;
  PRThread* thread = NULL;
  PRThreadStack* stack;
  char* top;
  PRIntn is;
  PRIntn native = 0;
  PRIntn useRecycled = 0;
  PRBool status;

  /*
  First, pin down the priority.  Not all compilers catch passing out of
  range enum here.  If we let bad values thru, priority queues won't work.
  */
  if (priority > PR_PRIORITY_LAST) {
    priority = PR_PRIORITY_LAST;
  } else if (priority < PR_PRIORITY_FIRST) {
    priority = PR_PRIORITY_FIRST;
  }

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }

  if (!(flags & _PR_IDLE_THREAD)) {
    me = _PR_MD_CURRENT_THREAD();
  }

#if defined(_PR_GLOBAL_THREADS_ONLY)
  /*
   * can create global threads only
   */
  if (scope == PR_LOCAL_THREAD) {
    scope = PR_GLOBAL_THREAD;
  }
#endif

  if (_native_threads_only) {
    scope = PR_GLOBAL_THREAD;
  }

  native =
      (((scope == PR_GLOBAL_THREAD) || (scope == PR_GLOBAL_BOUND_THREAD)) &&
       _PR_IS_NATIVE_THREAD_SUPPORTED());

  _PR_ADJUST_STACKSIZE(stackSize);

  if (native) {
    /*
     * clear the IDLE_THREAD flag which applies to LOCAL
     * threads only
     */
    flags &= ~_PR_IDLE_THREAD;
    flags |= _PR_GLOBAL_SCOPE;
    if (_PR_NUM_DEADNATIVE > 0) {
      _PR_DEADQ_LOCK;

      if (_PR_NUM_DEADNATIVE == 0) { /* Thread safe check */
        _PR_DEADQ_UNLOCK;
      } else {
        thread = _PR_THREAD_PTR(_PR_DEADNATIVEQ.next);
        PR_REMOVE_LINK(&thread->links);
        _PR_DEC_DEADNATIVE;
        _PR_DEADQ_UNLOCK;

        _PR_InitializeRecycledThread(thread);
        thread->startFunc = start;
        thread->arg = arg;
        thread->flags = (flags | _PR_GLOBAL_SCOPE);
        if (type == PR_SYSTEM_THREAD) {
          thread->flags |= _PR_SYSTEM;
          PR_ATOMIC_INCREMENT(&_pr_systemActive);
        } else {
          PR_ATOMIC_INCREMENT(&_pr_userActive);
        }

        if (state == PR_JOINABLE_THREAD) {
          if (!thread->term) {
            thread->term = PR_NewCondVar(_pr_terminationCVLock);
          }
        } else {
          if (thread->term) {
            PR_DestroyCondVar(thread->term);
            thread->term = 0;
          }
        }

        thread->priority = priority;
        _PR_MD_SET_PRIORITY(&(thread->md), priority);
        /* XXX what about stackSize? */
        thread->state = _PR_RUNNING;
        _PR_MD_WAKEUP_WAITER(thread);
        return thread;
      }
    }
    thread = _PR_NativeCreateThread(type, start, arg, priority, scope, state,
                                    stackSize, flags);
  } else {
    if (_PR_NUM_DEADUSER > 0) {
      _PR_DEADQ_LOCK;

      if (_PR_NUM_DEADUSER == 0) { /* thread safe check */
        _PR_DEADQ_UNLOCK;
      } else {
        PRCList* ptr;

        /* Go down list checking for a recycled thread with a
         * large enough stack.  XXXMB - this has a bad degenerate case.
         */
        ptr = _PR_DEADUSERQ.next;
        while (ptr != &_PR_DEADUSERQ) {
          thread = _PR_THREAD_PTR(ptr);
          if ((thread->stack->stackSize >= stackSize) && (!thread->no_sched)) {
            PR_REMOVE_LINK(&thread->links);
            _PR_DEC_DEADUSER;
            break;
          } else {
            ptr = ptr->next;
            thread = NULL;
          }
        }

        _PR_DEADQ_UNLOCK;

        if (thread) {
          _PR_InitializeRecycledThread(thread);
          thread->startFunc = start;
          thread->arg = arg;
          thread->priority = priority;
          if (state == PR_JOINABLE_THREAD) {
            if (!thread->term) {
              thread->term = PR_NewCondVar(_pr_terminationCVLock);
            }
          } else {
            if (thread->term) {
              PR_DestroyCondVar(thread->term);
              thread->term = 0;
            }
          }
          useRecycled++;
        }
      }
    }
    if (thread == NULL) {
#ifndef HAVE_CUSTOM_USER_THREADS
      stack = _PR_NewStack(stackSize);
      if (!stack) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return NULL;
      }

      /* Allocate thread object and per-thread data off the top of the stack*/
      top = stack->stackTop;
#  ifdef HAVE_STACK_GROWING_UP
      thread = (PRThread*)top;
      top = top + sizeof(PRThread);
      /*
       * Make stack 64-byte aligned
       */
      if ((PRUptrdiff)top & 0x3f) {
        top = (char*)(((PRUptrdiff)top + 0x40) & ~0x3f);
      }
#  else
      top = top - sizeof(PRThread);
      thread = (PRThread*)top;
      /*
       * Make stack 64-byte aligned
       */
      if ((PRUptrdiff)top & 0x3f) {
        top = (char*)((PRUptrdiff)top & ~0x3f);
      }
#  endif
      stack->thr = thread;
      memset(thread, 0, sizeof(PRThread));
      thread->threadAllocatedOnStack = 1;
#else
      thread = _PR_MD_CREATE_USER_THREAD(stackSize, start, arg);
      if (!thread) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return NULL;
      }
      thread->threadAllocatedOnStack = 0;
      stack = NULL;
      top = NULL;
#endif

      /* Initialize thread */
      thread->tpdLength = 0;
      thread->privateData = NULL;
      thread->stack = stack;
      thread->priority = priority;
      thread->startFunc = start;
      thread->arg = arg;
      PR_INIT_CLIST(&thread->lockList);

      if (_PR_MD_INIT_THREAD(thread) == PR_FAILURE) {
        if (thread->threadAllocatedOnStack == 1) {
          _PR_FreeStack(thread->stack);
        } else {
          PR_DELETE(thread);
        }
        PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
        return NULL;
      }

      if (_PR_MD_NEW_LOCK(&thread->threadLock) == PR_FAILURE) {
        if (thread->threadAllocatedOnStack == 1) {
          _PR_FreeStack(thread->stack);
        } else {
          PR_DELETE(thread->privateData);
          PR_DELETE(thread);
        }
        PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
        return NULL;
      }

      _PR_MD_INIT_CONTEXT(thread, top, _PR_UserRunThread, &status);

      if (status == PR_FALSE) {
        _PR_MD_FREE_LOCK(&thread->threadLock);
        if (thread->threadAllocatedOnStack == 1) {
          _PR_FreeStack(thread->stack);
        } else {
          PR_DELETE(thread->privateData);
          PR_DELETE(thread);
        }
        return NULL;
      }

      /*
        Set thread flags related to scope and joinable state. If joinable
        thread, allocate a "termination" condition variable.
      */
      if (state == PR_JOINABLE_THREAD) {
        thread->term = PR_NewCondVar(_pr_terminationCVLock);
        if (thread->term == NULL) {
          _PR_MD_FREE_LOCK(&thread->threadLock);
          if (thread->threadAllocatedOnStack == 1) {
            _PR_FreeStack(thread->stack);
          } else {
            PR_DELETE(thread->privateData);
            PR_DELETE(thread);
          }
          return NULL;
        }
      }
    }

    /* Update thread type counter */
    PR_Lock(_pr_activeLock);
    thread->flags = flags;
    thread->id = ++_pr_utid;
    if (type == PR_SYSTEM_THREAD) {
      thread->flags |= _PR_SYSTEM;
      _pr_systemActive++;
    } else {
      _pr_userActive++;
    }

    /* Make thread runnable */
    thread->state = _PR_RUNNABLE;
    /*
     * Add to list of active threads
     */
    PR_Unlock(_pr_activeLock);

    if ((!(thread->flags & _PR_IDLE_THREAD)) && _PR_IS_NATIVE_THREAD(me)) {
      thread->cpu = _PR_GetPrimordialCPU();
    } else {
      thread->cpu = _PR_MD_CURRENT_CPU();
    }

    PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));

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

    if (thread->flags & _PR_IDLE_THREAD) {
      /*
      ** If the creating thread is a kernel thread, we need to
      ** awaken the user thread idle thread somehow; potentially
      ** it could be sleeping in its idle loop, and we need to poke
      ** it.  To do so, wake the idle thread...
      */
      _PR_MD_WAKEUP_WAITER(NULL);
    } else if (_PR_IS_NATIVE_THREAD(me)) {
      _PR_MD_WAKEUP_WAITER(thread);
    }
    if ((!(thread->flags & _PR_IDLE_THREAD)) && !_PR_IS_NATIVE_THREAD(me)) {
      _PR_INTSON(is);
    }
  }

  return thread;
}

PR_IMPLEMENT(PRThread*)
PR_CreateThread(PRThreadType type, void (*start)(void* arg), void* arg,
                PRThreadPriority priority, PRThreadScope scope,
                PRThreadState state, PRUint32 stackSize) {
  return _PR_CreateThread(type, start, arg, priority, scope, state, stackSize,
                          0);
}

/*
** Associate a thread object with an existing native thread.
**     "type" is the type of thread object to attach
**     "priority" is the priority to assign to the thread
**     "stack" defines the shape of the threads stack
**
** This can return NULL if some kind of error occurs, or if memory is
** tight.
**
** This call is not normally needed unless you create your own native
** thread. PR_Init does this automatically for the primordial thread.
*/
PRThread* _PRI_AttachThread(PRThreadType type, PRThreadPriority priority,
                            PRThreadStack* stack, PRUint32 flags) {
  PRThread* thread;

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

  /* Clear out any state if this thread was attached before */
  _PR_MD_SET_CURRENT_CPU(NULL);

  thread = _PR_AttachThread(type, priority, stack);
  if (thread) {
    PRIntn is;

    _PR_MD_SET_CURRENT_THREAD(thread);

    thread->flags = flags | _PR_GLOBAL_SCOPE | _PR_ATTACHED;

    if (!stack) {
      thread->stack = PR_NEWZAP(PRThreadStack);
      if (!thread->stack) {
        _PR_DestroyThread(thread);
        return NULL;
      }
      thread->stack->stackSize = _MD_DEFAULT_STACK_SIZE;
    }
    PR_INIT_CLIST(&thread->links);

    if (_PR_MD_INIT_ATTACHED_THREAD(thread) == PR_FAILURE) {
      PR_DELETE(thread->stack);
      _PR_DestroyThread(thread);
      return NULL;
    }

    _PR_MD_SET_CURRENT_CPU(NULL);

    if (_PR_MD_CURRENT_CPU()) {
      _PR_INTSOFF(is);
      PR_Lock(_pr_activeLock);
    }
    if (type == PR_SYSTEM_THREAD) {
      thread->flags |= _PR_SYSTEM;
      _pr_systemActive++;
    } else {
      _pr_userActive++;
    }
    if (_PR_MD_CURRENT_CPU()) {
      PR_Unlock(_pr_activeLock);
      _PR_INTSON(is);
    }
  }
  return thread;
}

PR_IMPLEMENT(PRThread*)
PR_AttachThread(PRThreadType type, PRThreadPriority priority,
                PRThreadStack* stack) {
  return PR_GetCurrentThread();
}

PR_IMPLEMENT(void) PR_DetachThread(void) {
  /*
   * On 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
}
