blob: 6d5d6688ab9936473db326c72b4a8daadbfecf98 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape Portable Runtime (NSPR).
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "primpl.h"
#include "prinrval.h"
#include "prtypes.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
/*
** Notify one thread that it has finished waiting on a condition variable
** Caller must hold the _PR_CVAR_LOCK(cv)
*/
PRBool _PR_NotifyThread (PRThread *thread, PRThread *me)
{
PRBool rv;
PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);
_PR_THREAD_LOCK(thread);
PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
if ( !_PR_IS_NATIVE_THREAD(thread) ) {
if (thread->wait.cvar != NULL) {
thread->wait.cvar = NULL;
_PR_SLEEPQ_LOCK(thread->cpu);
/* The notify and timeout can collide; in which case both may
* attempt to delete from the sleepQ; only let one do it.
*/
if (thread->flags & (_PR_ON_SLEEPQ|_PR_ON_PAUSEQ))
_PR_DEL_SLEEPQ(thread, PR_TRUE);
_PR_SLEEPQ_UNLOCK(thread->cpu);
if (thread->flags & _PR_SUSPENDING) {
/*
* set thread state to SUSPENDED; a Resume operation
* on the thread will move it to the runQ
*/
thread->state = _PR_SUSPENDED;
_PR_MISCQ_LOCK(thread->cpu);
_PR_ADD_SUSPENDQ(thread, thread->cpu);
_PR_MISCQ_UNLOCK(thread->cpu);
_PR_THREAD_UNLOCK(thread);
} else {
/* Make thread runnable */
thread->state = _PR_RUNNABLE;
_PR_THREAD_UNLOCK(thread);
_PR_AddThreadToRunQ(me, thread);
_PR_MD_WAKEUP_WAITER(thread);
}
rv = PR_TRUE;
} else {
/* Thread has already been notified */
_PR_THREAD_UNLOCK(thread);
rv = PR_FALSE;
}
} else { /* If the thread is a native thread */
if (thread->wait.cvar) {
thread->wait.cvar = NULL;
if (thread->flags & _PR_SUSPENDING) {
/*
* set thread state to SUSPENDED; a Resume operation
* on the thread will enable the thread to run
*/
thread->state = _PR_SUSPENDED;
} else
thread->state = _PR_RUNNING;
_PR_THREAD_UNLOCK(thread);
_PR_MD_WAKEUP_WAITER(thread);
rv = PR_TRUE;
} else {
_PR_THREAD_UNLOCK(thread);
rv = PR_FALSE;
}
}
return rv;
}
/*
* Notify thread waiting on cvar; called when thread is interrupted
* The thread lock is held on entry and released before return
*/
void _PR_NotifyLockedThread (PRThread *thread)
{
PRThread *me = _PR_MD_CURRENT_THREAD();
PRCondVar *cvar;
PRThreadPriority pri;
if ( !_PR_IS_NATIVE_THREAD(me))
PR_ASSERT(_PR_MD_GET_INTSOFF() != 0);
cvar = thread->wait.cvar;
thread->wait.cvar = NULL;
_PR_THREAD_UNLOCK(thread);
_PR_CVAR_LOCK(cvar);
_PR_THREAD_LOCK(thread);
if (!_PR_IS_NATIVE_THREAD(thread)) {
_PR_SLEEPQ_LOCK(thread->cpu);
/* The notify and timeout can collide; in which case both may
* attempt to delete from the sleepQ; only let one do it.
*/
if (thread->flags & (_PR_ON_SLEEPQ|_PR_ON_PAUSEQ))
_PR_DEL_SLEEPQ(thread, PR_TRUE);
_PR_SLEEPQ_UNLOCK(thread->cpu);
/* Make thread runnable */
pri = thread->priority;
thread->state = _PR_RUNNABLE;
PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
_PR_AddThreadToRunQ(me, thread);
_PR_THREAD_UNLOCK(thread);
_PR_MD_WAKEUP_WAITER(thread);
} else {
if (thread->flags & _PR_SUSPENDING) {
/*
* set thread state to SUSPENDED; a Resume operation
* on the thread will enable the thread to run
*/
thread->state = _PR_SUSPENDED;
} else
thread->state = _PR_RUNNING;
_PR_THREAD_UNLOCK(thread);
_PR_MD_WAKEUP_WAITER(thread);
}
_PR_CVAR_UNLOCK(cvar);
return;
}
/*
** Make the given thread wait for the given condition variable
*/
PRStatus _PR_WaitCondVar(
PRThread *thread, PRCondVar *cvar, PRLock *lock, PRIntervalTime timeout)
{
PRIntn is;
PRStatus rv = PR_SUCCESS;
PR_ASSERT(thread == _PR_MD_CURRENT_THREAD());
PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
#ifdef _PR_GLOBAL_THREADS_ONLY
if (_PR_PENDING_INTERRUPT(thread)) {
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
thread->flags &= ~_PR_INTERRUPT;
return PR_FAILURE;
}
thread->wait.cvar = cvar;
lock->owner = NULL;
_PR_MD_WAIT_CV(&cvar->md,&lock->ilock, timeout);
thread->wait.cvar = NULL;
lock->owner = thread;
if (_PR_PENDING_INTERRUPT(thread)) {
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
thread->flags &= ~_PR_INTERRUPT;
return PR_FAILURE;
}
return PR_SUCCESS;
#else /* _PR_GLOBAL_THREADS_ONLY */
if ( !_PR_IS_NATIVE_THREAD(thread))
_PR_INTSOFF(is);
_PR_CVAR_LOCK(cvar);
_PR_THREAD_LOCK(thread);
if (_PR_PENDING_INTERRUPT(thread)) {
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
thread->flags &= ~_PR_INTERRUPT;
_PR_CVAR_UNLOCK(cvar);
_PR_THREAD_UNLOCK(thread);
if ( !_PR_IS_NATIVE_THREAD(thread))
_PR_INTSON(is);
return PR_FAILURE;
}
thread->state = _PR_COND_WAIT;
thread->wait.cvar = cvar;
/*
** Put the caller thread on the condition variable's wait Q
*/
PR_APPEND_LINK(&thread->waitQLinks, &cvar->condQ);
/* Note- for global scope threads, we don't put them on the
* global sleepQ, so each global thread must put itself
* to sleep only for the time it wants to.
*/
if ( !_PR_IS_NATIVE_THREAD(thread) ) {
_PR_SLEEPQ_LOCK(thread->cpu);
_PR_ADD_SLEEPQ(thread, timeout);
_PR_SLEEPQ_UNLOCK(thread->cpu);
}
_PR_CVAR_UNLOCK(cvar);
_PR_THREAD_UNLOCK(thread);
/*
** Release lock protecting the condition variable and thereby giving time
** to the next thread which can potentially notify on the condition variable
*/
PR_Unlock(lock);
PR_LOG(_pr_cvar_lm, PR_LOG_MIN,
("PR_Wait: cvar=%p waiting for %d", cvar, timeout));
rv = _PR_MD_WAIT(thread, timeout);
_PR_CVAR_LOCK(cvar);
PR_REMOVE_LINK(&thread->waitQLinks);
_PR_CVAR_UNLOCK(cvar);
PR_LOG(_pr_cvar_lm, PR_LOG_MIN,
("PR_Wait: cvar=%p done waiting", cvar));
if ( !_PR_IS_NATIVE_THREAD(thread))
_PR_INTSON(is);
/* Acquire lock again that we had just relinquished */
PR_Lock(lock);
if (_PR_PENDING_INTERRUPT(thread)) {
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
thread->flags &= ~_PR_INTERRUPT;
return PR_FAILURE;
}
return rv;
#endif /* _PR_GLOBAL_THREADS_ONLY */
}
void _PR_NotifyCondVar(PRCondVar *cvar, PRThread *me)
{
#ifdef _PR_GLOBAL_THREADS_ONLY
_PR_MD_NOTIFY_CV(&cvar->md, &cvar->lock->ilock);
#else /* _PR_GLOBAL_THREADS_ONLY */
PRCList *q;
PRIntn is;
if ( !_PR_IS_NATIVE_THREAD(me))
_PR_INTSOFF(is);
PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);
_PR_CVAR_LOCK(cvar);
q = cvar->condQ.next;
while (q != &cvar->condQ) {
PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("_PR_NotifyCondVar: cvar=%p", cvar));
if (_PR_THREAD_CONDQ_PTR(q)->wait.cvar) {
if (_PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me) == PR_TRUE)
break;
}
q = q->next;
}
_PR_CVAR_UNLOCK(cvar);
if ( !_PR_IS_NATIVE_THREAD(me))
_PR_INTSON(is);
#endif /* _PR_GLOBAL_THREADS_ONLY */
}
/*
** Cndition variable debugging log info.
*/
PRUint32 _PR_CondVarToString(PRCondVar *cvar, char *buf, PRUint32 buflen)
{
PRUint32 nb;
if (cvar->lock->owner) {
nb = PR_snprintf(buf, buflen, "[%p] owner=%ld[%p]",
cvar, cvar->lock->owner->id, cvar->lock->owner);
} else {
nb = PR_snprintf(buf, buflen, "[%p]", cvar);
}
return nb;
}
/*
** Expire condition variable waits that are ready to expire. "now" is the current
** time.
*/
void _PR_ClockInterrupt(void)
{
PRThread *thread, *me = _PR_MD_CURRENT_THREAD();
_PRCPU *cpu = me->cpu;
PRIntervalTime elapsed, now;
PR_ASSERT(_PR_MD_GET_INTSOFF() != 0);
/* Figure out how much time elapsed since the last clock tick */
now = PR_IntervalNow();
elapsed = now - cpu->last_clock;
cpu->last_clock = now;
PR_LOG(_pr_clock_lm, PR_LOG_MAX,
("ExpireWaits: elapsed=%lld usec", elapsed));
while(1) {
_PR_SLEEPQ_LOCK(cpu);
if (_PR_SLEEPQ(cpu).next == &_PR_SLEEPQ(cpu)) {
_PR_SLEEPQ_UNLOCK(cpu);
break;
}
thread = _PR_THREAD_PTR(_PR_SLEEPQ(cpu).next);
PR_ASSERT(thread->cpu == cpu);
if (elapsed < thread->sleep) {
thread->sleep -= elapsed;
_PR_SLEEPQMAX(thread->cpu) -= elapsed;
_PR_SLEEPQ_UNLOCK(cpu);
break;
}
_PR_SLEEPQ_UNLOCK(cpu);
PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));
_PR_THREAD_LOCK(thread);
if (thread->cpu != cpu) {
/*
** The thread was switched to another CPU
** between the time we unlocked the sleep
** queue and the time we acquired the thread
** lock, so it is none of our business now.
*/
_PR_THREAD_UNLOCK(thread);
continue;
}
/*
** Consume this sleeper's amount of elapsed time from the elapsed
** time value. The next remaining piece of elapsed time will be
** available for the next sleeping thread's timer.
*/
_PR_SLEEPQ_LOCK(cpu);
PR_ASSERT(!(thread->flags & _PR_ON_PAUSEQ));
if (thread->flags & _PR_ON_SLEEPQ) {
_PR_DEL_SLEEPQ(thread, PR_FALSE);
elapsed -= thread->sleep;
_PR_SLEEPQ_UNLOCK(cpu);
} else {
/* Thread was already handled; Go get another one */
_PR_SLEEPQ_UNLOCK(cpu);
_PR_THREAD_UNLOCK(thread);
continue;
}
/* Notify the thread waiting on the condition variable */
if (thread->flags & _PR_SUSPENDING) {
PR_ASSERT((thread->state == _PR_IO_WAIT) ||
(thread->state == _PR_COND_WAIT));
/*
** Thread is suspended and its condition timeout
** expired. Transfer thread from sleepQ to suspendQ.
*/
thread->wait.cvar = NULL;
_PR_MISCQ_LOCK(cpu);
thread->state = _PR_SUSPENDED;
_PR_ADD_SUSPENDQ(thread, cpu);
_PR_MISCQ_UNLOCK(cpu);
} else {
if (thread->wait.cvar) {
PRThreadPriority pri;
/* Do work very similar to what _PR_NotifyThread does */
PR_ASSERT( !_PR_IS_NATIVE_THREAD(thread) );
/* Make thread runnable */
pri = thread->priority;
thread->state = _PR_RUNNABLE;
PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
PR_ASSERT(thread->cpu == cpu);
_PR_RUNQ_LOCK(cpu);
_PR_ADD_RUNQ(thread, cpu, pri);
_PR_RUNQ_UNLOCK(cpu);
if (pri > me->priority)
_PR_SET_RESCHED_FLAG();
thread->wait.cvar = NULL;
_PR_MD_WAKEUP_WAITER(thread);
} else if (thread->io_pending == PR_TRUE) {
/* Need to put IO sleeper back on runq */
int pri = thread->priority;
thread->io_suspended = PR_TRUE;
#ifdef WINNT
/*
* For NT, record the cpu on which I/O was issued
* I/O cancellation is done on the same cpu
*/
thread->md.thr_bound_cpu = cpu;
#endif
PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
PR_ASSERT(thread->cpu == cpu);
thread->state = _PR_RUNNABLE;
_PR_RUNQ_LOCK(cpu);
_PR_ADD_RUNQ(thread, cpu, pri);
_PR_RUNQ_UNLOCK(cpu);
}
}
_PR_THREAD_UNLOCK(thread);
}
}
/************************************************************************/
/*
** Create a new condition variable.
** "lock" is the lock to use with the condition variable.
**
** Condition variables are synchronization objects that threads can use
** to wait for some condition to occur.
**
** This may fail if memory is tight or if some operating system resource
** is low.
*/
PR_IMPLEMENT(PRCondVar*) PR_NewCondVar(PRLock *lock)
{
PRCondVar *cvar;
PR_ASSERT(lock != NULL);
cvar = PR_NEWZAP(PRCondVar);
if (cvar) {
#ifdef _PR_GLOBAL_THREADS_ONLY
if(_PR_MD_NEW_CV(&cvar->md)) {
PR_DELETE(cvar);
PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
return NULL;
}
#endif
if (_PR_MD_NEW_LOCK(&(cvar->ilock)) == PR_FAILURE) {
PR_DELETE(cvar);
PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
return NULL;
}
cvar->lock = lock;
PR_INIT_CLIST(&cvar->condQ);
} else {
PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
}
return cvar;
}
/*
** Destroy a condition variable. There must be no thread
** waiting on the condvar. The caller is responsible for guaranteeing
** that the condvar is no longer in use.
**
*/
PR_IMPLEMENT(void) PR_DestroyCondVar(PRCondVar *cvar)
{
PR_ASSERT(cvar->condQ.next == &cvar->condQ);
#ifdef _PR_GLOBAL_THREADS_ONLY
_PR_MD_FREE_CV(&cvar->md);
#endif
_PR_MD_FREE_LOCK(&(cvar->ilock));
PR_DELETE(cvar);
}
/*
** Wait for a notify on the condition variable. Sleep for "tiemout" amount
** of ticks (if "timeout" is zero then the sleep is indefinite). While
** the thread is waiting it unlocks lock. When the wait has
** finished the thread regains control of the condition variable after
** locking the associated lock.
**
** The thread waiting on the condvar will be resumed when the condvar is
** notified (assuming the thread is the next in line to receive the
** notify) or when the timeout elapses.
**
** Returns PR_FAILURE if the caller has not locked the lock associated
** with the condition variable or the thread has been interrupted.
*/
extern PRThread *suspendAllThread;
PR_IMPLEMENT(PRStatus) PR_WaitCondVar(PRCondVar *cvar, PRIntervalTime timeout)
{
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(cvar->lock->owner == me);
PR_ASSERT(me != suspendAllThread);
if (cvar->lock->owner != me) return PR_FAILURE;
return _PR_WaitCondVar(me, cvar, cvar->lock, timeout);
}
/*
** Notify the highest priority thread waiting on the condition
** variable. If a thread is waiting on the condition variable (using
** PR_Wait) then it is awakened and begins waiting on the lock.
*/
PR_IMPLEMENT(PRStatus) PR_NotifyCondVar(PRCondVar *cvar)
{
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(cvar->lock->owner == me);
PR_ASSERT(me != suspendAllThread);
if (cvar->lock->owner != me) return PR_FAILURE;
_PR_NotifyCondVar(cvar, me);
return PR_SUCCESS;
}
/*
** Notify all of the threads waiting on the condition variable. All of
** threads are notified in turn. The highest priority thread will
** probably acquire the lock.
*/
PR_IMPLEMENT(PRStatus) PR_NotifyAllCondVar(PRCondVar *cvar)
{
PRCList *q;
PRIntn is;
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(cvar->lock->owner == me);
if (cvar->lock->owner != me) return PR_FAILURE;
#ifdef _PR_GLOBAL_THREADS_ONLY
_PR_MD_NOTIFYALL_CV(&cvar->md, &cvar->lock->ilock);
return PR_SUCCESS;
#else /* _PR_GLOBAL_THREADS_ONLY */
if ( !_PR_IS_NATIVE_THREAD(me))
_PR_INTSOFF(is);
_PR_CVAR_LOCK(cvar);
q = cvar->condQ.next;
while (q != &cvar->condQ) {
PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("PR_NotifyAll: cvar=%p", cvar));
_PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me);
q = q->next;
}
_PR_CVAR_UNLOCK(cvar);
if (!_PR_IS_NATIVE_THREAD(me))
_PR_INTSON(is);
return PR_SUCCESS;
#endif /* _PR_GLOBAL_THREADS_ONLY */
}
/*********************************************************************/
/*********************************************************************/
/********************ROUTINES FOR DCE EMULATION***********************/
/*********************************************************************/
/*********************************************************************/
#include "prpdce.h"
PR_IMPLEMENT(PRCondVar*) PRP_NewNakedCondVar(void)
{
PRCondVar *cvar = PR_NEWZAP(PRCondVar);
if (NULL != cvar)
{
if (_PR_MD_NEW_LOCK(&(cvar->ilock)) == PR_FAILURE)
{
PR_DELETE(cvar); cvar = NULL;
}
else
{
PR_INIT_CLIST(&cvar->condQ);
cvar->lock = _PR_NAKED_CV_LOCK;
}
}
return cvar;
}
PR_IMPLEMENT(void) PRP_DestroyNakedCondVar(PRCondVar *cvar)
{
PR_ASSERT(cvar->condQ.next == &cvar->condQ);
PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);
_PR_MD_FREE_LOCK(&(cvar->ilock));
PR_DELETE(cvar);
}
PR_IMPLEMENT(PRStatus) PRP_NakedWait(
PRCondVar *cvar, PRLock *lock, PRIntervalTime timeout)
{
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);
return _PR_WaitCondVar(me, cvar, lock, timeout);
} /* PRP_NakedWait */
PR_IMPLEMENT(PRStatus) PRP_NakedNotify(PRCondVar *cvar)
{
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);
_PR_NotifyCondVar(cvar, me);
return PR_SUCCESS;
} /* PRP_NakedNotify */
PR_IMPLEMENT(PRStatus) PRP_NakedBroadcast(PRCondVar *cvar)
{
PRCList *q;
PRIntn is;
PRThread *me = _PR_MD_CURRENT_THREAD();
PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);
if ( !_PR_IS_NATIVE_THREAD(me)) _PR_INTSOFF(is);
_PR_MD_LOCK( &(cvar->ilock) );
q = cvar->condQ.next;
while (q != &cvar->condQ) {
PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("PR_NotifyAll: cvar=%p", cvar));
_PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me);
q = q->next;
}
_PR_MD_UNLOCK( &(cvar->ilock) );
if (!_PR_IS_NATIVE_THREAD(me)) _PR_INTSON(is);
return PR_SUCCESS;
} /* PRP_NakedBroadcast */