/** * Puts work item to the global pool or deallocates it. Also deallocates * the events associated with the work item. NOTE: this code is designed * to be efficient, not compact */ STATIC void WKQ_ReleaseWorkItem(WorkQueueModule * module, WorkItem * w) { Bool locked = False; ASSERT(module->initcount > 0); ASSERT(!w->submitQ.queue); ASSERT(!w->itemsQ.queue); /* deallocate waiters */ while (w->waiters) { Waiter * waiter = w->waiters; Waiter * next = waiter->next; if (module->nwait < module->maxwait) { if (locked) { WKQ_WaiterToPool(module, waiter); waiter = NULL; } else { locked = MUTEX_Lock(&module->mutex); if (module->nwait < module->maxwait) { WKQ_WaiterToPool(module, waiter); waiter = NULL; } } } if (waiter) { EVENT_Destroy(&waiter->event); MEM_Free(waiter); } w->waiters = next; } if (QUEUE_Size(&module->itempool) < module->maxitems) { if (locked) { w->flags = WKI_DETACHED; QUEUE_InsertTail(&module->itempool, &w->itemsQ); } else { locked = MUTEX_Lock(&module->mutex); if (QUEUE_Size(&module->itempool) < module->maxitems) { w->flags = WKI_DETACHED; QUEUE_InsertTail(&module->itempool, &w->itemsQ); } else { MEM_Free(w); } } } else { MEM_Free(w); } if (locked) { MUTEX_Unlock(&module->mutex); } }
/** * Moves all the entries from the source queue to the tail of the destination * queue. DOES NOT remove the entries that are already in the destination * queue. Returns number of moved entries. */ int QUEUE_Move(Queue * dest, Queue * src) { int n = 0; if (src && src != dest && QUEUE_Size(src) > 0) { QEntry * e; while ((e = QUEUE_RemoveHead(src)) != NULL) { QUEUE_InsertTail(dest, e); n++; } ASSERT(QUEUE_Size(src) == 0); } return n; }
/** * Puts RWLockWaiter structure into the cache or deallocates it. This is * enough for a shared waiter but not for exclusive waiter (see function * below). Must be used under synchronization. */ STATIC void RWLOCK_ReleaseWaiter(RWLock * lock, RWLockWaiter * w) { QUEUE_RemoveEntry(&w->entry); if (QUEUE_Size(&lock->waiterCache) < MAX_WAITER_CACHE_SIZE) { w->index = 0; w->event = NULL; QUEUE_InsertTail(&lock->waiterCache, &w->entry); } else { MEM_Free(w); } }
/** * Waits until all work items associated with this work queue are done ane * deallocates the work queue. */ void WKQ_Delete(WorkQueue * q) { if (q) { WKQ_Stop(q, True); if (q->flags & WKQ_KILLME) { ASSERT(QUEUE_Size(&q->items) == 1); } else { ASSERT(QUEUE_IsEmpty(&q->items)); WKQ_Free(q); } } }
/** * Locks resource for non-exclusive use, waits if necessary. Returns True * if lock has been successfully acquired, otherwise False. */ Bool RWLOCK_TimeReadLock(RWLock * lock, long ms) { Bool ok = True; Bool success = False; RWLockWaiter * waiter = NULL; Time deadline = 0; /* * this flag is False if we have found that current thread is NOT * an owner of the resource, so that we don't scan the lock entries * more than once. */ Bool maybeOwner = True; RWEntry * entry = NULL; /* calculate the deadline if it's a wait with timeout */ if (ms > 0) { deadline = TIME_Now() + ms; } MUTEX_Lock(&lock->mutex); while (ok) { Time now = 0; /* * if this thread already owns this resource either exclusively * or shared, we are all set. All we need is to increment entry * count. NOTE that we don't touch the "exclusive" flag, meaning * that if resource has been acquired exclusively, it remains * this way. */ if (maybeOwner) { entry = RWLOCK_FindEntry(lock); if (entry) { success = True; if (lock->flags & RWLOCK_FLAG_EXCLUSIVE_LOCK) { ASSERT(entry->write > 0); entry->write++; } else { ASSERT(entry->write == 0); entry->read++; } break; } else { maybeOwner = False; /* don't scan entry table again */ } } /* if resource is not owned and no one is waiting, we can have it */ if (lock->locks <= 0 && QUEUE_Size(&lock->shareWaiters) == 0 && QUEUE_Size(&lock->exclusiveWaiters) == 0) { /* * note that it's quite possible that resource is not owned * but the wait queue is not empty. this can happen for example * if this thread just released the resource and waiters didn't * yet have the chance to run. in such case, this thread should * be place into the queue to avoid starving the waiters */ entry = RWLOCK_GetEntry(lock); if (entry) { success = True; lock->flags &= ~RWLOCK_FLAG_EXCLUSIVE_LOCK; entry->read++; } break; } /* * if resource is owned in shared mode, there's a good chance that * we can have it immediately. Some restrictions apply (see below) */ if (!(lock->flags & RWLOCK_FLAG_EXCLUSIVE_LOCK)) { /* * normally we allow this thread to access the resource * in readonly mode even if there's an exclusive waiter. * However, if we always did that, the exclusive waiter * might end up waiting forever if new readonly waters * keep coming. To prevent this from happening, we count * the number of times an exclusive waiter has been bypassed * by a lucky late-coming reader. If this number exceeds * the limit, everyone has to stay in the line. */ if (QUEUE_Size(&lock->exclusiveWaiters) == 0 || lock->bypassCount < RWLOCK_MAX_BYPASS_COUNT) { entry = RWLOCK_GetEntry(lock); if (entry) { if (QUEUE_Size(&lock->exclusiveWaiters) > 0) { lock->bypassCount++; } ASSERT(entry->write == 0); success = True; entry->read++; } break; } } /* * resource cannot be acquired immediately for exclusive access. * If we cannot wait (any longer), break the loop. */ if (ms == 0) { break; } else if (ms > 0) { /* check for timeout */ now = TIME_Now(); if (now >= deadline) { break; } } /* * release the mutex and wait for event to be signalled, then * start it all over again. */ lock->contentions++; if (!waiter) { waiter = RWLOCK_GetShareWaiter(lock); if (!waiter) break; } EVENT_Reset(&lock->shareEvent); MUTEX_Unlock(&lock->mutex); /* wait */ if (ms > 0) { long tmo = (long)(deadline - now); if (EVENT_TimeWait(waiter->event, tmo) == WAIT_STATE_ERROR) { ok = False; } } else { ok = BoolValue(EVENT_Wait(waiter->event) == WAIT_STATE_OK); } MUTEX_Lock(&lock->mutex); } if (success) lock->locks++; if (waiter) RWLOCK_ReleaseWaiter(lock, waiter); MUTEX_Unlock(&lock->mutex); return success; }
/** * Locks resource for exclusive use, waits if necessary. Returns True if lock * has been successfully acquired, otherwise False. */ Bool RWLOCK_TimeWriteLock(RWLock * lock, long ms) { Bool ok = True; Bool success = False; RWLockWaiter * waiter = NULL; Time deadline = 0; /* * this flag is False if we have found that current thread is NOT * an owner of the resource, so that we don't scan the lock entries * more than once. */ Bool maybeOwner = True; RWEntry * entry = NULL; /* calculate the deadline if it's a wait with timeout */ if (ms > 0) { deadline = TIME_Now() + ms; } /* * we can acquire the resource immediately if * 1. resource is unowned and no one is waiting; or * 2. this thread is the only one that is using the resource, either * shared or exclusively */ MUTEX_Lock(&lock->mutex); while (ok) { Time now = 0; /* * if this thread already owns this resource exclusively, * we are all set. All we need is to increment entry count. */ if (lock->entriesActive == 1 && maybeOwner) { if (!entry) { entry = RWLOCK_FindEntry(lock); } if (entry) { success = True; lock->flags |= RWLOCK_FLAG_EXCLUSIVE_LOCK; entry->write++; /* convert shared to exclusive */ break; } else { maybeOwner = False; } } /* if resource is not owned and no one is waiting, we can have it */ if (lock->locks <= 0) { Bool gotIt = False; if (waiter) { gotIt = BoolValue(lock->exclusiveWaiters.head.next == &waiter->entry); } else { gotIt = BoolValue(QUEUE_Size(&lock->shareWaiters) == 0 && QUEUE_Size(&lock->exclusiveWaiters) == 0); } /* * note that it's quite possible that resource is not owned * but the wait queue is not empty. this can happen for example * if this thread just released the resource and waiters didn't * yet have the chance to run. in such case, this thread should * be place into the queue to avoid starving the waiters */ if (gotIt) { if (!entry) { entry = RWLOCK_GetEntry(lock); } if (entry) { success = True; lock->flags |= RWLOCK_FLAG_EXCLUSIVE_LOCK; entry->write++; } break; } } /* * resource cannot be acquired immediately for exclusive access. * If we cannot wait (any longer), break the loop. */ if (ms == 0) { break; } else if (ms > 0) { /* check for timeout */ now = TIME_Now(); if (now >= deadline) { break; } } /* * release the mutex and wait for event to be signaled, then * start it all over again. */ lock->contentions++; if (!waiter) { waiter = RWLOCK_GetExclusiveWaiter(lock); if (!waiter) break; } EVENT_Reset(waiter->event); MUTEX_Unlock(&lock->mutex); /* wait */ if (ms > 0) { long tmo = (long)(deadline - now); if (EVENT_TimeWait(waiter->event,tmo) == WAIT_STATE_ERROR) { ok = False; } } else { ok = BoolValue(EVENT_Wait(waiter->event) == WAIT_STATE_OK); } MUTEX_Lock(&lock->mutex); } if (success) lock->locks++; if (waiter) RWLOCK_ReleaseExclusiveWaiter(lock, waiter); lock->bypassCount = 0; MUTEX_Unlock(&lock->mutex); return success; }