/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "primpl.h" #include "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; cvar = PR_NEWZAP(PRCondVar); if (cvar) { if (_PR_InitCondVar(cvar, lock) != PR_SUCCESS) { PR_DELETE(cvar); return NULL; } } else { PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); } return cvar; } PRStatus _PR_InitCondVar(PRCondVar *cvar, PRLock *lock) { PR_ASSERT(lock != NULL); #ifdef _PR_GLOBAL_THREADS_ONLY if(_PR_MD_NEW_CV(&cvar->md)) { PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0); return PR_FAILURE; } #endif if (_PR_MD_NEW_LOCK(&(cvar->ilock)) != PR_SUCCESS) { PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0); return PR_FAILURE; } cvar->lock = lock; PR_INIT_CLIST(&cvar->condQ); return PR_SUCCESS; } /* ** 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_FreeCondVar(cvar); PR_DELETE(cvar); } void _PR_FreeCondVar(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)); } /* ** 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 */