/** * Returns the current state, EVENT_SIGNALED or EVENT_NOT_SIGNALED, * of a given event object. */ EventState EVENT_State(Event * e) { if (EVENT_TimeWait(e,0) == WAIT_STATE_OK) { return EVENT_SIGNALED; } else { return EVENT_NOT_SIGNALED; } }
/** * Waits for the work item to complete */ WaitState WKI_TimeWait(WorkItem * w, long ms) { WaitState result = WAIT_STATE_OK; Waiter * waiter = WKI_AttachWaiter(w); if (waiter) { result = EVENT_TimeWait(&waiter->event,ms); WKQ_ReleaseWaiter(&WKQ, waiter); } return result; }
/** * Waits for this work queue to stop */ WaitState WKQ_TimeWait(WorkQueue * q, long ms) { return EVENT_TimeWait(&q->stopEvent, ms); }
/** * The worker thread */ STATIC void WKQ_Thread(void * par) { WorkQueue * q = (WorkQueue *)par; TRACE("WKQ: starting\n"); /* start the loop */ MUTEX_Lock(&q->mutex); q->lastActivity = TIME_Now(); while ((q->flags & WKQ_ACTIVE) || !QUEUE_IsEmpty(&q->submit)) { QEntry * e; while ((e = QUEUE_RemoveHead(&q->submit)) != NULL) { WorkItem * w = QCAST(e,WorkItem,submitQ); ASSERT(!(w->flags & (WKI_DONE|WKI_CANCELED))); /* * NULL callback may be used by dummy work items whose purpose * is to wait until all pending work items have been processed */ if (w->proc) { /* update flags */ w->flags |= WKI_CALL; /* invoke the handler */ MUTEX_Unlock(&q->mutex); w->proc(w, w->param); MUTEX_Lock(&q->mutex); q->lastActivity = TIME_Now(); if (w->flags & WKI_DETACHED) { /* put the work item to the pool or deallocate it */ ASSERT(!w->waiters); QUEUE_RemoveEntry(&w->itemsQ); WKQ_ReleaseWorkItem(&WKQ, w); } else { /* * update flags. Note that we released the mutex when * were invoking the callback. Therefore, this work * item could be re-submitted to the queue. Or it could * be re-submitted and then canceled. In such cases we * don't need to set the WKI_DONE flag. */ w->flags &= ~WKI_CALL; if (!(w->flags & WKI_CANCELED) && !w->submitQ.queue) { w->flags |= WKI_DONE; } /* signal the events associated with the work item */ WKI_Signal(w); } } else { /* it's a dummy work item. Just release the waiters */ WKI_Signal(w); } } /* wait for a signal */ if (q->flags & WKQ_ACTIVE) { EVENT_Reset(&q->event); if (q->idleProc) { /* we have an idle timeout */ IdleProc idle = q->idleProc; void * param = q->idleParam; Time now = TIME_Now(); Time deadline = q->lastActivity + q->idleTimeout; if (deadline > now) { MUTEX_Unlock(&q->mutex); switch (EVENT_TimeWait(&q->event,(long)(deadline-now))) { case WAIT_STATE_OK: /* don't invoke idle callback */ MUTEX_Lock(&q->mutex); break; case WAIT_STATE_TIMEOUT: /* invoke idle callback */ MUTEX_Lock(&q->mutex); now = TIME_Now(); deadline = q->lastActivity + q->idleTimeout; if (deadline <= now) { MUTEX_Unlock(&q->mutex); q->lastActivity = now; idle(q, param); MUTEX_Lock(&q->mutex); } break; default: case WAIT_STATE_ERROR: /* terminate the thread on error */ MUTEX_Lock(&q->mutex); q->flags &= ~WKQ_ACTIVE; break; } } else { q->lastActivity = now; MUTEX_Unlock(&q->mutex); idle(q, param); MUTEX_Lock(&q->mutex); } } else { /* wait forever */ MUTEX_Unlock(&q->mutex); EVENT_Wait(&q->event); MUTEX_Lock(&q->mutex); } } } /* cleanup */ MUTEX_Unlock(&q->mutex); TRACE("WKQ: done\n"); if (q->flags & WKQ_KILLME) { TRACE1("WKQ: killing WorkQueue %p\n",q); 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; }