Chromium Code Reviews| Index: src/futex-emulation.cc |
| diff --git a/src/futex-emulation.cc b/src/futex-emulation.cc |
| index 39f43dd7dd8c998e8ec571b5942194827a240f75..2c9d177304afabc66eb318c412488138af50a13e 100644 |
| --- a/src/futex-emulation.cc |
| +++ b/src/futex-emulation.cc |
| @@ -21,6 +21,21 @@ base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ = |
| LAZY_INSTANCE_INITIALIZER; |
| +void FutexWaitListNode::NotifyWake() { |
| + // Lock the FutexEmulation mutex before notifying. We know that the mutex |
| + // will have been unlocked if we are currently waiting on the condition |
| + // variable. |
| + // |
| + // The mutex may also not be locked if the other thread is currently handling |
| + // interrupts, or if FutexEmulation::Wait was just called and the mutex |
| + // hasn't been locked yet. In either of those cases, we set the interrupted |
| + // flag to true, which will be tested after the mutex is re-locked. |
| + base::LockGuard<base::Mutex> lock_guard(FutexEmulation::mutex_.Pointer()); |
| + cond_.NotifyOne(); |
| + set_interrupted(true); |
| +} |
| + |
| + |
| FutexWaitList::FutexWaitList() : head_(nullptr), tail_(nullptr) {} |
| @@ -58,12 +73,6 @@ void FutexWaitList::RemoveNode(FutexWaitListNode* node) { |
| Object* FutexEmulation::Wait(Isolate* isolate, |
| Handle<JSArrayBuffer> array_buffer, size_t addr, |
| int32_t value, double rel_timeout_ms) { |
| - // We never want to wait longer than this amount of time; this way we can |
| - // interrupt this thread even if this is an "infinitely blocking" wait. |
| - // TODO(binji): come up with a better way of interrupting only when |
| - // necessary, rather than busy-waiting. |
| - const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromMilliseconds(50); |
| - |
| DCHECK(addr < NumberToSize(isolate, array_buffer->byte_length())); |
| void* backing_store = array_buffer->backing_store(); |
| @@ -80,7 +89,7 @@ Object* FutexEmulation::Wait(Isolate* isolate, |
| node->backing_store_ = backing_store; |
| node->wait_addr_ = addr; |
| - node->waiting_ = true; |
| + node->set_waiting(true); |
| bool use_timeout = rel_timeout_ms != V8_INFINITY; |
| @@ -101,40 +110,70 @@ Object* FutexEmulation::Wait(Isolate* isolate, |
| } |
| } |
| - base::Time start_time = base::Time::NowFromSystemTime(); |
| - base::Time timeout_time = start_time + rel_timeout; |
| + base::TimeTicks start_time = base::TimeTicks::Now(); |
| + base::TimeTicks timeout_time = start_time + rel_timeout; |
| + base::TimeTicks current_time = start_time; |
| wait_list_.Pointer()->AddNode(node); |
| Object* result; |
| while (true) { |
| - base::Time current_time = base::Time::NowFromSystemTime(); |
| - if (use_timeout && current_time > timeout_time) { |
| - result = Smi::FromInt(Result::kTimedOut); |
| - break; |
| + // Unlock the mutex here to prevent deadlock from lock ordering between |
| + // mutex_ and mutexes locked by HandleInterrupts. |
| + mutex_.Pointer()->Unlock(); |
| + |
| + // Because the mutex is unlocked, we have to be careful about not dropping |
| + // an interrupt. The notification can happen in three different places: |
| + // 1) Before Wait is called: the notification will be dropped, but |
| + // interrupted_ will be set to 1. This will be checked below. |
| + // 2) After interrupted has been checked here, but before mutex_ is |
| + // acquired: interrupted is checked again below, with mutex_ locked. |
| + // Because the wakeup signal also acquires mutex_, we know it will not |
| + // be able to notify until mutex_ is released below, when waiting on the |
| + // condition variable. |
| + // 3) After the mutex is released in the call to WaitFor(): this |
| + // notification will wake up the condition variable. node->waiting() will |
| + // be false, so we'll loop and then check interrupts. |
| + if (node->CheckInterruptedAndClear()) { |
|
Jarin
2015/08/11 08:21:49
Why can't you read and clear the interrupt flag wh
binji
2015/08/11 14:51:17
Good point, I'll change this.
|
| + Object* interrupt_object = isolate->stack_guard()->HandleInterrupts(); |
| + if (interrupt_object->IsException()) { |
| + result = interrupt_object; |
| + mutex_.Pointer()->Lock(); |
| + break; |
| + } |
| + } |
| + |
| + mutex_.Pointer()->Lock(); |
| + |
| + if (node->interrupted()) { |
| + // An interrupt occured while the mutex_ was unlocked. Don't wait yet. |
| + continue; |
| } |
| - base::TimeDelta time_until_timeout = timeout_time - current_time; |
| - base::TimeDelta time_to_wait = |
| - (use_timeout && time_until_timeout < kMaxWaitTime) ? time_until_timeout |
| - : kMaxWaitTime; |
| + // No interrupts, now wait. |
| + if (use_timeout) { |
| + current_time = base::TimeTicks::Now(); |
| + if (current_time >= timeout_time) { |
| + result = Smi::FromInt(Result::kTimedOut); |
| + break; |
| + } |
| - bool wait_for_result = node->cond_.WaitFor(mutex_.Pointer(), time_to_wait); |
| - USE(wait_for_result); |
| + base::TimeDelta time_until_timeout = timeout_time - current_time; |
| + DCHECK(time_until_timeout.InMicroseconds() >= 0); |
| + bool wait_for_result = |
| + node->cond_.WaitFor(mutex_.Pointer(), time_until_timeout); |
| + USE(wait_for_result); |
| + } else { |
| + node->cond_.Wait(mutex_.Pointer()); |
| + } |
| - if (!node->waiting_) { |
| + if (!node->waiting()) { |
| result = Smi::FromInt(Result::kOk); |
| break; |
| } |
| - // Spurious wakeup or timeout. Potentially handle interrupts before |
| - // continuing to wait. |
| - Object* interrupt_object = isolate->stack_guard()->HandleInterrupts(); |
| - if (interrupt_object->IsException()) { |
| - result = interrupt_object; |
| - break; |
| - } |
| + // Spurious wakeup, interrupt or timeout. |
| } |
| wait_list_.Pointer()->RemoveNode(node); |
| @@ -155,7 +194,7 @@ Object* FutexEmulation::Wake(Isolate* isolate, |
| FutexWaitListNode* node = wait_list_.Pointer()->head_; |
| while (node && num_waiters_to_wake > 0) { |
| if (backing_store == node->backing_store_ && addr == node->wait_addr_) { |
| - node->waiting_ = false; |
| + node->set_waiting(false); |
| node->cond_.NotifyOne(); |
| --num_waiters_to_wake; |
| waiters_woken++; |
| @@ -190,7 +229,7 @@ Object* FutexEmulation::WakeOrRequeue(Isolate* isolate, |
| while (node) { |
| if (backing_store == node->backing_store_ && addr == node->wait_addr_) { |
| if (num_waiters_to_wake > 0) { |
| - node->waiting_ = false; |
| + node->set_waiting(false); |
| node->cond_.NotifyOne(); |
| --num_waiters_to_wake; |
| waiters_woken++; |