%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/execution/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/execution/futex-emulation.cc |
// 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/execution/futex-emulation.h"
#include <limits>
#include "src/api/api-inl.h"
#include "src/base/lazy-instance.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/small-map.h"
#include "src/execution/isolate.h"
#include "src/execution/vm-state-inl.h"
#include "src/handles/handles-inl.h"
#include "src/numbers/conversions.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/objects-inl.h"
#include "src/tasks/cancelable-task.h"
namespace v8::internal {
using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;
// A {FutexWaitList} manages all contexts waiting (synchronously or
// asynchronously) on any address.
class FutexWaitList {
public:
FutexWaitList() = default;
FutexWaitList(const FutexWaitList&) = delete;
FutexWaitList& operator=(const FutexWaitList&) = delete;
void AddNode(FutexWaitListNode* node);
void RemoveNode(FutexWaitListNode* node);
static void* ToWaitLocation(Tagged<JSArrayBuffer> array_buffer, size_t addr) {
DCHECK_LT(addr, array_buffer->GetByteLength());
// Use the cheaper JSArrayBuffer::backing_store() accessor, but DCHECK that
// it matches the start of the JSArrayBuffer::GetBackingStore().
DCHECK_EQ(array_buffer->backing_store(),
array_buffer->GetBackingStore()->buffer_start());
return static_cast<uint8_t*>(array_buffer->backing_store()) + addr;
}
// Deletes "node" and returns the next node of its list.
static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node) {
DCHECK(node->IsAsync());
DCHECK_NOT_NULL(node->async_state_->isolate_for_async_waiters);
FutexWaitListNode* next = node->next_;
if (node->prev_ != nullptr) {
node->prev_->next_ = next;
}
if (next != nullptr) {
next->prev_ = node->prev_;
}
delete node;
return next;
}
static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
FutexWaitListNode** tail) {
// For updating head & tail once we've iterated all nodes.
FutexWaitListNode* new_head = nullptr;
FutexWaitListNode* new_tail = nullptr;
for (FutexWaitListNode* node = *head; node;) {
if (node->IsAsync() &&
node->async_state_->isolate_for_async_waiters == isolate) {
node->async_state_->timeout_task_id =
CancelableTaskManager::kInvalidTaskId;
node = DeleteAsyncWaiterNode(node);
} else {
if (new_head == nullptr) {
new_head = node;
}
new_tail = node;
node = node->next_;
}
}
*head = new_head;
*tail = new_tail;
}
// For checking the internal consistency of the FutexWaitList.
void Verify() const;
// Returns true if |node| is on the linked list starting with |head|.
static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
base::Mutex* mutex() { return &mutex_; }
private:
friend class FutexEmulation;
struct HeadAndTail {
FutexWaitListNode* head;
FutexWaitListNode* tail;
};
// `mutex` protects the composition of the fields below (i.e. no elements may
// be added or removed without holding this mutex), as well as the `waiting_`
// and `interrupted_` fields for each individual list node that is currently
// part of the list. It must be the mutex used together with the `cond_`
// condition variable of such nodes.
base::Mutex mutex_;
// Location inside a shared buffer -> linked list of Nodes waiting on that
// location.
// As long as the map does not grow beyond 16 entries, there is no dynamic
// allocation and deallocation happening in wait or wake, which reduces the
// time spend in the critical section.
base::SmallMap<std::map<void*, HeadAndTail>, 16> location_lists_;
// Isolate* -> linked list of Nodes which are waiting for their Promises to
// be resolved.
base::SmallMap<std::map<Isolate*, HeadAndTail>> isolate_promises_to_resolve_;
};
namespace {
// {GetWaitList} returns the lazily initialized global wait list.
DEFINE_LAZY_LEAKY_OBJECT_GETTER(FutexWaitList, GetWaitList)
} // namespace
bool FutexWaitListNode::CancelTimeoutTask() {
DCHECK(IsAsync());
if (async_state_->timeout_task_id == CancelableTaskManager::kInvalidTaskId) {
return true;
}
auto* cancelable_task_manager =
async_state_->isolate_for_async_waiters->cancelable_task_manager();
TryAbortResult return_value =
cancelable_task_manager->TryAbort(async_state_->timeout_task_id);
async_state_->timeout_task_id = CancelableTaskManager::kInvalidTaskId;
return return_value != TryAbortResult::kTaskRunning;
}
void FutexWaitListNode::NotifyWake() {
DCHECK(!IsAsync());
// 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 will not be locked if FutexEmulation::Wait hasn't
// locked it yet. In that case, we set the interrupted_
// flag to true, which will be tested after the mutex locked by a future wait.
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
// if not waiting, this will not have any effect.
cond_.NotifyOne();
interrupted_ = true;
}
class ResolveAsyncWaiterPromisesTask : public CancelableTask {
public:
ResolveAsyncWaiterPromisesTask(Isolate* isolate)
: CancelableTask(isolate), isolate_(isolate) {}
void RunInternal() override {
FutexEmulation::ResolveAsyncWaiterPromises(isolate_);
}
private:
Isolate* isolate_;
};
class AsyncWaiterTimeoutTask : public CancelableTask {
public:
AsyncWaiterTimeoutTask(CancelableTaskManager* cancelable_task_manager,
FutexWaitListNode* node)
: CancelableTask(cancelable_task_manager), node_(node) {}
void RunInternal() override {
FutexEmulation::HandleAsyncWaiterTimeout(node_);
}
private:
FutexWaitListNode* node_;
};
void FutexEmulation::NotifyAsyncWaiter(FutexWaitListNode* node) {
DCHECK(node->IsAsync());
// This function can run in any thread.
FutexWaitList* wait_list = GetWaitList();
wait_list->mutex()->AssertHeld();
// Nullify the timeout time; this distinguishes timed out waiters from
// woken up ones.
node->async_state_->timeout_time = base::TimeTicks();
wait_list->RemoveNode(node);
// Schedule a task for resolving the Promise. It's still possible that the
// timeout task runs before the promise resolving task. In that case, the
// timeout task will just ignore the node.
auto& isolate_map = wait_list->isolate_promises_to_resolve_;
auto it = isolate_map.find(node->async_state_->isolate_for_async_waiters);
if (it == isolate_map.end()) {
// This Isolate doesn't have other Promises to resolve at the moment.
isolate_map.insert(
std::make_pair(node->async_state_->isolate_for_async_waiters,
FutexWaitList::HeadAndTail{node, node}));
auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
node->async_state_->isolate_for_async_waiters);
node->async_state_->task_runner->PostNonNestableTask(std::move(task));
} else {
// Add this Node into the existing list.
node->prev_ = it->second.tail;
it->second.tail->next_ = node;
it->second.tail = node;
}
}
void FutexWaitList::AddNode(FutexWaitListNode* node) {
DCHECK_NULL(node->prev_);
DCHECK_NULL(node->next_);
auto it = location_lists_.find(node->wait_location_);
if (it == location_lists_.end()) {
location_lists_.insert(
std::make_pair(node->wait_location_, HeadAndTail{node, node}));
} else {
it->second.tail->next_ = node;
node->prev_ = it->second.tail;
it->second.tail = node;
}
Verify();
}
void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
auto it = location_lists_.find(node->wait_location_);
DCHECK_NE(location_lists_.end(), it);
DCHECK(NodeIsOnList(node, it->second.head));
if (node->prev_) {
node->prev_->next_ = node->next_;
} else {
DCHECK_EQ(node, it->second.head);
it->second.head = node->next_;
}
if (node->next_) {
node->next_->prev_ = node->prev_;
} else {
DCHECK_EQ(node, it->second.tail);
it->second.tail = node->prev_;
}
// If the node was the last one on its list, delete the whole list.
if (node->prev_ == nullptr && node->next_ == nullptr) {
location_lists_.erase(it);
}
node->prev_ = node->next_ = nullptr;
Verify();
}
void AtomicsWaitWakeHandle::Wake() {
// Adding a separate `NotifyWake()` variant that doesn't acquire the lock
// itself would likely just add unnecessary complexity..
// The split lock by itself isn’t an issue, as long as the caller properly
// synchronizes this with the closing `AtomicsWaitCallback`.
FutexWaitList* wait_list = GetWaitList();
{
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
stopped_ = true;
}
isolate_->futex_wait_list_node()->NotifyWake();
}
enum WaitReturnValue : int { kOk = 0, kNotEqualValue = 1, kTimedOut = 2 };
namespace {
Tagged<Object> WaitJsTranslateReturn(Isolate* isolate, Tagged<Object> res) {
if (IsSmi(res)) {
int val = Smi::ToInt(res);
switch (val) {
case WaitReturnValue::kOk:
return ReadOnlyRoots(isolate).ok_string();
case WaitReturnValue::kNotEqualValue:
return ReadOnlyRoots(isolate).not_equal_string();
case WaitReturnValue::kTimedOut:
return ReadOnlyRoots(isolate).timed_out_string();
default:
UNREACHABLE();
}
}
return res;
}
} // namespace
Tagged<Object> FutexEmulation::WaitJs32(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int32_t value,
double rel_timeout_ms) {
Tagged<Object> res =
Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
return WaitJsTranslateReturn(isolate, res);
}
Tagged<Object> FutexEmulation::WaitJs64(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int64_t value,
double rel_timeout_ms) {
Tagged<Object> res =
Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
return WaitJsTranslateReturn(isolate, res);
}
Tagged<Object> FutexEmulation::WaitWasm32(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int32_t value,
int64_t rel_timeout_ns) {
return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
}
Tagged<Object> FutexEmulation::WaitWasm64(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, int64_t value,
int64_t rel_timeout_ns) {
return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
}
template <typename T>
Tagged<Object> FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value,
double rel_timeout_ms) {
DCHECK_LT(addr, array_buffer->GetByteLength());
bool use_timeout = rel_timeout_ms != V8_INFINITY;
int64_t rel_timeout_ns = -1;
if (use_timeout) {
// Convert to nanoseconds.
double timeout_ns = rel_timeout_ms *
base::Time::kNanosecondsPerMicrosecond *
base::Time::kMicrosecondsPerMillisecond;
if (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_ns = static_cast<int64_t>(timeout_ns);
}
}
return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
rel_timeout_ns);
}
namespace {
double WaitTimeoutInMs(double timeout_ns) {
return timeout_ns < 0
? V8_INFINITY
: timeout_ns / (base::Time::kNanosecondsPerMicrosecond *
base::Time::kMicrosecondsPerMillisecond);
}
} // namespace
template <typename T>
Tagged<Object> FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns,
CallType call_type) {
if (mode == WaitMode::kSync) {
return WaitSync(isolate, array_buffer, addr, value, use_timeout,
rel_timeout_ns, call_type);
}
DCHECK_EQ(mode, WaitMode::kAsync);
return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
rel_timeout_ns, call_type);
}
template <typename T>
Tagged<Object> FutexEmulation::WaitSync(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns,
CallType call_type) {
VMState<ATOMICS_WAIT> state(isolate);
base::TimeDelta rel_timeout =
base::TimeDelta::FromNanoseconds(rel_timeout_ns);
// We have to convert the timeout back to double for the AtomicsWaitCallback.
double rel_timeout_ms = WaitTimeoutInMs(static_cast<double>(rel_timeout_ns));
AtomicsWaitWakeHandle stop_handle(isolate);
isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
addr, value, rel_timeout_ms, &stop_handle);
if (isolate->has_scheduled_exception()) {
return isolate->PromoteScheduledException();
}
Handle<Object> result;
AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
FutexWaitList* wait_list = GetWaitList();
FutexWaitListNode* node = isolate->futex_wait_list_node();
void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
base::TimeTicks timeout_time;
if (use_timeout) {
base::TimeTicks current_time = base::TimeTicks::Now();
timeout_time = current_time + rel_timeout;
}
// The following is not really a loop; the do-while construct makes it easier
// to break out early.
// Keep the code in the loop as minimal as possible, because this is all in
// the critical section.
do {
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
node->wait_location_ = wait_location;
node->waiting_ = true;
// Reset node->waiting_ = false when leaving this scope (but while
// still holding the lock).
FutexWaitListNode::ResetWaitingOnScopeExit reset_waiting(node);
std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
T loaded_value = p->load();
#if defined(V8_TARGET_BIG_ENDIAN)
// If loading a Wasm value, it needs to be reversed on Big Endian platforms.
if (call_type == CallType::kIsWasm) {
DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
loaded_value = ByteReverse(loaded_value);
}
#endif
if (loaded_value != value) {
result = handle(Smi::FromInt(WaitReturnValue::kNotEqualValue), isolate);
callback_result = AtomicsWaitEvent::kNotEqual;
break;
}
wait_list->AddNode(node);
while (true) {
if (V8_UNLIKELY(node->interrupted_)) {
// Reset the interrupted flag while still holding the mutex.
node->interrupted_ = false;
// Unlock the mutex here to prevent deadlock from lock ordering between
// mutex and mutexes locked by HandleInterrupts.
lock_guard.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 in a loop, 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.
Tagged<Object> interrupt_object =
isolate->stack_guard()->HandleInterrupts();
lock_guard.Lock();
if (IsException(interrupt_object, isolate)) {
result = handle(interrupt_object, isolate);
callback_result = AtomicsWaitEvent::kTerminatedExecution;
break;
}
}
if (V8_UNLIKELY(node->interrupted_)) {
// An interrupt occurred while the mutex was unlocked. Don't wait yet.
continue;
}
if (stop_handle.has_stopped()) {
node->waiting_ = false;
callback_result = AtomicsWaitEvent::kAPIStopped;
}
if (!node->waiting_) {
result = handle(Smi::FromInt(WaitReturnValue::kOk), isolate);
break;
}
// No interrupts, now wait.
if (use_timeout) {
base::TimeTicks current_time = base::TimeTicks::Now();
if (current_time >= timeout_time) {
result = handle(Smi::FromInt(WaitReturnValue::kTimedOut), isolate);
callback_result = AtomicsWaitEvent::kTimedOut;
break;
}
base::TimeDelta time_until_timeout = timeout_time - current_time;
DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
bool wait_for_result =
node->cond_.WaitFor(wait_list->mutex(), time_until_timeout);
USE(wait_for_result);
} else {
node->cond_.Wait(wait_list->mutex());
}
// Spurious wakeup, interrupt or timeout.
}
wait_list->RemoveNode(node);
} while (false);
isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
rel_timeout_ms, nullptr);
if (isolate->has_scheduled_exception()) {
CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
result = handle(isolate->PromoteScheduledException(), isolate);
}
return *result;
}
namespace {
template <typename T>
Global<T> GetWeakGlobal(Isolate* isolate, Local<T> object) {
auto* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8::Global<T> global{v8_isolate, object};
global.SetWeak();
return global;
}
} // namespace
FutexWaitListNode::FutexWaitListNode(std::weak_ptr<BackingStore> backing_store,
void* wait_location,
Handle<JSObject> promise, Isolate* isolate)
: wait_location_(wait_location),
waiting_(true),
async_state_(std::make_unique<AsyncState>(
isolate,
V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate)),
std::move(backing_store),
GetWeakGlobal(isolate, Utils::PromiseToLocal(promise)),
GetWeakGlobal(isolate, Utils::ToLocal(isolate->native_context())))) {}
template <typename T>
Tagged<Object> FutexEmulation::WaitAsync(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer,
size_t addr, T value, bool use_timeout,
int64_t rel_timeout_ns,
CallType call_type) {
base::TimeDelta rel_timeout =
base::TimeDelta::FromNanoseconds(rel_timeout_ns);
Factory* factory = isolate->factory();
Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
Handle<JSObject> promise_capability = factory->NewJSPromise();
enum class ResultKind { kNotEqual, kTimedOut, kAsync };
ResultKind result_kind;
void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
// Get a weak pointer to the backing store, to be stored in the async state of
// the node.
std::weak_ptr<BackingStore> backing_store{array_buffer->GetBackingStore()};
FutexWaitList* wait_list = GetWaitList();
{
// 16. Perform EnterCriticalSection(WL).
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
// 17. Let w be ! AtomicLoad(typedArray, i).
std::atomic<T>* p = static_cast<std::atomic<T>*>(wait_location);
T loaded_value = p->load();
#if defined(V8_TARGET_BIG_ENDIAN)
// If loading a Wasm value, it needs to be reversed on Big Endian platforms.
if (call_type == CallType::kIsWasm) {
DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
loaded_value = ByteReverse(loaded_value);
}
#endif
if (loaded_value != value) {
result_kind = ResultKind::kNotEqual;
} else if (use_timeout && rel_timeout_ns == 0) {
result_kind = ResultKind::kTimedOut;
} else {
result_kind = ResultKind::kAsync;
FutexWaitListNode* node = new FutexWaitListNode(
std::move(backing_store), wait_location, promise_capability, isolate);
if (use_timeout) {
node->async_state_->timeout_time = base::TimeTicks::Now() + rel_timeout;
auto task = std::make_unique<AsyncWaiterTimeoutTask>(
node->async_state_->isolate_for_async_waiters
->cancelable_task_manager(),
node);
node->async_state_->timeout_task_id = task->id();
node->async_state_->task_runner->PostNonNestableDelayedTask(
std::move(task), rel_timeout.InSecondsF());
}
wait_list->AddNode(node);
}
// Leaving the block collapses the following steps:
// 18.a. Perform LeaveCriticalSection(WL).
// 19.b. Perform LeaveCriticalSection(WL).
// 24. Perform LeaveCriticalSection(WL).
}
switch (result_kind) {
case ResultKind::kNotEqual:
// 18. If v is not equal to w, then
// ...
// c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
// d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
// "not-equal").
// e. Return resultObject.
CHECK(JSReceiver::CreateDataProperty(
isolate, result, factory->async_string(),
factory->false_value(), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, result, factory->value_string(),
factory->not_equal_string(), Just(kDontThrow))
.FromJust());
break;
case ResultKind::kTimedOut:
// 19. If t is 0 and mode is async, then
// ...
// c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
// d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
// "timed-out").
// e. Return resultObject.
CHECK(JSReceiver::CreateDataProperty(
isolate, result, factory->async_string(),
factory->false_value(), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, result, factory->value_string(),
factory->timed_out_string(), Just(kDontThrow))
.FromJust());
break;
case ResultKind::kAsync:
// Add the Promise into the NativeContext's atomics_waitasync_promises
// set, so that the list keeps it alive.
Handle<NativeContext> native_context(isolate->native_context());
Handle<OrderedHashSet> promises(
native_context->atomics_waitasync_promises(), isolate);
promises = OrderedHashSet::Add(isolate, promises, promise_capability)
.ToHandleChecked();
native_context->set_atomics_waitasync_promises(*promises);
// 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
// 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
// promiseCapability.[[Promise]]).
// 28. Return resultObject.
CHECK(JSReceiver::CreateDataProperty(
isolate, result, factory->async_string(), factory->true_value(),
Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(isolate, result,
factory->value_string(),
promise_capability, Just(kDontThrow))
.FromJust());
break;
}
return *result;
}
int FutexEmulation::Wake(Tagged<JSArrayBuffer> array_buffer, size_t addr,
uint32_t num_waiters_to_wake) {
int num_waiters_woken = 0;
void* wait_location = FutexWaitList::ToWaitLocation(array_buffer, addr);
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
auto& location_lists = wait_list->location_lists_;
auto it = location_lists.find(wait_location);
if (it == location_lists.end()) return num_waiters_woken;
FutexWaitListNode* node = it->second.head;
while (node && num_waiters_to_wake > 0) {
if (!node->waiting_) {
node = node->next_;
continue;
}
// Relying on wait_location_ here is not enough, since we need to guard
// against the case where the BackingStore of the node has been deleted
// during an async wait and a new BackingStore recreated in the same memory
// area. Note that sync wait always keeps the backing store alive.
// It is sufficient to check whether the node's backing store is expired
// (and consider this a non-match). If it is not expired, it must be
// identical to the backing store from which wait_location was computed by
// the caller. In that case, the current context holds the arraybuffer and
// backing store alive during this call, so it can not expire while we
// execute this code.
bool matching_backing_store =
!node->IsAsync() || !node->async_state_->backing_store.expired();
if (V8_LIKELY(matching_backing_store)) {
node->waiting_ = false;
// Retrieve the next node to iterate before calling NotifyAsyncWaiter,
// since NotifyAsyncWaiter will take the node out of the linked list.
FutexWaitListNode* next_node = node->next_;
if (node->IsAsync()) {
NotifyAsyncWaiter(node);
} else {
// WaitSync will remove the node from the list.
node->cond_.NotifyOne();
}
node = next_node;
if (num_waiters_to_wake != kWakeAll) {
--num_waiters_to_wake;
}
num_waiters_woken++;
continue;
}
// ---
// Code below handles the unlikely case that this node's backing store was
// deleted during an async wait and a new one was allocated in its place.
// We delete the node if possible (no timeout, or context is gone).
// ---
bool delete_this_node = false;
DCHECK(node->IsAsync());
if (node->async_state_->timeout_time.IsNull()) {
// Backing store has been deleted and the node is still waiting, and
// there's no timeout. It's never going to be woken up, so we can clean it
// up now. We don't need to cancel the timeout task, because there is
// none.
// This cleanup code is not very efficient, since it only kicks in when
// a new BackingStore has been created in the same memory area where the
// deleted BackingStore was.
DCHECK(node->IsAsync());
DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
node->async_state_->timeout_task_id);
delete_this_node = true;
}
if (node->async_state_->native_context.IsEmpty()) {
// The NativeContext related to the async waiter has been deleted.
// Ditto, clean up now.
// Using the CancelableTaskManager here is OK since the Isolate is
// guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
// FutexWaitListNodes owned by an Isolate which is going to die.
if (node->CancelTimeoutTask()) {
delete_this_node = true;
}
// If cancelling the timeout task failed, the timeout task is already
// running and will clean up the node.
}
FutexWaitListNode* next_node = node->next_;
if (delete_this_node) {
wait_list->RemoveNode(node);
delete node;
}
node = next_node;
}
return num_waiters_woken;
}
void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
DCHECK(node->IsAsync());
// This function must run in the main thread of node's Isolate. This function
// may allocate memory. To avoid deadlocks, we shouldn't be holding the
// FutexEmulationGlobalState::mutex.
Isolate* isolate = node->async_state_->isolate_for_async_waiters;
auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
if (!node->async_state_->promise.IsEmpty()) {
Handle<JSPromise> promise = Handle<JSPromise>::cast(
Utils::OpenHandle(*node->async_state_->promise.Get(v8_isolate)));
// Promise keeps the NativeContext alive.
DCHECK(!node->async_state_->native_context.IsEmpty());
Handle<NativeContext> native_context = Handle<NativeContext>::cast(
Utils::OpenHandle(*node->async_state_->native_context.Get(v8_isolate)));
// Remove the Promise from the NativeContext's set.
Handle<OrderedHashSet> promises(
native_context->atomics_waitasync_promises(), isolate);
bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
DCHECK(was_deleted);
USE(was_deleted);
promises = OrderedHashSet::Shrink(isolate, promises);
native_context->set_atomics_waitasync_promises(*promises);
} else {
// NativeContext keeps the Promise alive; if the Promise is dead then
// surely NativeContext is too.
DCHECK(node->async_state_->native_context.IsEmpty());
}
}
void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
DCHECK(node->IsAsync());
// This function must run in the main thread of node's Isolate.
Isolate* isolate = node->async_state_->isolate_for_async_waiters;
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
// Try to cancel the timeout task (if one exists). If the timeout task exists,
// cancelling it will always succeed. It's not possible for the timeout task
// to be running, since it's scheduled to run in the same thread as this task.
// Using the CancelableTaskManager here is OK since the Isolate is guaranteed
// to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
// owned by an Isolate which is going to die.
bool success = node->CancelTimeoutTask();
DCHECK(success);
USE(success);
if (!node->async_state_->promise.IsEmpty()) {
DCHECK(!node->async_state_->native_context.IsEmpty());
Local<v8::Context> native_context =
node->async_state_->native_context.Get(v8_isolate);
v8::Context::Scope contextScope(native_context);
Handle<JSPromise> promise = Handle<JSPromise>::cast(
Utils::OpenHandle(*node->async_state_->promise.Get(v8_isolate)));
Handle<String> result_string;
// When waiters are notified, their timeout_time is reset. Having a
// non-zero timeout_time here means the waiter timed out.
if (node->async_state_->timeout_time != base::TimeTicks()) {
DCHECK(node->waiting_);
result_string = isolate->factory()->timed_out_string();
} else {
DCHECK(!node->waiting_);
result_string = isolate->factory()->ok_string();
}
MaybeHandle<Object> resolve_result =
JSPromise::Resolve(promise, result_string);
DCHECK(!resolve_result.is_null());
USE(resolve_result);
}
}
void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
// This function must run in the main thread of isolate.
FutexWaitList* wait_list = GetWaitList();
FutexWaitListNode* node;
{
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
auto& isolate_map = wait_list->isolate_promises_to_resolve_;
auto it = isolate_map.find(isolate);
DCHECK_NE(isolate_map.end(), it);
node = it->second.head;
isolate_map.erase(it);
}
// The list of nodes starting from "node" are no longer on any list, so it's
// ok to iterate them without holding the mutex. We also need to not hold the
// mutex while calling CleanupAsyncWaiterPromise, since it may allocate
// memory.
HandleScope handle_scope(isolate);
while (node) {
DCHECK(node->IsAsync());
DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
DCHECK(!node->waiting_);
ResolveAsyncWaiterPromise(node);
CleanupAsyncWaiterPromise(node);
// We've already tried to cancel the timeout task for the node; since we're
// now in the same thread the timeout task is supposed to run, we know the
// timeout task will never happen, and it's safe to delete the node here.
DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
node->async_state_->timeout_task_id);
node = FutexWaitList::DeleteAsyncWaiterNode(node);
}
}
void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
// This function must run in the main thread of node's Isolate.
DCHECK(node->IsAsync());
FutexWaitList* wait_list = GetWaitList();
{
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
node->async_state_->timeout_task_id = CancelableTaskManager::kInvalidTaskId;
if (!node->waiting_) {
// If the Node is not waiting, it's already scheduled to have its Promise
// resolved. Ignore the timeout.
return;
}
wait_list->RemoveNode(node);
}
// "node" has been taken out of the lists, so it's ok to access it without
// holding the mutex. We also need to not hold the mutex while calling
// CleanupAsyncWaiterPromise, since it may allocate memory.
HandleScope handle_scope(node->async_state_->isolate_for_async_waiters);
ResolveAsyncWaiterPromise(node);
CleanupAsyncWaiterPromise(node);
delete node;
}
void FutexEmulation::IsolateDeinit(Isolate* isolate) {
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
// Iterate all locations to find nodes belonging to "isolate" and delete them.
// The Isolate is going away; don't bother cleaning up the Promises in the
// NativeContext. Also we don't need to cancel the timeout tasks, since they
// will be cancelled by Isolate::Deinit.
{
auto& location_lists = wait_list->location_lists_;
auto it = location_lists.begin();
while (it != location_lists.end()) {
FutexWaitListNode*& head = it->second.head;
FutexWaitListNode*& tail = it->second.tail;
FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
// head and tail are either both nullptr or both non-nullptr.
DCHECK_EQ(head == nullptr, tail == nullptr);
if (head == nullptr) {
it = location_lists.erase(it);
} else {
++it;
}
}
}
{
auto& isolate_map = wait_list->isolate_promises_to_resolve_;
auto it = isolate_map.find(isolate);
if (it != isolate_map.end()) {
for (FutexWaitListNode* node = it->second.head; node;) {
DCHECK(node->IsAsync());
DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
node->async_state_->timeout_task_id =
CancelableTaskManager::kInvalidTaskId;
node = FutexWaitList::DeleteAsyncWaiterNode(node);
}
isolate_map.erase(it);
}
}
wait_list->Verify();
}
int FutexEmulation::NumWaitersForTesting(Tagged<JSArrayBuffer> array_buffer,
size_t addr) {
void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
int num_waiters = 0;
auto& location_lists = wait_list->location_lists_;
auto it = location_lists.find(wait_location);
if (it == location_lists.end()) return num_waiters;
for (FutexWaitListNode* node = it->second.head; node; node = node->next_) {
if (!node->waiting_) continue;
if (node->IsAsync()) {
if (node->async_state_->backing_store.expired()) continue;
DCHECK_EQ(array_buffer->GetBackingStore(),
node->async_state_->backing_store.lock());
}
num_waiters++;
}
return num_waiters;
}
int FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
int num_waiters = 0;
for (const auto& it : wait_list->location_lists_) {
for (FutexWaitListNode* node = it.second.head; node; node = node->next_) {
if (!node->IsAsync()) continue;
if (!node->waiting_) continue;
if (node->async_state_->isolate_for_async_waiters != isolate) continue;
num_waiters++;
}
}
return num_waiters;
}
int FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
Tagged<JSArrayBuffer> array_buffer, size_t addr) {
void* wait_location = FutexWaitList::ToWaitLocation(array_buffer, addr);
FutexWaitList* wait_list = GetWaitList();
NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
int num_waiters = 0;
auto& isolate_map = wait_list->isolate_promises_to_resolve_;
for (const auto& it : isolate_map) {
for (FutexWaitListNode* node = it.second.head; node; node = node->next_) {
DCHECK(node->IsAsync());
if (node->waiting_) continue;
if (wait_location != node->wait_location_) continue;
if (node->async_state_->backing_store.expired()) continue;
DCHECK_EQ(array_buffer->GetBackingStore(),
node->async_state_->backing_store.lock());
num_waiters++;
}
}
return num_waiters;
}
void FutexWaitList::Verify() const {
#ifdef DEBUG
auto VerifyNode = [](FutexWaitListNode* node, FutexWaitListNode* head,
FutexWaitListNode* tail) {
if (node->next_ != nullptr) {
DCHECK_NE(node, tail);
DCHECK_EQ(node, node->next_->prev_);
} else {
DCHECK_EQ(node, tail);
}
if (node->prev_ != nullptr) {
DCHECK_NE(node, head);
DCHECK_EQ(node, node->prev_->next_);
} else {
DCHECK_EQ(node, head);
}
DCHECK(NodeIsOnList(node, head));
};
for (const auto& [addr, head_and_tail] : location_lists_) {
auto [head, tail] = head_and_tail;
for (FutexWaitListNode* node = head; node; node = node->next_) {
VerifyNode(node, head, tail);
}
}
for (const auto& [isolate, head_and_tail] : isolate_promises_to_resolve_) {
auto [head, tail] = head_and_tail;
for (FutexWaitListNode* node = head; node; node = node->next_) {
DCHECK(node->IsAsync());
VerifyNode(node, head, tail);
DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
}
}
#endif // DEBUG
}
bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
FutexWaitListNode* head) {
for (FutexWaitListNode* n = head; n; n = n->next_) {
if (n == node) return true;
}
return false;
}
} // namespace v8::internal