Commit c891ab96 authored by agl@chromium.org's avatar agl@chromium.org

POSIX: allow WaitableEvents to be deleted while watching them.

On Windows, one can close a HANDLE which is currently being waited on. The MSDN
documentation says that the resulting behaviour is 'undefined', but it doesn't
crash. Currently, on POSIX, one couldn't use WaitableEventWatcher to watch an
event which gets deleted. This mismatch has bitten us several times now.

This patch allows WaitableEvents to be deleted while a WaitableEventWatcher is
still watching them. It applies only to watchers, the usual Wait() and
WaitMany() calls still require that all their target be valid until the end of
the call.

http://crbug.com/8809

Review URL: http://codereview.chromium.org/53026


git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12576 0039d316-1c4b-4281-b951-d872f2087c98
parent 546dc8d9
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <utility> #include <utility>
#include "base/condition_variable.h" #include "base/condition_variable.h"
#include "base/lock.h" #include "base/lock.h"
#include "base/ref_counted.h"
#endif #endif
#include "base/message_loop.h" #include "base/message_loop.h"
...@@ -60,8 +61,6 @@ class WaitableEvent { ...@@ -60,8 +61,6 @@ class WaitableEvent {
HANDLE Release(); HANDLE Release();
#endif #endif
// WARNING: Destroying a WaitableEvent while threads are waiting on it is not
// supported. Doing so will cause crashes or other instability.
~WaitableEvent(); ~WaitableEvent();
// Put the event in the un-signaled state. // Put the event in the un-signaled state.
...@@ -93,6 +92,9 @@ class WaitableEvent { ...@@ -93,6 +92,9 @@ class WaitableEvent {
// count: the number of elements in @waitables // count: the number of elements in @waitables
// //
// returns: the index of a WaitableEvent which has been signaled. // returns: the index of a WaitableEvent which has been signaled.
//
// You MUST NOT delete any of the WaitableEvent objects while this wait is
// happening.
static size_t WaitMany(WaitableEvent** waitables, size_t count); static size_t WaitMany(WaitableEvent** waitables, size_t count);
// For asynchronous waiting, see WaitableEventWatcher // For asynchronous waiting, see WaitableEventWatcher
...@@ -130,10 +132,35 @@ class WaitableEvent { ...@@ -130,10 +132,35 @@ class WaitableEvent {
#if defined(OS_WIN) #if defined(OS_WIN)
HANDLE handle_; HANDLE handle_;
#else #else
// On Windows, one can close a HANDLE which is currently being waited on. The
// MSDN documentation says that the resulting behaviour is 'undefined', but
// it doesn't crash. However, if we were to include the following members
// directly then, on POSIX, one couldn't use WaitableEventWatcher to watch an
// event which gets deleted. This mismatch has bitten us several times now,
// so we have a kernel of the WaitableEvent, which is reference counted.
// WaitableEventWatchers may then take a reference and thus match the Windows
// behaviour.
struct WaitableEventKernel :
public RefCountedThreadSafe<WaitableEventKernel> {
public:
WaitableEventKernel(bool manual_reset, bool initially_signaled)
: manual_reset_(manual_reset),
signaled_(initially_signaled) {
}
bool Dequeue(Waiter* waiter, void* tag);
Lock lock_;
const bool manual_reset_;
bool signaled_;
std::list<Waiter*> waiters_;
};
scoped_refptr<WaitableEventKernel> kernel_;
bool SignalAll(); bool SignalAll();
bool SignalOne(); bool SignalOne();
void Enqueue(Waiter* waiter); void Enqueue(Waiter* waiter);
bool Dequeue(Waiter* waiter, void* tag);
// When dealing with arrays of WaitableEvent*, we want to sort by the address // When dealing with arrays of WaitableEvent*, we want to sort by the address
// of the WaitableEvent in order to have a globally consistent locking order. // of the WaitableEvent in order to have a globally consistent locking order.
...@@ -143,11 +170,6 @@ class WaitableEvent { ...@@ -143,11 +170,6 @@ class WaitableEvent {
typedef std::pair<WaitableEvent*, size_t> WaiterAndIndex; typedef std::pair<WaitableEvent*, size_t> WaiterAndIndex;
static size_t EnqueueMany(WaiterAndIndex* waitables, static size_t EnqueueMany(WaiterAndIndex* waitables,
size_t count, Waiter* waiter); size_t count, Waiter* waiter);
Lock lock_;
bool signaled_;
const bool manual_reset_;
std::list<Waiter*> waiters_;
#endif #endif
DISALLOW_COPY_AND_ASSIGN(WaitableEvent); DISALLOW_COPY_AND_ASSIGN(WaitableEvent);
......
...@@ -35,46 +35,40 @@ namespace base { ...@@ -35,46 +35,40 @@ namespace base {
// This is just an abstract base class for waking the two types of waiters // This is just an abstract base class for waking the two types of waiters
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
WaitableEvent::WaitableEvent(bool manual_reset, bool initially_signaled) WaitableEvent::WaitableEvent(bool manual_reset, bool initially_signaled)
: signaled_(initially_signaled), : kernel_(new WaitableEventKernel(manual_reset, initially_signaled)) {
manual_reset_(manual_reset) {
} }
WaitableEvent::~WaitableEvent() { WaitableEvent::~WaitableEvent() {
if (!waiters_.empty()) {
LOG(ERROR) << "Destroying a WaitableEvent (" << this << ") with "
<< waiters_.size() << " waiters";
NOTREACHED() << "Aborting.";
}
} }
void WaitableEvent::Reset() { void WaitableEvent::Reset() {
AutoLock locked(lock_); AutoLock locked(kernel_->lock_);
signaled_ = false; kernel_->signaled_ = false;
} }
void WaitableEvent::Signal() { void WaitableEvent::Signal() {
AutoLock locked(lock_); AutoLock locked(kernel_->lock_);
if (signaled_) if (kernel_->signaled_)
return; return;
if (manual_reset_) { if (kernel_->manual_reset_) {
SignalAll(); SignalAll();
signaled_ = true; kernel_->signaled_ = true;
} else { } else {
// In the case of auto reset, if no waiters were woken, we remain // In the case of auto reset, if no waiters were woken, we remain
// signaled. // signaled.
if (!SignalOne()) if (!SignalOne())
signaled_ = true; kernel_->signaled_ = true;
} }
} }
bool WaitableEvent::IsSignaled() { bool WaitableEvent::IsSignaled() {
AutoLock locked(lock_); AutoLock locked(kernel_->lock_);
const bool result = signaled_; const bool result = kernel_->signaled_;
if (result && !manual_reset_) if (result && !kernel_->manual_reset_)
signaled_ = false; kernel_->signaled_ = false;
return result; return result;
} }
...@@ -150,15 +144,15 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) { ...@@ -150,15 +144,15 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) {
const Time end_time(Time::Now() + max_time); const Time end_time(Time::Now() + max_time);
const bool finite_time = max_time.ToInternalValue() >= 0; const bool finite_time = max_time.ToInternalValue() >= 0;
lock_.Acquire(); kernel_->lock_.Acquire();
if (signaled_) { if (kernel_->signaled_) {
if (!manual_reset_) { if (!kernel_->manual_reset_) {
// In this case we were signaled when we had no waiters. Now that // In this case we were signaled when we had no waiters. Now that
// someone has waited upon us, we can automatically reset. // someone has waited upon us, we can automatically reset.
signaled_ = false; kernel_->signaled_ = false;
} }
lock_.Release(); kernel_->lock_.Release();
return true; return true;
} }
...@@ -168,7 +162,7 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) { ...@@ -168,7 +162,7 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) {
SyncWaiter sw(&cv, &lock); SyncWaiter sw(&cv, &lock);
Enqueue(&sw); Enqueue(&sw);
lock_.Release(); kernel_->lock_.Release();
// We are violating locking order here by holding the SyncWaiter lock but not // We are violating locking order here by holding the SyncWaiter lock but not
// the WaitableEvent lock. However, this is safe because we don't lock @lock_ // the WaitableEvent lock. However, this is safe because we don't lock @lock_
// again before unlocking it. // again before unlocking it.
...@@ -187,9 +181,9 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) { ...@@ -187,9 +181,9 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) {
sw.Disable(); sw.Disable();
lock.Release(); lock.Release();
lock_.Acquire(); kernel_->lock_.Acquire();
Dequeue(&sw, &sw); kernel_->Dequeue(&sw, &sw);
lock_.Release(); kernel_->lock_.Release();
return return_value; return return_value;
} }
...@@ -261,7 +255,7 @@ size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables, ...@@ -261,7 +255,7 @@ size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables,
lock.Acquire(); lock.Acquire();
// Release the WaitableEvent locks in the reverse order // Release the WaitableEvent locks in the reverse order
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
waitables[count - (1 + i)].first->lock_.Release(); waitables[count - (1 + i)].first->kernel_->lock_.Release();
} }
for (;;) { for (;;) {
...@@ -281,12 +275,12 @@ size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables, ...@@ -281,12 +275,12 @@ size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables,
// remove our SyncWaiter from the wait-list // remove our SyncWaiter from the wait-list
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
if (raw_waitables[i] != signaled_event) { if (raw_waitables[i] != signaled_event) {
raw_waitables[i]->lock_.Acquire(); raw_waitables[i]->kernel_->lock_.Acquire();
// There's no possible ABA issue with the address of the SyncWaiter here // There's no possible ABA issue with the address of the SyncWaiter here
// because it lives on the stack. Thus the tag value is just the pointer // because it lives on the stack. Thus the tag value is just the pointer
// value again. // value again.
raw_waitables[i]->Dequeue(&sw, &sw); raw_waitables[i]->kernel_->Dequeue(&sw, &sw);
raw_waitables[i]->lock_.Release(); raw_waitables[i]->kernel_->lock_.Release();
} else { } else {
signaled_index = i; signaled_index = i;
} }
...@@ -312,17 +306,17 @@ size_t WaitableEvent::EnqueueMany ...@@ -312,17 +306,17 @@ size_t WaitableEvent::EnqueueMany
if (!count) if (!count)
return 0; return 0;
waitables[0].first->lock_.Acquire(); waitables[0].first->kernel_->lock_.Acquire();
if (waitables[0].first->signaled_) { if (waitables[0].first->kernel_->signaled_) {
if (!waitables[0].first->manual_reset_) if (!waitables[0].first->kernel_->manual_reset_)
waitables[0].first->signaled_ = false; waitables[0].first->kernel_->signaled_ = false;
waitables[0].first->lock_.Release(); waitables[0].first->kernel_->lock_.Release();
return count; return count;
} }
const size_t r = EnqueueMany(waitables + 1, count - 1, waiter); const size_t r = EnqueueMany(waitables + 1, count - 1, waiter);
if (r) { if (r) {
waitables[0].first->lock_.Release(); waitables[0].first->kernel_->lock_.Release();
} else { } else {
waitables[0].first->Enqueue(waiter); waitables[0].first->Enqueue(waiter);
} }
...@@ -343,12 +337,12 @@ bool WaitableEvent::SignalAll() { ...@@ -343,12 +337,12 @@ bool WaitableEvent::SignalAll() {
bool signaled_at_least_one = false; bool signaled_at_least_one = false;
for (std::list<Waiter*>::iterator for (std::list<Waiter*>::iterator
i = waiters_.begin(); i != waiters_.end(); ++i) { i = kernel_->waiters_.begin(); i != kernel_->waiters_.end(); ++i) {
if ((*i)->Fire(this)) if ((*i)->Fire(this))
signaled_at_least_one = true; signaled_at_least_one = true;
} }
waiters_.clear(); kernel_->waiters_.clear();
return signaled_at_least_one; return signaled_at_least_one;
} }
...@@ -358,11 +352,11 @@ bool WaitableEvent::SignalAll() { ...@@ -358,11 +352,11 @@ bool WaitableEvent::SignalAll() {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
bool WaitableEvent::SignalOne() { bool WaitableEvent::SignalOne() {
for (;;) { for (;;) {
if (waiters_.empty()) if (kernel_->waiters_.empty())
return false; return false;
const bool r = (*waiters_.begin())->Fire(this); const bool r = (*kernel_->waiters_.begin())->Fire(this);
waiters_.pop_front(); kernel_->waiters_.pop_front();
if (r) if (r)
return true; return true;
} }
...@@ -372,14 +366,14 @@ bool WaitableEvent::SignalOne() { ...@@ -372,14 +366,14 @@ bool WaitableEvent::SignalOne() {
// Add a waiter to the list of those waiting. Called with lock held. // Add a waiter to the list of those waiting. Called with lock held.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void WaitableEvent::Enqueue(Waiter* waiter) { void WaitableEvent::Enqueue(Waiter* waiter) {
waiters_.push_back(waiter); kernel_->waiters_.push_back(waiter);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Remove a waiter from the list of those waiting. Return true if the waiter was // Remove a waiter from the list of those waiting. Return true if the waiter was
// actually removed. Called with lock held. // actually removed. Called with lock held.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool WaitableEvent::Dequeue(Waiter* waiter, void* tag) { bool WaitableEvent::WaitableEventKernel::Dequeue(Waiter* waiter, void* tag) {
for (std::list<Waiter*>::iterator for (std::list<Waiter*>::iterator
i = waiters_.begin(); i != waiters_.end(); ++i) { i = waiters_.begin(); i != waiters_.end(); ++i) {
if (*i == waiter && (*i)->Compare(tag)) { if (*i == waiter && (*i)->Compare(tag)) {
......
...@@ -11,14 +11,15 @@ ...@@ -11,14 +11,15 @@
#include "base/object_watcher.h" #include "base/object_watcher.h"
#else #else
#include "base/message_loop.h" #include "base/message_loop.h"
#include "base/waitable_event.h"
#endif #endif
namespace base { namespace base {
class WaitableEvent;
class Flag; class Flag;
class AsyncWaiter; class AsyncWaiter;
class AsyncCallbackTask; class AsyncCallbackTask;
class WaitableEvent;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// This class provides a way to wait on a WaitableEvent asynchronously. // This class provides a way to wait on a WaitableEvent asynchronously.
...@@ -51,6 +52,9 @@ class AsyncCallbackTask; ...@@ -51,6 +52,9 @@ class AsyncCallbackTask;
// occurs just before a WaitableEventWatcher is deleted. There is currently no // occurs just before a WaitableEventWatcher is deleted. There is currently no
// safe way to stop watching an automatic reset WaitableEvent without possibly // safe way to stop watching an automatic reset WaitableEvent without possibly
// missing a signal. // missing a signal.
//
// NOTE: you /are/ allowed to delete the WaitableEvent while still waiting on
// it with a Watcher. It will act as if the event was never signaled.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class WaitableEventWatcher class WaitableEventWatcher
...@@ -140,6 +144,7 @@ class WaitableEventWatcher ...@@ -140,6 +144,7 @@ class WaitableEventWatcher
scoped_refptr<Flag> cancel_flag_; scoped_refptr<Flag> cancel_flag_;
AsyncWaiter* waiter_; AsyncWaiter* waiter_;
AsyncCallbackTask* callback_task_; AsyncCallbackTask* callback_task_;
scoped_refptr<WaitableEvent::WaitableEventKernel> kernel_;
#endif #endif
}; };
......
...@@ -155,12 +155,13 @@ bool WaitableEventWatcher::StartWatching ...@@ -155,12 +155,13 @@ bool WaitableEventWatcher::StartWatching
cancel_flag_ = new Flag; cancel_flag_ = new Flag;
callback_task_ = new AsyncCallbackTask(cancel_flag_, delegate, event); callback_task_ = new AsyncCallbackTask(cancel_flag_, delegate, event);
WaitableEvent::WaitableEventKernel* kernel = event->kernel_.get();
AutoLock locked(event->lock_); AutoLock locked(kernel->lock_);
if (event->signaled_) { if (kernel->signaled_) {
if (!event->manual_reset_) if (!kernel->manual_reset_)
event->signaled_ = false; kernel->signaled_ = false;
// No hairpinning - we can't call the delegate directly here. We have to // No hairpinning - we can't call the delegate directly here. We have to
// enqueue a task on the MessageLoop as normal. // enqueue a task on the MessageLoop as normal.
...@@ -172,6 +173,7 @@ bool WaitableEventWatcher::StartWatching ...@@ -172,6 +173,7 @@ bool WaitableEventWatcher::StartWatching
current_ml->AddDestructionObserver(this); current_ml->AddDestructionObserver(this);
event_ = event; event_ = event;
kernel_ = kernel;
waiter_ = new AsyncWaiter(current_ml, callback_task_, cancel_flag_); waiter_ = new AsyncWaiter(current_ml, callback_task_, cancel_flag_);
event->Enqueue(waiter_); event->Enqueue(waiter_);
...@@ -194,9 +196,9 @@ void WaitableEventWatcher::StopWatching() { ...@@ -194,9 +196,9 @@ void WaitableEventWatcher::StopWatching() {
return; return;
} }
if (!event_) { if (!kernel_.get()) {
// We have no WaitableEvent. This means that we never enqueued a Waiter on // We have no kernel. This means that we never enqueued a Waiter on an
// an event because the event was already signaled when StartWatching was // event because the event was already signaled when StartWatching was
// called. // called.
// //
// In this case, a task was enqueued on the MessageLoop and will run. // In this case, a task was enqueued on the MessageLoop and will run.
...@@ -208,9 +210,9 @@ void WaitableEventWatcher::StopWatching() { ...@@ -208,9 +210,9 @@ void WaitableEventWatcher::StopWatching() {
return; return;
} }
AutoLock locked(event_->lock_); AutoLock locked(kernel_->lock_);
// We have a lock on the WaitableEvent. No one else can signal the event while // We have a lock on the kernel. No one else can signal the event while we
// we have it. // have it.
// We have a possible ABA issue here. If Dequeue was to compare only the // We have a possible ABA issue here. If Dequeue was to compare only the
// pointer values then it's possible that the AsyncWaiter could have been // pointer values then it's possible that the AsyncWaiter could have been
...@@ -224,7 +226,7 @@ void WaitableEventWatcher::StopWatching() { ...@@ -224,7 +226,7 @@ void WaitableEventWatcher::StopWatching() {
// have a reference to the Flag, its memory cannot be reused while this object // have a reference to the Flag, its memory cannot be reused while this object
// still exists. So if we find a waiter with the correct pointer value, and // still exists. So if we find a waiter with the correct pointer value, and
// which shares a Flag pointer, we have a real match. // which shares a Flag pointer, we have a real match.
if (event_->Dequeue(waiter_, cancel_flag_.get())) { if (kernel_->Dequeue(waiter_, cancel_flag_.get())) {
// Case 2: the waiter hasn't been signaled yet; it was still on the wait // Case 2: the waiter hasn't been signaled yet; it was still on the wait
// list. We've removed it, thus we can delete it and the task (which cannot // list. We've removed it, thus we can delete it and the task (which cannot
// have been enqueued with the MessageLoop because the waiter was never // have been enqueued with the MessageLoop because the waiter was never
......
...@@ -107,6 +107,22 @@ void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) { ...@@ -107,6 +107,22 @@ void RunTest_OutlivesMessageLoop(MessageLoop::Type message_loop_type) {
} }
} }
void RunTest_DeleteUnder(MessageLoop::Type message_loop_type) {
// Delete the WaitableEvent out from under the Watcher. This is explictly
// allowed by the interface.
MessageLoop message_loop(message_loop_type);
{
WaitableEventWatcher watcher;
WaitableEvent* event = new WaitableEvent(false, false);
QuitDelegate delegate;
watcher.StartWatching(event, &delegate);
delete event;
}
}
} // namespace } // namespace
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
...@@ -134,3 +150,9 @@ TEST(WaitableEventWatcherTest, OutlivesMessageLoop) { ...@@ -134,3 +150,9 @@ TEST(WaitableEventWatcherTest, OutlivesMessageLoop) {
RunTest_OutlivesMessageLoop(MessageLoop::TYPE_IO); RunTest_OutlivesMessageLoop(MessageLoop::TYPE_IO);
RunTest_OutlivesMessageLoop(MessageLoop::TYPE_UI); RunTest_OutlivesMessageLoop(MessageLoop::TYPE_UI);
} }
TEST(WaitableEventWatcherTest, DeleteUnder) {
RunTest_DeleteUnder(MessageLoop::TYPE_DEFAULT);
RunTest_DeleteUnder(MessageLoop::TYPE_IO);
RunTest_DeleteUnder(MessageLoop::TYPE_UI);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment