2015-07-17 17:11:32 +00:00
|
|
|
// Copyright 2015 the V8 project 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 "src/futex-emulation.h"
|
|
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
#include "src/base/macros.h"
|
|
|
|
#include "src/base/platform/time.h"
|
|
|
|
#include "src/conversions.h"
|
|
|
|
#include "src/handles-inl.h"
|
|
|
|
#include "src/isolate.h"
|
|
|
|
#include "src/list-inl.h"
|
|
|
|
|
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
|
|
|
|
|
|
|
base::LazyMutex FutexEmulation::mutex_ = LAZY_MUTEX_INITIALIZER;
|
|
|
|
base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ =
|
|
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
|
|
|
|
|
2015-08-21 16:41:43 +00:00
|
|
|
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());
|
|
|
|
if (waiting_) {
|
|
|
|
cond_.NotifyOne();
|
|
|
|
interrupted_ = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-17 17:11:32 +00:00
|
|
|
FutexWaitList::FutexWaitList() : head_(nullptr), tail_(nullptr) {}
|
|
|
|
|
|
|
|
|
|
|
|
void FutexWaitList::AddNode(FutexWaitListNode* node) {
|
|
|
|
DCHECK(node->prev_ == nullptr && node->next_ == nullptr);
|
|
|
|
if (tail_) {
|
|
|
|
tail_->next_ = node;
|
|
|
|
} else {
|
|
|
|
head_ = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
node->prev_ = tail_;
|
|
|
|
node->next_ = nullptr;
|
|
|
|
tail_ = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
|
|
|
|
if (node->prev_) {
|
|
|
|
node->prev_->next_ = node->next_;
|
|
|
|
} else {
|
|
|
|
head_ = node->next_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node->next_) {
|
|
|
|
node->next_->prev_ = node->prev_;
|
|
|
|
} else {
|
|
|
|
tail_ = node->prev_;
|
|
|
|
}
|
|
|
|
|
|
|
|
node->prev_ = node->next_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Object* FutexEmulation::Wait(Isolate* isolate,
|
|
|
|
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
|
|
|
int32_t value, double rel_timeout_ms) {
|
2016-08-08 15:54:35 +00:00
|
|
|
DCHECK(addr < NumberToSize(array_buffer->byte_length()));
|
2015-07-17 17:11:32 +00:00
|
|
|
|
|
|
|
void* backing_store = array_buffer->backing_store();
|
|
|
|
int32_t* p =
|
|
|
|
reinterpret_cast<int32_t*>(static_cast<int8_t*>(backing_store) + addr);
|
|
|
|
|
|
|
|
base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
|
|
|
|
|
|
|
|
if (*p != value) {
|
2016-07-13 18:29:48 +00:00
|
|
|
return isolate->heap()->not_equal();
|
2015-07-17 17:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FutexWaitListNode* node = isolate->futex_wait_list_node();
|
|
|
|
|
|
|
|
node->backing_store_ = backing_store;
|
|
|
|
node->wait_addr_ = addr;
|
|
|
|
node->waiting_ = true;
|
|
|
|
|
|
|
|
bool use_timeout = rel_timeout_ms != V8_INFINITY;
|
|
|
|
|
|
|
|
base::TimeDelta rel_timeout;
|
|
|
|
if (use_timeout) {
|
|
|
|
// Convert to nanoseconds.
|
|
|
|
double rel_timeout_ns = rel_timeout_ms *
|
|
|
|
base::Time::kNanosecondsPerMicrosecond *
|
|
|
|
base::Time::kMicrosecondsPerMillisecond;
|
|
|
|
if (rel_timeout_ns >
|
|
|
|
static_cast<double>(std::numeric_limits<int64_t>::max())) {
|
|
|
|
// 2**63 nanoseconds is 292 years. Let's just treat anything greater as
|
|
|
|
// infinite.
|
|
|
|
use_timeout = false;
|
|
|
|
} else {
|
|
|
|
rel_timeout = base::TimeDelta::FromNanoseconds(
|
|
|
|
static_cast<int64_t>(rel_timeout_ns));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-11 14:28:06 +00:00
|
|
|
base::TimeTicks start_time = base::TimeTicks::Now();
|
|
|
|
base::TimeTicks timeout_time = start_time + rel_timeout;
|
2015-08-21 16:41:43 +00:00
|
|
|
base::TimeTicks current_time = start_time;
|
2015-07-17 17:11:32 +00:00
|
|
|
|
|
|
|
wait_list_.Pointer()->AddNode(node);
|
|
|
|
|
|
|
|
Object* result;
|
|
|
|
|
|
|
|
while (true) {
|
2015-08-21 16:41:43 +00:00
|
|
|
bool interrupted = node->interrupted_;
|
|
|
|
node->interrupted_ = false;
|
|
|
|
|
|
|
|
// 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 (interrupted) {
|
|
|
|
Object* interrupt_object = isolate->stack_guard()->HandleInterrupts();
|
2016-06-14 10:08:44 +00:00
|
|
|
if (interrupt_object->IsException(isolate)) {
|
2015-08-21 16:41:43 +00:00
|
|
|
result = interrupt_object;
|
|
|
|
mutex_.Pointer()->Lock();
|
|
|
|
break;
|
|
|
|
}
|
2015-08-05 20:58:06 +00:00
|
|
|
}
|
|
|
|
|
2015-08-21 16:41:43 +00:00
|
|
|
mutex_.Pointer()->Lock();
|
2015-07-17 17:11:32 +00:00
|
|
|
|
2015-08-21 16:41:43 +00:00
|
|
|
if (node->interrupted_) {
|
|
|
|
// An interrupt occured while the mutex_ was unlocked. Don't wait yet.
|
|
|
|
continue;
|
|
|
|
}
|
2015-07-17 17:11:32 +00:00
|
|
|
|
|
|
|
if (!node->waiting_) {
|
2016-07-13 18:29:48 +00:00
|
|
|
result = isolate->heap()->ok();
|
2015-07-17 17:11:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-08-21 16:41:43 +00:00
|
|
|
// No interrupts, now wait.
|
|
|
|
if (use_timeout) {
|
|
|
|
current_time = base::TimeTicks::Now();
|
|
|
|
if (current_time >= timeout_time) {
|
2016-07-13 18:29:48 +00:00
|
|
|
result = isolate->heap()->timed_out();
|
2015-08-21 16:41:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
2015-07-17 17:11:32 +00:00
|
|
|
}
|
2015-08-21 16:41:43 +00:00
|
|
|
|
|
|
|
// Spurious wakeup, interrupt or timeout.
|
2015-07-17 17:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wait_list_.Pointer()->RemoveNode(node);
|
2015-08-21 16:41:43 +00:00
|
|
|
node->waiting_ = false;
|
2015-07-17 17:11:32 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Object* FutexEmulation::Wake(Isolate* isolate,
|
|
|
|
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
|
|
|
int num_waiters_to_wake) {
|
2016-08-08 15:54:35 +00:00
|
|
|
DCHECK(addr < NumberToSize(array_buffer->byte_length()));
|
2015-07-17 17:11:32 +00:00
|
|
|
|
|
|
|
int waiters_woken = 0;
|
|
|
|
void* backing_store = array_buffer->backing_store();
|
|
|
|
|
|
|
|
base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
|
|
|
|
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->cond_.NotifyOne();
|
|
|
|
--num_waiters_to_wake;
|
|
|
|
waiters_woken++;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = node->next_;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Smi::FromInt(waiters_woken);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Object* FutexEmulation::NumWaitersForTesting(Isolate* isolate,
|
|
|
|
Handle<JSArrayBuffer> array_buffer,
|
|
|
|
size_t addr) {
|
2016-08-08 15:54:35 +00:00
|
|
|
DCHECK(addr < NumberToSize(array_buffer->byte_length()));
|
2015-07-17 17:11:32 +00:00
|
|
|
void* backing_store = array_buffer->backing_store();
|
|
|
|
|
|
|
|
base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
|
|
|
|
|
|
|
|
int waiters = 0;
|
|
|
|
FutexWaitListNode* node = wait_list_.Pointer()->head_;
|
|
|
|
while (node) {
|
2015-11-20 11:39:25 +00:00
|
|
|
if (backing_store == node->backing_store_ && addr == node->wait_addr_ &&
|
|
|
|
node->waiting_) {
|
2015-07-17 17:11:32 +00:00
|
|
|
waiters++;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = node->next_;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Smi::FromInt(waiters);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|