michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * os2cv.c -- OS/2 Machine-Dependent Code for Condition Variables michael@0: * michael@0: * We implement our own condition variable wait queue. Each thread michael@0: * has a semaphore object (thread->md.blocked_sema) to block on while michael@0: * waiting on a condition variable. michael@0: * michael@0: * We use a deferred condition notify algorithm. When PR_NotifyCondVar michael@0: * or PR_NotifyAllCondVar is called, the condition notifies are simply michael@0: * recorded in the _MDLock structure. We defer the condition notifies michael@0: * until right after we unlock the lock. This way the awakened threads michael@0: * have a better chance to reaquire the lock. michael@0: */ michael@0: michael@0: #include "primpl.h" michael@0: michael@0: /* michael@0: * AddThreadToCVWaitQueueInternal -- michael@0: * michael@0: * Add the thread to the end of the condition variable's wait queue. michael@0: * The CV's lock must be locked when this function is called. michael@0: */ michael@0: michael@0: static void michael@0: AddThreadToCVWaitQueueInternal(PRThread *thred, struct _MDCVar *cv) michael@0: { michael@0: PR_ASSERT((cv->waitTail != NULL && cv->waitHead != NULL) michael@0: || (cv->waitTail == NULL && cv->waitHead == NULL)); michael@0: cv->nwait += 1; michael@0: thred->md.inCVWaitQueue = PR_TRUE; michael@0: thred->md.next = NULL; michael@0: thred->md.prev = cv->waitTail; michael@0: if (cv->waitHead == NULL) { michael@0: cv->waitHead = thred; michael@0: } else { michael@0: cv->waitTail->md.next = thred; michael@0: } michael@0: cv->waitTail = thred; michael@0: } michael@0: michael@0: /* michael@0: * md_UnlockAndPostNotifies -- michael@0: * michael@0: * Unlock the lock, and then do the deferred condition notifies. michael@0: * If waitThred and waitCV are not NULL, waitThred is added to michael@0: * the wait queue of waitCV before the lock is unlocked. michael@0: * michael@0: * This function is called by _PR_MD_WAIT_CV and _PR_MD_UNLOCK, michael@0: * the two places where a lock is unlocked. michael@0: */ michael@0: void michael@0: md_UnlockAndPostNotifies( michael@0: _MDLock *lock, michael@0: PRThread *waitThred, michael@0: _MDCVar *waitCV) michael@0: { michael@0: PRIntn index; michael@0: _MDNotified post; michael@0: _MDNotified *notified, *prev = NULL; michael@0: michael@0: /* michael@0: * Time to actually notify any conditions that were affected michael@0: * while the lock was held. Get a copy of the list that's in michael@0: * the lock structure and then zero the original. If it's michael@0: * linked to other such structures, we own that storage. michael@0: */ michael@0: post = lock->notified; /* a safe copy; we own the lock */ michael@0: michael@0: #if defined(DEBUG) michael@0: memset(&lock->notified, 0, sizeof(_MDNotified)); /* reset */ michael@0: #else michael@0: lock->notified.length = 0; /* these are really sufficient */ michael@0: lock->notified.link = NULL; michael@0: #endif michael@0: michael@0: /* michael@0: * Figure out how many threads we need to wake up. michael@0: */ michael@0: notified = &post; /* this is where we start */ michael@0: do { michael@0: for (index = 0; index < notified->length; ++index) { michael@0: _MDCVar *cv = notified->cv[index].cv; michael@0: PRThread *thred; michael@0: int i; michael@0: michael@0: /* Fast special case: no waiting threads */ michael@0: if (cv->waitHead == NULL) { michael@0: notified->cv[index].notifyHead = NULL; michael@0: continue; michael@0: } michael@0: michael@0: /* General case */ michael@0: if (-1 == notified->cv[index].times) { michael@0: /* broadcast */ michael@0: thred = cv->waitHead; michael@0: while (thred != NULL) { michael@0: thred->md.inCVWaitQueue = PR_FALSE; michael@0: thred = thred->md.next; michael@0: } michael@0: notified->cv[index].notifyHead = cv->waitHead; michael@0: cv->waitHead = cv->waitTail = NULL; michael@0: cv->nwait = 0; michael@0: } else { michael@0: thred = cv->waitHead; michael@0: i = notified->cv[index].times; michael@0: while (thred != NULL && i > 0) { michael@0: thred->md.inCVWaitQueue = PR_FALSE; michael@0: thred = thred->md.next; michael@0: i--; michael@0: } michael@0: notified->cv[index].notifyHead = cv->waitHead; michael@0: cv->waitHead = thred; michael@0: if (cv->waitHead == NULL) { michael@0: cv->waitTail = NULL; michael@0: } else { michael@0: if (cv->waitHead->md.prev != NULL) { michael@0: cv->waitHead->md.prev->md.next = NULL; michael@0: cv->waitHead->md.prev = NULL; michael@0: } michael@0: } michael@0: cv->nwait -= notified->cv[index].times - i; michael@0: } michael@0: } michael@0: notified = notified->link; michael@0: } while (NULL != notified); michael@0: michael@0: if (waitThred) { michael@0: AddThreadToCVWaitQueueInternal(waitThred, waitCV); michael@0: } michael@0: michael@0: /* Release the lock before notifying */ michael@0: DosReleaseMutexSem(lock->mutex); michael@0: michael@0: notified = &post; /* this is where we start */ michael@0: do { michael@0: for (index = 0; index < notified->length; ++index) { michael@0: PRThread *thred; michael@0: PRThread *next; michael@0: michael@0: thred = notified->cv[index].notifyHead; michael@0: while (thred != NULL) { michael@0: BOOL rv; michael@0: michael@0: next = thred->md.next; michael@0: thred->md.prev = thred->md.next = NULL; michael@0: rv = DosPostEventSem(thred->md.blocked_sema); michael@0: PR_ASSERT(rv == NO_ERROR); michael@0: thred = next; michael@0: } michael@0: } michael@0: prev = notified; michael@0: notified = notified->link; michael@0: if (&post != prev) PR_DELETE(prev); michael@0: } while (NULL != notified); michael@0: } michael@0: michael@0: /* michael@0: * Notifies just get posted to the protecting mutex. The michael@0: * actual notification is done when the lock is released so that michael@0: * MP systems don't contend for a lock that they can't have. michael@0: */ michael@0: static void md_PostNotifyToCvar(_MDCVar *cvar, _MDLock *lock, michael@0: PRBool broadcast) michael@0: { michael@0: PRIntn index = 0; michael@0: _MDNotified *notified = &lock->notified; michael@0: michael@0: while (1) { michael@0: for (index = 0; index < notified->length; ++index) { michael@0: if (notified->cv[index].cv == cvar) { michael@0: if (broadcast) { michael@0: notified->cv[index].times = -1; michael@0: } else if (-1 != notified->cv[index].times) { michael@0: notified->cv[index].times += 1; michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: /* if not full, enter new CV in this array */ michael@0: if (notified->length < _MD_CV_NOTIFIED_LENGTH) break; michael@0: michael@0: /* if there's no link, create an empty array and link it */ michael@0: if (NULL == notified->link) { michael@0: notified->link = PR_NEWZAP(_MDNotified); michael@0: } michael@0: michael@0: notified = notified->link; michael@0: } michael@0: michael@0: /* A brand new entry in the array */ michael@0: notified->cv[index].times = (broadcast) ? -1 : 1; michael@0: notified->cv[index].cv = cvar; michael@0: notified->length += 1; michael@0: } michael@0: michael@0: /* michael@0: * _PR_MD_NEW_CV() -- Creating new condition variable michael@0: * ... Solaris uses cond_init() in similar function. michael@0: * michael@0: * returns: -1 on failure michael@0: * 0 when it succeeds. michael@0: * michael@0: */ michael@0: PRInt32 michael@0: _PR_MD_NEW_CV(_MDCVar *cv) michael@0: { michael@0: cv->magic = _MD_MAGIC_CV; michael@0: /* michael@0: * The waitHead, waitTail, and nwait fields are zeroed michael@0: * when the PRCondVar structure is created. michael@0: */ michael@0: return 0; michael@0: } michael@0: michael@0: void _PR_MD_FREE_CV(_MDCVar *cv) michael@0: { michael@0: cv->magic = (PRUint32)-1; michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * _PR_MD_WAIT_CV() -- Wait on condition variable michael@0: */ michael@0: void michael@0: _PR_MD_WAIT_CV(_MDCVar *cv, _MDLock *lock, PRIntervalTime timeout ) michael@0: { michael@0: PRThread *thred = _PR_MD_CURRENT_THREAD(); michael@0: ULONG rv, count; michael@0: ULONG msecs = (timeout == PR_INTERVAL_NO_TIMEOUT) ? michael@0: SEM_INDEFINITE_WAIT : PR_IntervalToMilliseconds(timeout); michael@0: michael@0: /* michael@0: * If we have pending notifies, post them now. michael@0: */ michael@0: if (0 != lock->notified.length) { michael@0: md_UnlockAndPostNotifies(lock, thred, cv); michael@0: } else { michael@0: AddThreadToCVWaitQueueInternal(thred, cv); michael@0: DosReleaseMutexSem(lock->mutex); michael@0: } michael@0: michael@0: /* Wait for notification or timeout; don't really care which */ michael@0: rv = DosWaitEventSem(thred->md.blocked_sema, msecs); michael@0: if (rv == NO_ERROR) { michael@0: DosResetEventSem(thred->md.blocked_sema, &count); michael@0: } michael@0: michael@0: DosRequestMutexSem((lock->mutex), SEM_INDEFINITE_WAIT); michael@0: michael@0: PR_ASSERT(rv == NO_ERROR || rv == ERROR_TIMEOUT); michael@0: michael@0: if(rv == ERROR_TIMEOUT) michael@0: { michael@0: if (thred->md.inCVWaitQueue) { michael@0: PR_ASSERT((cv->waitTail != NULL && cv->waitHead != NULL) michael@0: || (cv->waitTail == NULL && cv->waitHead == NULL)); michael@0: cv->nwait -= 1; michael@0: thred->md.inCVWaitQueue = PR_FALSE; michael@0: if (cv->waitHead == thred) { michael@0: cv->waitHead = thred->md.next; michael@0: if (cv->waitHead == NULL) { michael@0: cv->waitTail = NULL; michael@0: } else { michael@0: cv->waitHead->md.prev = NULL; michael@0: } michael@0: } else { michael@0: PR_ASSERT(thred->md.prev != NULL); michael@0: thred->md.prev->md.next = thred->md.next; michael@0: if (thred->md.next != NULL) { michael@0: thred->md.next->md.prev = thred->md.prev; michael@0: } else { michael@0: PR_ASSERT(cv->waitTail == thred); michael@0: cv->waitTail = thred->md.prev; michael@0: } michael@0: } michael@0: thred->md.next = thred->md.prev = NULL; michael@0: } else { michael@0: /* michael@0: * This thread must have been notified, but the michael@0: * SemRelease call happens after SemRequest michael@0: * times out. Wait on the semaphore again to make it michael@0: * non-signaled. We assume this wait won't take long. michael@0: */ michael@0: rv = DosWaitEventSem(thred->md.blocked_sema, SEM_INDEFINITE_WAIT); michael@0: if (rv == NO_ERROR) { michael@0: DosResetEventSem(thred->md.blocked_sema, &count); michael@0: } michael@0: PR_ASSERT(rv == NO_ERROR); michael@0: } michael@0: } michael@0: PR_ASSERT(thred->md.inCVWaitQueue == PR_FALSE); michael@0: return; michael@0: } /* --- end _PR_MD_WAIT_CV() --- */ michael@0: michael@0: void michael@0: _PR_MD_NOTIFY_CV(_MDCVar *cv, _MDLock *lock) michael@0: { michael@0: md_PostNotifyToCvar(cv, lock, PR_FALSE); michael@0: return; michael@0: } michael@0: michael@0: PRStatus michael@0: _PR_MD_NEW_LOCK(_MDLock *lock) michael@0: { michael@0: DosCreateMutexSem(0, &(lock->mutex), 0, 0); michael@0: (lock)->notified.length=0; michael@0: (lock)->notified.link=NULL; michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: void michael@0: _PR_MD_NOTIFYALL_CV(_MDCVar *cv, _MDLock *lock) michael@0: { michael@0: md_PostNotifyToCvar(cv, lock, PR_TRUE); michael@0: return; michael@0: } michael@0: michael@0: void _PR_MD_UNLOCK(_MDLock *lock) michael@0: { michael@0: if (0 != lock->notified.length) { michael@0: md_UnlockAndPostNotifies(lock, NULL, NULL); michael@0: } else { michael@0: DosReleaseMutexSem(lock->mutex); michael@0: } michael@0: }