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