condition_variable_win.cc

// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.// Use of this source code is governed by a BSD-style license that can be// found in the LICENSE file.#include "base/condition_variable.h"#include <stack>#include "base/lock.h"#include "base/logging.h"#include "base/time.h"using base::TimeDelta;
ConditionVariable::ConditionVariable(Lock* user_lock)
: user_lock_(*user_lock),
run_state_(RUNNING),
allocation_counter_(0),
recycling_list_size_(0) {
DCHECK(user_lock);
}
ConditionVariable::~ConditionVariable() {
AutoLock auto_lock(internal_lock_);
run_state_ = SHUTDOWN; // Prevent any more waiting.
DCHECK_EQ(recycling_list_size_, allocation_counter_);
if (recycling_list_size_ != allocation_counter_) { // Rare shutdown problem.// There are threads of execution still in this->TimedWait() and yet the// caller has instigated the destruction of this instance :-/.// A common reason for such "overly hasty" destruction is that the caller// was not willing to wait for all the threads to terminate. Such hasty// actions are a violation of our usage contract, but we'll give the// waiting thread(s) one last chance to exit gracefully (prior to our// destruction).// Note: waiting_list_ *might* be empty, but recycling is still pending.
AutoUnlock auto_unlock(internal_lock_);
Broadcast(); // Make sure all waiting threads have been signaled.
Sleep(10); // Give threads a chance to grab internal_lock_.// All contained threads should be blocked on user_lock_ by now :-).
} // Reacquire internal_lock_.
DCHECK_EQ(recycling_list_size_, allocation_counter_);
}
void ConditionVariable::Wait() {
// Default to "wait forever" timing, which means have to get a Signal()// or Broadcast() to come out of this wait state.
TimedWait(TimeDelta::FromMilliseconds(INFINITE));
}
void ConditionVariable::TimedWait(const TimeDelta& max_time) {
Event* waiting_event;
HANDLE handle;
{
AutoLock auto_lock(internal_lock_);
if (RUNNING != run_state_) return; // Destruction in progress.
waiting_event = GetEventForWaiting();
handle = waiting_event->handle();
DCHECK(handle);
} // Release internal_lock.
{
AutoUnlock unlock(user_lock_); // Release caller's lock
WaitForSingleObject(handle, static_cast<DWORD>(max_time.InMilliseconds()));
// Minimize spurious signal creation window by recycling asap.
AutoLock auto_lock(internal_lock_);
RecycleEvent(waiting_event);
// Release internal_lock_
} // Reacquire callers lock to depth at entry.
}
// Broadcast() is guaranteed to signal all threads that were waiting (i.e., had// a cv_event internally allocated for them) before Broadcast() was called.void ConditionVariable::Broadcast() {
std::stack<HANDLE> handles; // See FAQ-question-10.
{
AutoLock auto_lock(internal_lock_);
if (waiting_list_.IsEmpty())
return;
while (!waiting_list_.IsEmpty())
// This is not a leak from waiting_list_. See FAQ-question 12.
handles.push(waiting_list_.PopBack()->handle());
} // Release internal_lock_.while (!handles.empty()) {
SetEvent(handles.top());
handles.pop();
}
}
// Signal() will select one of the waiting threads, and signal it (signal its// cv_event). For better performance we signal the thread that went to sleep// most recently (LIFO). If we want fairness, then we wake the thread that has// been sleeping the longest (FIFO).void ConditionVariable::Signal() {
HANDLE handle;
{
AutoLock auto_lock(internal_lock_);
if (waiting_list_.IsEmpty())
return; // No one to signal.// Only performance option should be used.// This is not a leak from waiting_list. See FAQ-question 12.
handle = waiting_list_.PopBack()->handle(); // LIFO.
} // Release internal_lock_.
SetEvent(handle);
}
// GetEventForWaiting() provides a unique cv_event for any caller that needs to// wait. This means that (worst case) we may over time create as many cv_event// objects as there are threads simultaneously using this instance's Wait()// functionality.
ConditionVariable::Event* ConditionVariable::GetEventForWaiting() {
// We hold internal_lock, courtesy of Wait().
Event* cv_event;
if (0 == recycling_list_size_) {
DCHECK(recycling_list_.IsEmpty());
cv_event = new Event();
cv_event->InitListElement();
allocation_counter_++;
CHECK(cv_event->handle());
} else {
cv_event = recycling_list_.PopFront();
recycling_list_size_--;
}
waiting_list_.PushBack(cv_event);
return cv_event;
}
// RecycleEvent() takes a cv_event that was previously used for Wait()ing, and// recycles it for use in future Wait() calls for this or other threads.// Note that there is a tiny chance that the cv_event is still signaled when we// obtain it, and that can cause spurious signals (if/when we re-use the// cv_event), but such is quite rare (see FAQ-question-5).void ConditionVariable::RecycleEvent(Event* used_event) {
// We hold internal_lock, courtesy of Wait().// If the cv_event timed out, then it is necessary to remove it from// waiting_list_. If it was selected by Broadcast() or Signal(), then it is// already gone.
used_event->Extract(); // Possibly redundant
recycling_list_.PushBack(used_event);
recycling_list_size_++;
}
//------------------------------------------------------------------------------// The next section provides the implementation for the private Event class.//------------------------------------------------------------------------------// Event provides a doubly-linked-list of events for use exclusively by the// ConditionVariable class.// This custom container was crafted because no simple combination of STL// classes appeared to support the functionality required. The specific// unusual requirement for a linked-list-class is support for the Extract()// method, which can remove an element from a list, potentially for insertion// into a second list. Most critically, the Extract() method is idempotent,// turning the indicated element into an extracted singleton whether it was// contained in a list or not. This functionality allows one (or more) of// threads to do the extraction. The iterator that identifies this extractable// element (in this case, a pointer to the list element) can be used after// arbitrary manipulation of the (possibly) enclosing list container. In// general, STL containers do not provide iterators that can be used across// modifications (insertions/extractions) of the enclosing containers, and// certainly don't provide iterators that can be used if the identified// element is *deleted* (removed) from the container.// It is possible to use multiple redundant containers, such as an STL list,// and an STL map, to achieve similar container semantics. This container has// only O(1) methods, while the corresponding (multiple) STL container approach// would have more complex O(log(N)) methods (yeah... N isn't that large).// Multiple containers also makes correctness more difficult to assert, as// data is redundantly stored and maintained, which is generally evil.
ConditionVariable::Event::Event() : handle_(0) {
next_ = prev_ = this; // Self referencing circular.
}
ConditionVariable::Event::~Event() {
if (0 == handle_) {
// This is the list holderwhile (!IsEmpty()) {
Event* cv_event = PopFront();
DCHECK(cv_event->ValidateAsItem());
delete cv_event;
}
}
DCHECK(IsSingleton());
if (0 != handle_) {
int ret_val = CloseHandle(handle_);
DCHECK(ret_val);
}
}
// Change a container instance permanently into an element of a list.void ConditionVariable::Event::InitListElement() {
DCHECK(!handle_);
handle_ = CreateEvent(NULL, false, false, NULL);
CHECK(handle_);
}
// Methods for use on lists.bool ConditionVariable::Event::IsEmpty() const {
DCHECK(ValidateAsList());
return IsSingleton();
}
void ConditionVariable::Event::PushBack(Event* other) {
DCHECK(ValidateAsList());
DCHECK(other->ValidateAsItem());
DCHECK(other->IsSingleton());
// Prepare other for insertion.
other->prev_ = prev_;
other->next_ = this;
// Cut into list.
prev_->next_ = other;
prev_ = other;
DCHECK(ValidateAsDistinct(other));
}
ConditionVariable::Event* ConditionVariable::Event::PopFront() {
DCHECK(ValidateAsList());
DCHECK(!IsSingleton());
return next_->Extract();
}
ConditionVariable::Event* ConditionVariable::Event::PopBack() {
DCHECK(ValidateAsList());
DCHECK(!IsSingleton());
return prev_->Extract();
}
// Methods for use on list elements.// Accessor method.
HANDLE ConditionVariable::Event::handle() const {
DCHECK(ValidateAsItem());
return handle_;
}
// Pull an element from a list (if it's in one).
ConditionVariable::Event* ConditionVariable::Event::Extract() {
DCHECK(ValidateAsItem());
if (!IsSingleton()) {
// Stitch neighbors together.
next_->prev_ = prev_;
prev_->next_ = next_;
// Make extractee into a singleton.
prev_ = next_ = this;
}
DCHECK(IsSingleton());
returnthis;
}
// Method for use on a list element or on a list.bool ConditionVariable::Event::IsSingleton() const {
DCHECK(ValidateLinks());
return next_ == this;
}
// Provide pre/post conditions to validate correct manipulations.bool ConditionVariable::Event::ValidateAsDistinct(Event* other) const {
return ValidateLinks() && other->ValidateLinks() && (this != other);
}
bool ConditionVariable::Event::ValidateAsItem() const {
return (0 != handle_) && ValidateLinks();
}
bool ConditionVariable::Event::ValidateAsList() const {
return (0 == handle_) && ValidateLinks();
}
bool ConditionVariable::Event::ValidateLinks() const {
// Make sure both of our neighbors have links that point back to us.// We don't do the O(n) check and traverse the whole loop, and instead only// do a local check to (and returning from) our immediate neighbors.return (next_->prev_ == this) && (prev_->next_ == this);
}
/*FAQ On subtle implementation details:1) What makes this problem subtle? Please take a look at "Strategiesfor Implementing POSIX Condition Variables on Win32" by DouglasC. Schmidt and Irfan Pyarali.http://www.cs.wustl.edu/~schmidt/win32-cv-1.html It includesdiscussions of numerous flawed strategies for implementing thisfunctionality. I'm not convinced that even the final proposedimplementation has semantics that are as nice as this implementation(especially with regard to Broadcast() and the impact on threads thattry to Wait() after a Broadcast() has been called, but before all theoriginal waiting threads have been signaled).2) Why can't you use a single wait_event for all threads that callWait()? See FAQ-question-1, or consider the following: If a singleevent were used, then numerous threads calling Wait() could releasetheir cs locks, and be preempted just before callingWaitForSingleObject(). If a call to Broadcast() was then presented ona second thread, it would be impossible to actually signal allwaiting(?) threads. Some number of SetEvent() calls *could* be made,but there could be no guarantee that those led to to more than onesignaled thread (SetEvent()'s may be discarded after the first!), andthere could be no guarantee that the SetEvent() calls didn't justawaken "other" threads that hadn't even started waiting yet (oops).Without any limit on the number of requisite SetEvent() calls, thesystem would be forced to do many such calls, allowing many new waitsto receive spurious signals.3) How does this implementation cause spurious signal events? Thecause in this implementation involves a race between a signal viatime-out and a signal via Signal() or Broadcast(). The series ofactions leading to this are:a) Timer fires, and a waiting thread exits the line of code: WaitForSingleObject(waiting_event, max_time.InMilliseconds());b) That thread (in (a)) is randomly pre-empted after the above line,leaving the waiting_event reset (unsignaled) and still in thewaiting_list_.c) A call to Signal() (or Broadcast()) on a second thread proceeds, andselects the waiting cv_event (identified in step (b)) as the event to revivevia a call to SetEvent().d) The Signal() method (step c) calls SetEvent() on waiting_event (step b).e) The waiting cv_event (step b) is now signaled, but no thread iswaiting on it.f) When that waiting_event (step b) is reused, it will immediatelybe signaled (spuriously).4) Why do you recycle events, and cause spurious signals? First off,the spurious events are very rare. They can only (I think) appearwhen the race described in FAQ-question-3 takes place. This should bevery rare. Most(?) uses will involve only timer expiration, or onlySignal/Broadcast() actions. When both are used, it will be rare thatthe race will appear, and it would require MANY Wait() and signalingactivities. If this implementation did not recycle events, then itwould have to create and destroy events for every call to Wait().That allocation/deallocation and associated construction/destructionwould be costly (per wait), and would only be a rare benefit (when therace was "lost" and a spurious signal took place). That would be bad(IMO) optimization trade-off. Finally, such spurious events areallowed by the specification of condition variables (such asimplemented in Vista), and hence it is better if any user accommodatessuch spurious events (see usage note in condition_variable.h).5) Why don't you reset events when you are about to recycle them, orabout to reuse them, so that the spurious signals don't take place?The thread described in FAQ-question-3 step c may be pre-empted for anarbitrary length of time before proceeding to step d. As a result,the wait_event may actually be re-used *before* step (e) is reached.As a result, calling reset would not help significantly.6) How is it that the callers lock is released atomically with theentry into a wait state? We commit to the wait activity when weallocate the wait_event for use in a given call to Wait(). Thisallocation takes place before the caller's lock is released (andactually before our internal_lock_ is released). That allocation isthe defining moment when "the wait state has been entered," as thatthread *can* now be signaled by a call to Broadcast() or Signal().Hence we actually "commit to wait" before releasing the lock, makingthe pair effectively atomic.8) Why do you need to lock your data structures during waiting, as thecaller is already in possession of a lock? We need to Acquire() andRelease() our internal lock during Signal() and Broadcast(). If we triedto use a callers lock for this purpose, we might conflict with theirexternal use of the lock. For example, the caller may use to consistentlyhold a lock on one thread while calling Signal() on another, and that wouldblock Signal().9) Couldn't a more efficient implementation be provided if youpreclude using more than one external lock in conjunction with asingle ConditionVariable instance? Yes, at least it could be viewedas a simpler API (since you don't have to reiterate the lock argumentin each Wait() call). One of the constructors now takes a specificlock as an argument, and a there are corresponding Wait() calls thatdon't specify a lock now. It turns that the resulting implmentationcan't be made more efficient, as the internal lock needs to be used bySignal() and Broadcast(), to access internal data structures. As aresult, I was not able to utilize the user supplied lock (which isbeing used by the user elsewhere presumably) to protect the privatemember access.9) Since you have a second lock, how can be be sure that there is nopossible deadlock scenario? Our internal_lock_ is always the lastlock acquired, and the first one released, and hence a deadlock (dueto critical section problems) is impossible as a consequence of ourlock.10) When doing a Broadcast(), why did you copy all the events intoan STL queue, rather than making a linked-loop, and iterating over it?The iterating during Broadcast() is done so outside the protectionof the internal lock. As a result, other threads, such as the threadwherein a related event is waiting, could asynchronously manipulatethe links around a cv_event. As a result, the link structure cannotbe used outside a lock. Broadcast() could iterate over waitingevents by cycling in-and-out of the protection of the internal_lock,but that appears more expensive than copying the list into an STLstack.11) Why did the lock.h file need to be modified so much for thischange? Central to a Condition Variable is the atomic release of alock during a Wait(). This places Wait() functionality exactlymid-way between the two classes, Lock and Condition Variable. Giventhat there can be nested Acquire()'s of locks, and Wait() had toRelease() completely a held lock, it was necessary to augment the Lockclass with a recursion counter. Even more subtle is the fact that therecursion counter (in a Lock) must be protected, as many threads canaccess it asynchronously. As a positive fallout of this, there arenow some DCHECKS to be sure no one Release()s a Lock more than theyAcquire()ed it, and there is ifdef'ed functionality that can detectnested locks (legal under windows, but not under Posix).12) Why is it that the cv_events removed from list in Broadcast() and Signal()are not leaked? How are they recovered?? The cv_events that appear to leak aretaken from the waiting_list_. For each element in that list, there is currentlya thread in or around the WaitForSingleObject() call of Wait(), and thosethreads have references to these otherwise leaked events. They are passed asarguments to be recycled just aftre returning from WaitForSingleObject().13) Why did you use a custom container class (the linked list), when STL hasperfectly good containers, such as an STL list? The STL list, as with anycontainer, does not guarantee the utility of an iterator across manipulation(such as insertions and deletions) of the underlying container. The customdouble-linked-list container provided that assurance. I don't believe anycombination of STL containers provided the services that were needed at the sameO(1) efficiency as the custom linked list. The unusual requirementfor the container class is that a reference to an item within a container (aniterator) needed to be maintained across an arbitrary manipulation of thecontainer. This requirement exposes itself in the Wait() method, where awaiting_event must be selected prior to the WaitForSingleObject(), and then itmust be used as part of recycling to remove the related instance from thewaiting_list. A hash table (STL map) could be used, but I was embarrased touse a complex and relatively low efficiency container when a doubly linked listprovided O(1) performance in all required operations. Since other operationsto provide performance-and/or-fairness required queue (FIFO) and list (LIFO)containers, I would also have needed to use an STL list/queue as well as an STLmap. In the end I decided it would be "fun" to just do it right, and Iput so many assertions (DCHECKs) into the container class that it is trivial tocode review and validate its correctness.*/