v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
futex-emulation.cc
Go to the documentation of this file.
1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <limits>
8
9#include "src/api/api-inl.h"
11#include "src/base/logging.h"
12#include "src/base/macros.h"
13#include "src/base/small-map.h"
22
23namespace v8::internal {
24
25// A {FutexWaitList} manages all contexts waiting (synchronously or
26// asynchronously) on any address.
28 public:
29 FutexWaitList() = default;
30 FutexWaitList(const FutexWaitList&) = delete;
32
33 void AddNode(FutexWaitListNode* node);
34 void RemoveNode(FutexWaitListNode* node);
35
36 static void* ToWaitLocation(Tagged<JSArrayBuffer> array_buffer, size_t addr) {
37 DCHECK_LT(addr, array_buffer->GetByteLength());
38 // Use the cheaper JSArrayBuffer::backing_store() accessor, but DCHECK that
39 // it matches the start of the JSArrayBuffer::GetBackingStore().
40 DCHECK_EQ(array_buffer->backing_store(),
41 array_buffer->GetBackingStore()->buffer_start());
42 return static_cast<uint8_t*>(array_buffer->backing_store()) + addr;
43 }
44
45 // Deletes "node" and returns the next node of its list.
47 DCHECK(node->IsAsync());
48 DCHECK_NOT_NULL(node->async_state_->isolate_for_async_waiters);
49 FutexWaitListNode* next = node->next_;
50 if (node->prev_ != nullptr) {
51 node->prev_->next_ = next;
52 }
53 if (next != nullptr) {
54 next->prev_ = node->prev_;
55 }
56 delete node;
57 return next;
58 }
59
60 static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
61 FutexWaitListNode** tail) {
62 // For updating head & tail once we've iterated all nodes.
63 FutexWaitListNode* new_head = nullptr;
64 FutexWaitListNode* new_tail = nullptr;
65 for (FutexWaitListNode* node = *head; node;) {
66 if (node->IsAsync() &&
67 node->async_state_->isolate_for_async_waiters == isolate) {
68 node->async_state_->timeout_task_id =
70 node = DeleteAsyncWaiterNode(node);
71 } else {
72 if (new_head == nullptr) {
73 new_head = node;
74 }
75 new_tail = node;
76 node = node->next_;
77 }
78 }
79 *head = new_head;
80 *tail = new_tail;
81 }
82
83 // For checking the internal consistency of the FutexWaitList.
84 void Verify() const;
85 // Returns true if |node| is on the linked list starting with |head|.
86 static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
87
88 base::Mutex* mutex() { return &mutex_; }
89
90 private:
91 friend class FutexEmulation;
92
97
98 // `mutex` protects the composition of the fields below (i.e. no elements may
99 // be added or removed without holding this mutex), as well as the `waiting_`
100 // and `interrupted_` fields for each individual list node that is currently
101 // part of the list. It must be the mutex used together with the `cond_`
102 // condition variable of such nodes.
104
105 // Location inside a shared buffer -> linked list of Nodes waiting on that
106 // location.
107 // As long as the map does not grow beyond 16 entries, there is no dynamic
108 // allocation and deallocation happening in wait or wake, which reduces the
109 // time spend in the critical section.
111
112 // Isolate* -> linked list of Nodes which are waiting for their Promises to
113 // be resolved.
115};
116
117namespace {
118
119// {GetWaitList} returns the lazily initialized global wait list.
121
122} // namespace
123
125 DCHECK(IsAsync());
126 if (async_state_->timeout_task_id == CancelableTaskManager::kInvalidTaskId) {
127 return true;
128 }
129 auto* cancelable_task_manager =
130 async_state_->isolate_for_async_waiters->cancelable_task_manager();
131 TryAbortResult return_value =
132 cancelable_task_manager->TryAbort(async_state_->timeout_task_id);
134 return return_value != TryAbortResult::kTaskRunning;
135}
136
138 DCHECK(!IsAsync());
139 // Lock the FutexEmulation mutex before notifying. We know that the mutex
140 // will have been unlocked if we are currently waiting on the condition
141 // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
142 // locked it yet. In that case, we set the interrupted_
143 // flag to true, which will be tested after the mutex locked by a future wait.
144 FutexWaitList* wait_list = GetWaitList();
145 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
146
147 // if not waiting, this will not have any effect.
149 interrupted_ = true;
150}
151
153 public:
155 : CancelableTask(isolate), isolate_(isolate) {}
156
160
161 private:
163};
164
166 public:
168 FutexWaitListNode* node)
169 : CancelableTask(cancelable_task_manager), node_(node) {}
170
174
175 private:
177};
178
180 DCHECK(node->IsAsync());
181 // This function can run in any thread.
182
183 FutexWaitList* wait_list = GetWaitList();
184 wait_list->mutex()->AssertHeld();
185
186 // Nullify the timeout time; this distinguishes timed out waiters from
187 // woken up ones.
188 node->async_state_->timeout_time = base::TimeTicks();
189
190 wait_list->RemoveNode(node);
191
192 // Schedule a task for resolving the Promise. It's still possible that the
193 // timeout task runs before the promise resolving task. In that case, the
194 // timeout task will just ignore the node.
195 auto& isolate_map = wait_list->isolate_promises_to_resolve_;
196 auto it = isolate_map.find(node->async_state_->isolate_for_async_waiters);
197 if (it == isolate_map.end()) {
198 // This Isolate doesn't have other Promises to resolve at the moment.
199 isolate_map.insert(
200 std::make_pair(node->async_state_->isolate_for_async_waiters,
201 FutexWaitList::HeadAndTail{node, node}));
202 auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
203 node->async_state_->isolate_for_async_waiters);
204 node->async_state_->task_runner->PostNonNestableTask(std::move(task));
205 } else {
206 // Add this Node into the existing list.
207 node->prev_ = it->second.tail;
208 it->second.tail->next_ = node;
209 it->second.tail = node;
210 }
211}
212
214 DCHECK_NULL(node->prev_);
215 DCHECK_NULL(node->next_);
216 auto [it, inserted] =
217 location_lists_.insert({node->wait_location_, HeadAndTail{node, node}});
218 if (!inserted) {
219 it->second.tail->next_ = node;
220 node->prev_ = it->second.tail;
221 it->second.tail = node;
222 }
223
224 Verify();
225}
226
228 if (!node->prev_ && !node->next_) {
229 // If the node was the last one on its list, delete the whole list.
230 size_t erased = location_lists_.erase(node->wait_location_);
231 DCHECK_EQ(1, erased);
232 USE(erased);
233 } else if (node->prev_ && node->next_) {
234 // If we have both a successor and a predecessor, skip the lookup in the
235 // list and just update those two nodes directly.
236 node->prev_->next_ = node->next_;
237 node->next_->prev_ = node->prev_;
238 node->prev_ = node->next_ = nullptr;
239 } else {
240 // Otherwise we have to lookup in the list to find the head and tail
241 // pointers.
242 auto it = location_lists_.find(node->wait_location_);
243 DCHECK_NE(location_lists_.end(), it);
244 DCHECK(NodeIsOnList(node, it->second.head));
245
246 if (node->prev_) {
247 DCHECK(!node->next_);
248 node->prev_->next_ = nullptr;
249 DCHECK_EQ(node, it->second.tail);
250 it->second.tail = node->prev_;
251 node->prev_ = nullptr;
252 } else {
253 DCHECK_EQ(node, it->second.head);
254 it->second.head = node->next_;
255 DCHECK(node->next_);
256 node->next_->prev_ = nullptr;
257 node->next_ = nullptr;
258 }
259 }
260
261 Verify();
262}
263
264enum WaitReturnValue : int { kOk = 0, kNotEqualValue = 1, kTimedOut = 2 };
265
266namespace {
267
268Tagged<Object> WaitJsTranslateReturn(Isolate* isolate, Tagged<Object> res) {
269 if (IsSmi(res)) {
270 int val = Smi::ToInt(res);
271 switch (val) {
273 return ReadOnlyRoots(isolate).ok_string();
275 return ReadOnlyRoots(isolate).not_equal_string();
277 return ReadOnlyRoots(isolate).timed_out_string();
278 default:
279 UNREACHABLE();
280 }
281 }
282 return res;
283}
284
285} // namespace
286
288 Isolate* isolate, WaitMode mode, DirectHandle<JSArrayBuffer> array_buffer,
289 size_t addr, int32_t value, double rel_timeout_ms) {
290 Tagged<Object> res =
291 Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
292 return WaitJsTranslateReturn(isolate, res);
293}
294
296 Isolate* isolate, WaitMode mode, DirectHandle<JSArrayBuffer> array_buffer,
297 size_t addr, int64_t value, double rel_timeout_ms) {
298 Tagged<Object> res =
299 Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
300 return WaitJsTranslateReturn(isolate, res);
301}
302
304 Isolate* isolate, DirectHandle<JSArrayBuffer> array_buffer, size_t addr,
305 int32_t value, int64_t rel_timeout_ns) {
306 return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
307 rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
308}
309
311 Isolate* isolate, DirectHandle<JSArrayBuffer> array_buffer, size_t addr,
312 int64_t value, int64_t rel_timeout_ns) {
313 return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
314 rel_timeout_ns >= 0, rel_timeout_ns, CallType::kIsWasm);
315}
316
317template <typename T>
319 DirectHandle<JSArrayBuffer> array_buffer,
320 size_t addr, T value,
321 double rel_timeout_ms) {
322 DCHECK_LT(addr, array_buffer->GetByteLength());
323
324 bool use_timeout = rel_timeout_ms != V8_INFINITY;
325 int64_t rel_timeout_ns = -1;
326
327 if (use_timeout) {
328 // Convert to nanoseconds.
329 double timeout_ns = rel_timeout_ms *
332 if (timeout_ns > static_cast<double>(std::numeric_limits<int64_t>::max())) {
333 // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
334 // infinite.
335 use_timeout = false;
336 } else {
337 rel_timeout_ns = static_cast<int64_t>(timeout_ns);
338 }
339 }
340 return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
341 rel_timeout_ns);
342}
343
344template <typename T>
346 DirectHandle<JSArrayBuffer> array_buffer,
347 size_t addr, T value, bool use_timeout,
348 int64_t rel_timeout_ns,
349 CallType call_type) {
350 if (mode == WaitMode::kSync) {
351 return WaitSync(isolate, array_buffer, addr, value, use_timeout,
352 rel_timeout_ns, call_type);
353 }
355 return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
356 rel_timeout_ns, call_type);
357}
358
359template <typename T>
361 Isolate* isolate, DirectHandle<JSArrayBuffer> array_buffer, size_t addr,
362 T value, bool use_timeout, int64_t rel_timeout_ns, CallType call_type) {
364 base::TimeDelta rel_timeout =
365 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
366
368
369 FutexWaitList* wait_list = GetWaitList();
370 FutexWaitListNode* node = isolate->futex_wait_list_node();
371 void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
372
373 base::TimeTicks timeout_time;
374 if (use_timeout) {
375 base::TimeTicks current_time = base::TimeTicks::Now();
376 timeout_time = current_time + rel_timeout;
377 }
378
379 // The following is not really a loop; the do-while construct makes it easier
380 // to break out early.
381 // Keep the code in the loop as minimal as possible, because this is all in
382 // the critical section.
383 do {
384 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
385
386 std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
387 T loaded_value = p->load();
388#if defined(V8_TARGET_BIG_ENDIAN)
389 // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
390 if (call_type == CallType::kIsWasm) {
391 DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
392 loaded_value = ByteReverse(loaded_value);
393 }
394#endif
395 if (loaded_value != value) {
396 result =
398 break;
399 }
400
401 node->wait_location_ = wait_location;
402 node->waiting_ = true;
403 wait_list->AddNode(node);
404
405 while (true) {
406 if (V8_UNLIKELY(node->interrupted_)) {
407 // Reset the interrupted flag while still holding the mutex.
408 node->interrupted_ = false;
409
410 // Unlock the mutex here to prevent deadlock from lock ordering between
411 // mutex and mutexes locked by HandleInterrupts.
412 lock_guard.Unlock();
413
414 // Because the mutex is unlocked, we have to be careful about not
415 // dropping an interrupt. The notification can happen in three different
416 // places:
417 // 1) Before Wait is called: the notification will be dropped, but
418 // interrupted_ will be set to 1. This will be checked below.
419 // 2) After interrupted has been checked here, but before mutex is
420 // acquired: interrupted is checked in a loop, with mutex locked.
421 // Because the wakeup signal also acquires mutex, we know it will not
422 // be able to notify until mutex is released below, when waiting on
423 // the condition variable.
424 // 3) After the mutex is released in the call to WaitFor(): this
425 // notification will wake up the condition variable. node->waiting()
426 // will be false, so we'll loop and then check interrupts.
427 Tagged<Object> interrupt_object =
428 isolate->stack_guard()->HandleInterrupts();
429
430 lock_guard.Lock();
431
432 if (IsException(interrupt_object, isolate)) {
433 result = direct_handle(interrupt_object, isolate);
434 break;
435 }
436 }
437
438 if (V8_UNLIKELY(node->interrupted_)) {
439 // An interrupt occurred while the mutex was unlocked. Don't wait yet.
440 continue;
441 }
442
443 if (!node->waiting_) {
444 // We were woken via Wake.
446 break;
447 }
448
449 // No interrupts, now wait.
450 if (use_timeout) {
451 base::TimeTicks current_time = base::TimeTicks::Now();
452 if (current_time >= timeout_time) {
453 result =
455 break;
456 }
457
458 base::TimeDelta time_until_timeout = timeout_time - current_time;
459 DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
460 bool wait_for_result =
461 node->cond_.WaitFor(wait_list->mutex(), time_until_timeout);
462 USE(wait_for_result);
463 } else {
464 node->cond_.Wait(wait_list->mutex());
465 }
466
467 // Spurious wakeup, interrupt or timeout.
468 }
469
470 node->waiting_ = false;
471 wait_list->RemoveNode(node);
472 } while (false);
473 DCHECK(!node->waiting_);
474
475 return *result;
476}
477
478namespace {
479template <typename T>
480Global<T> GetWeakGlobal(Isolate* isolate, Local<T> object) {
481 auto* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
482 v8::Global<T> global{v8_isolate, object};
483 global.SetWeak();
484 return global;
485}
486} // namespace
487
488FutexWaitListNode::FutexWaitListNode(std::weak_ptr<BackingStore> backing_store,
489 void* wait_location,
491 Isolate* isolate)
492 : wait_location_(wait_location),
493 waiting_(true),
494 async_state_(std::make_unique<AsyncState>(
495 isolate,
496 V8::GetCurrentPlatform()->GetForegroundTaskRunner(
497 reinterpret_cast<v8::Isolate*>(isolate)),
498 std::move(backing_store),
499 GetWeakGlobal(isolate, Utils::PromiseToLocal(promise)),
500 GetWeakGlobal(isolate, Utils::ToLocal(isolate->native_context())))) {}
501
502template <typename T>
504 Isolate* isolate, DirectHandle<JSArrayBuffer> array_buffer, size_t addr,
505 T value, bool use_timeout, int64_t rel_timeout_ns, CallType call_type) {
506 base::TimeDelta rel_timeout =
507 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
508
509 Factory* factory = isolate->factory();
511 factory->NewJSObject(isolate->object_function());
512 DirectHandle<JSObject> promise_capability = factory->NewJSPromise();
513
514 enum class ResultKind { kNotEqual, kTimedOut, kAsync };
515 ResultKind result_kind;
516 void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
517 // Get a weak pointer to the backing store, to be stored in the async state of
518 // the node.
519 std::weak_ptr<BackingStore> backing_store{array_buffer->GetBackingStore()};
520 FutexWaitList* wait_list = GetWaitList();
521 {
522 // 16. Perform EnterCriticalSection(WL).
523 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
524
525 // 17. Let w be ! AtomicLoad(typedArray, i).
526 std::atomic<T>* p = static_cast<std::atomic<T>*>(wait_location);
527 T loaded_value = p->load();
528#if defined(V8_TARGET_BIG_ENDIAN)
529 // If loading a Wasm value, it needs to be reversed on Big Endian platforms.
530 if (call_type == CallType::kIsWasm) {
531 DCHECK(sizeof(T) == kInt32Size || sizeof(T) == kInt64Size);
532 loaded_value = ByteReverse(loaded_value);
533 }
534#endif
535 if (loaded_value != value) {
536 result_kind = ResultKind::kNotEqual;
537 } else if (use_timeout && rel_timeout_ns == 0) {
538 result_kind = ResultKind::kTimedOut;
539 } else {
540 result_kind = ResultKind::kAsync;
541
543 std::move(backing_store), wait_location, promise_capability, isolate);
544
545 if (use_timeout) {
546 node->async_state_->timeout_time = base::TimeTicks::Now() + rel_timeout;
547 auto task = std::make_unique<AsyncWaiterTimeoutTask>(
548 node->async_state_->isolate_for_async_waiters
549 ->cancelable_task_manager(),
550 node);
551 node->async_state_->timeout_task_id = task->id();
552 node->async_state_->task_runner->PostNonNestableDelayedTask(
553 std::move(task), rel_timeout.InSecondsF());
554 }
555
556 wait_list->AddNode(node);
557 }
558
559 // Leaving the block collapses the following steps:
560 // 18.a. Perform LeaveCriticalSection(WL).
561 // 19.b. Perform LeaveCriticalSection(WL).
562 // 24. Perform LeaveCriticalSection(WL).
563 }
564
565 switch (result_kind) {
566 case ResultKind::kNotEqual:
567 // 18. If v is not equal to w, then
568 // ...
569 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
570 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
571 // "not-equal").
572 // e. Return resultObject.
574 isolate, result, factory->async_string(),
575 factory->false_value(), Just(kDontThrow))
576 .FromJust());
578 isolate, result, factory->value_string(),
579 factory->not_equal_string(), Just(kDontThrow))
580 .FromJust());
581 break;
582
583 case ResultKind::kTimedOut:
584 // 19. If t is 0 and mode is async, then
585 // ...
586 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
587 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
588 // "timed-out").
589 // e. Return resultObject.
591 isolate, result, factory->async_string(),
592 factory->false_value(), Just(kDontThrow))
593 .FromJust());
595 isolate, result, factory->value_string(),
596 factory->timed_out_string(), Just(kDontThrow))
597 .FromJust());
598 break;
599
600 case ResultKind::kAsync:
601 // Add the Promise into the NativeContext's atomics_waitasync_promises
602 // set, so that the list keeps it alive.
603 DirectHandle<NativeContext> native_context(isolate->native_context());
605 native_context->atomics_waitasync_promises(), isolate);
606 promises = OrderedHashSet::Add(isolate, promises, promise_capability)
607 .ToHandleChecked();
608 native_context->set_atomics_waitasync_promises(*promises);
609
610 // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
611 // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
612 // promiseCapability.[[Promise]]).
613 // 28. Return resultObject.
615 isolate, result, factory->async_string(), factory->true_value(),
617 .FromJust());
619 factory->value_string(),
620 promise_capability, Just(kDontThrow))
621 .FromJust());
622 break;
623 }
624
625 return *result;
626}
627
628int FutexEmulation::Wake(Tagged<JSArrayBuffer> array_buffer, size_t addr,
629 uint32_t num_waiters_to_wake) {
630 void* wait_location = FutexWaitList::ToWaitLocation(array_buffer, addr);
631 return Wake(wait_location, num_waiters_to_wake);
632}
633
634int FutexEmulation::Wake(void* wait_location, uint32_t num_waiters_to_wake) {
635 int num_waiters_woken = 0;
636 FutexWaitList* wait_list = GetWaitList();
637 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
638
639 auto& location_lists = wait_list->location_lists_;
640 auto it = location_lists.find(wait_location);
641 if (it == location_lists.end()) return num_waiters_woken;
642
643 FutexWaitListNode* node = it->second.head;
644 while (node && num_waiters_to_wake > 0) {
645 if (!node->waiting_) {
646 node = node->next_;
647 continue;
648 }
649 // Relying on wait_location_ here is not enough, since we need to guard
650 // against the case where the BackingStore of the node has been deleted
651 // during an async wait and a new BackingStore recreated in the same memory
652 // area. Note that sync wait always keeps the backing store alive.
653 // It is sufficient to check whether the node's backing store is expired
654 // (and consider this a non-match). If it is not expired, it must be
655 // identical to the backing store from which wait_location was computed by
656 // the caller. In that case, the current context holds the arraybuffer and
657 // backing store alive during this call, so it can not expire while we
658 // execute this code.
659 bool matching_backing_store =
660 !node->IsAsync() || !node->async_state_->backing_store.expired();
661 if (V8_LIKELY(matching_backing_store)) {
662 node->waiting_ = false;
663
664 // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
665 // since NotifyAsyncWaiter will take the node out of the linked list.
666 FutexWaitListNode* next_node = node->next_;
667 if (node->IsAsync()) {
668 NotifyAsyncWaiter(node);
669 } else {
670 // WaitSync will remove the node from the list.
671 node->cond_.NotifyOne();
672 }
673 node = next_node;
674 if (num_waiters_to_wake != kWakeAll) {
675 --num_waiters_to_wake;
676 }
677 num_waiters_woken++;
678 continue;
679 }
680
681 // ---
682 // Code below handles the unlikely case that this node's backing store was
683 // deleted during an async wait and a new one was allocated in its place.
684 // We delete the node if possible (no timeout, or context is gone).
685 // ---
686 bool delete_this_node = false;
687 DCHECK(node->IsAsync());
688 if (node->async_state_->timeout_time.IsNull()) {
689 // Backing store has been deleted and the node is still waiting, and
690 // there's no timeout. It's never going to be woken up, so we can clean it
691 // up now. We don't need to cancel the timeout task, because there is
692 // none.
693
694 // This cleanup code is not very efficient, since it only kicks in when
695 // a new BackingStore has been created in the same memory area where the
696 // deleted BackingStore was.
697 DCHECK(node->IsAsync());
699 node->async_state_->timeout_task_id);
700 delete_this_node = true;
701 }
702 if (node->async_state_->native_context.IsEmpty()) {
703 // The NativeContext related to the async waiter has been deleted.
704 // Ditto, clean up now.
705
706 // Using the CancelableTaskManager here is OK since the Isolate is
707 // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
708 // FutexWaitListNodes owned by an Isolate which is going to die.
709 if (node->CancelTimeoutTask()) {
710 delete_this_node = true;
711 }
712 // If cancelling the timeout task failed, the timeout task is already
713 // running and will clean up the node.
714 }
715
716 FutexWaitListNode* next_node = node->next_;
717 if (delete_this_node) {
718 wait_list->RemoveNode(node);
719 delete node;
720 }
721 node = next_node;
722 }
723
724 return num_waiters_woken;
725}
726
728 DCHECK(node->IsAsync());
729 // This function must run in the main thread of node's Isolate. This function
730 // may allocate memory. To avoid deadlocks, we shouldn't be holding the
731 // FutexEmulationGlobalState::mutex.
732
733 Isolate* isolate = node->async_state_->isolate_for_async_waiters;
734 auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
735
736 if (!node->async_state_->promise.IsEmpty()) {
737 auto promise = Cast<JSPromise>(
738 Utils::OpenDirectHandle(*node->async_state_->promise.Get(v8_isolate)));
739 // Promise keeps the NativeContext alive.
740 DCHECK(!node->async_state_->native_context.IsEmpty());
742 *node->async_state_->native_context.Get(v8_isolate)));
743
744 // Remove the Promise from the NativeContext's set.
746 native_context->atomics_waitasync_promises(), isolate);
747 bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
748 DCHECK(was_deleted);
749 USE(was_deleted);
750 promises = OrderedHashSet::Shrink(isolate, promises);
751 native_context->set_atomics_waitasync_promises(*promises);
752 } else {
753 // NativeContext keeps the Promise alive; if the Promise is dead then
754 // surely NativeContext is too.
755 DCHECK(node->async_state_->native_context.IsEmpty());
756 }
757}
758
760 DCHECK(node->IsAsync());
761 // This function must run in the main thread of node's Isolate.
762
763 Isolate* isolate = node->async_state_->isolate_for_async_waiters;
764 v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
765
766 // Try to cancel the timeout task (if one exists). If the timeout task exists,
767 // cancelling it will always succeed. It's not possible for the timeout task
768 // to be running, since it's scheduled to run in the same thread as this task.
769
770 // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
771 // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
772 // owned by an Isolate which is going to die.
773 bool success = node->CancelTimeoutTask();
774 DCHECK(success);
775 USE(success);
776
777 if (!node->async_state_->promise.IsEmpty()) {
778 DCHECK(!node->async_state_->native_context.IsEmpty());
780 node->async_state_->native_context.Get(v8_isolate);
783 Utils::OpenHandle(*node->async_state_->promise.Get(v8_isolate)));
784 DirectHandle<String> result_string;
785 // When waiters are notified, their timeout_time is reset. Having a
786 // non-zero timeout_time here means the waiter timed out.
787 if (node->async_state_->timeout_time != base::TimeTicks()) {
788 DCHECK(node->waiting_);
789 result_string = isolate->factory()->timed_out_string();
790 } else {
791 DCHECK(!node->waiting_);
792 result_string = isolate->factory()->ok_string();
793 }
794 MaybeDirectHandle<Object> resolve_result =
795 JSPromise::Resolve(promise, result_string);
796 DCHECK(!resolve_result.is_null());
797 USE(resolve_result);
798 }
799}
800
802 // This function must run in the main thread of isolate.
803
804 FutexWaitList* wait_list = GetWaitList();
806 {
807 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
808
809 auto& isolate_map = wait_list->isolate_promises_to_resolve_;
810 auto it = isolate_map.find(isolate);
811 DCHECK_NE(isolate_map.end(), it);
812
813 node = it->second.head;
814 isolate_map.erase(it);
815 }
816
817 // The list of nodes starting from "node" are no longer on any list, so it's
818 // ok to iterate them without holding the mutex. We also need to not hold the
819 // mutex while calling CleanupAsyncWaiterPromise, since it may allocate
820 // memory.
821 HandleScope handle_scope(isolate);
822 while (node) {
823 DCHECK(node->IsAsync());
824 DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
825 DCHECK(!node->waiting_);
828 // We've already tried to cancel the timeout task for the node; since we're
829 // now in the same thread the timeout task is supposed to run, we know the
830 // timeout task will never happen, and it's safe to delete the node here.
832 node->async_state_->timeout_task_id);
834 }
835}
836
838 // This function must run in the main thread of node's Isolate.
839 DCHECK(node->IsAsync());
840
841 FutexWaitList* wait_list = GetWaitList();
842
843 {
844 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
845
846 node->async_state_->timeout_task_id = CancelableTaskManager::kInvalidTaskId;
847 if (!node->waiting_) {
848 // If the Node is not waiting, it's already scheduled to have its Promise
849 // resolved. Ignore the timeout.
850 return;
851 }
852 wait_list->RemoveNode(node);
853 }
854
855 // "node" has been taken out of the lists, so it's ok to access it without
856 // holding the mutex. We also need to not hold the mutex while calling
857 // CleanupAsyncWaiterPromise, since it may allocate memory.
858 HandleScope handle_scope(node->async_state_->isolate_for_async_waiters);
861 delete node;
862}
863
865 FutexWaitList* wait_list = GetWaitList();
866 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
867
868 // Iterate all locations to find nodes belonging to "isolate" and delete them.
869 // The Isolate is going away; don't bother cleaning up the Promises in the
870 // NativeContext. Also we don't need to cancel the timeout tasks, since they
871 // will be cancelled by Isolate::Deinit.
872 {
873 auto& location_lists = wait_list->location_lists_;
874 auto it = location_lists.begin();
875 while (it != location_lists.end()) {
876 FutexWaitListNode*& head = it->second.head;
877 FutexWaitListNode*& tail = it->second.tail;
878 FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
879 // head and tail are either both nullptr or both non-nullptr.
880 DCHECK_EQ(head == nullptr, tail == nullptr);
881 if (head == nullptr) {
882 it = location_lists.erase(it);
883 } else {
884 ++it;
885 }
886 }
887 }
888
889 {
890 auto& isolate_map = wait_list->isolate_promises_to_resolve_;
891 auto it = isolate_map.find(isolate);
892 if (it != isolate_map.end()) {
893 for (FutexWaitListNode* node = it->second.head; node;) {
894 DCHECK(node->IsAsync());
895 DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
896 node->async_state_->timeout_task_id =
899 }
900 isolate_map.erase(it);
901 }
902 }
903
904 wait_list->Verify();
905}
906
908 size_t addr) {
909 void* wait_location = FutexWaitList::ToWaitLocation(*array_buffer, addr);
910 FutexWaitList* wait_list = GetWaitList();
911 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
912
913 int num_waiters = 0;
914 auto& location_lists = wait_list->location_lists_;
915 auto it = location_lists.find(wait_location);
916 if (it == location_lists.end()) return num_waiters;
917
918 for (FutexWaitListNode* node = it->second.head; node; node = node->next_) {
919 if (!node->waiting_) continue;
920 if (node->IsAsync()) {
921 if (node->async_state_->backing_store.expired()) continue;
922 DCHECK_EQ(array_buffer->GetBackingStore(),
923 node->async_state_->backing_store.lock());
924 }
925 num_waiters++;
926 }
927
928 return num_waiters;
929}
930
932 Tagged<JSArrayBuffer> array_buffer, size_t addr) {
933 void* wait_location = FutexWaitList::ToWaitLocation(array_buffer, addr);
934 FutexWaitList* wait_list = GetWaitList();
935 NoGarbageCollectionMutexGuard lock_guard(wait_list->mutex());
936
937 int num_waiters = 0;
938 auto& isolate_map = wait_list->isolate_promises_to_resolve_;
939 for (const auto& it : isolate_map) {
940 for (FutexWaitListNode* node = it.second.head; node; node = node->next_) {
941 DCHECK(node->IsAsync());
942 if (node->waiting_) continue;
943 if (wait_location != node->wait_location_) continue;
944 if (node->async_state_->backing_store.expired()) continue;
945 DCHECK_EQ(array_buffer->GetBackingStore(),
946 node->async_state_->backing_store.lock());
947 num_waiters++;
948 }
949 }
950
951 return num_waiters;
952}
953
955#ifdef DEBUG
956 auto VerifyNode = [](FutexWaitListNode* node, FutexWaitListNode* head,
957 FutexWaitListNode* tail) {
958 if (node->next_ != nullptr) {
959 DCHECK_NE(node, tail);
960 DCHECK_EQ(node, node->next_->prev_);
961 } else {
962 DCHECK_EQ(node, tail);
963 }
964 if (node->prev_ != nullptr) {
965 DCHECK_NE(node, head);
966 DCHECK_EQ(node, node->prev_->next_);
967 } else {
968 DCHECK_EQ(node, head);
969 }
970
971 DCHECK(NodeIsOnList(node, head));
972 };
973
974 for (const auto& [addr, head_and_tail] : location_lists_) {
975 auto [head, tail] = head_and_tail;
976 for (FutexWaitListNode* node = head; node; node = node->next_) {
977 VerifyNode(node, head, tail);
978 }
979 }
980
981 for (const auto& [isolate, head_and_tail] : isolate_promises_to_resolve_) {
982 auto [head, tail] = head_and_tail;
983 for (FutexWaitListNode* node = head; node; node = node->next_) {
984 DCHECK(node->IsAsync());
985 VerifyNode(node, head, tail);
986 DCHECK_EQ(isolate, node->async_state_->isolate_for_async_waiters);
987 }
988 }
989#endif // DEBUG
990}
991
993 FutexWaitListNode* head) {
994 for (FutexWaitListNode* n = head; n; n = n->next_) {
995 if (n == node) return true;
996 }
997 return false;
998}
999
1000} // namespace v8::internal
V8_INLINE void SetWeak(P *parameter, typename WeakCallbackInfo< P >::Callback callback, WeakCallbackType type)
static v8::internal::Handle< To > OpenHandle(v8::Local< From > handle)
Definition api.h:274
static v8::internal::DirectHandle< To > OpenDirectHandle(v8::Local< From > handle)
Definition api.h:279
V8_INLINE void AssertHeld() const
Definition mutex.h:58
static constexpr int64_t kNanosecondsPerMicrosecond
Definition time.h:56
static constexpr int64_t kMicrosecondsPerMillisecond
Definition time.h:48
static constexpr TimeDelta FromNanoseconds(int64_t nanoseconds)
Definition time.h:90
double InSecondsF() const
Definition time.cc:210
int64_t InMicroseconds() const
Definition time.cc:251
static TimeTicks Now()
Definition time.cc:736
AsyncWaiterTimeoutTask(CancelableTaskManager *cancelable_task_manager, FutexWaitListNode *node)
Handle< JSObject > NewJSObject(DirectHandle< JSFunction > constructor, AllocationType allocation=AllocationType::kYoung, NewJSObjectType=NewJSObjectType::kNoAPIWrapper)
Definition factory.cc:2985
Handle< JSPromise > NewJSPromise()
Definition factory.cc:4526
static void HandleAsyncWaiterTimeout(FutexWaitListNode *node)
static Tagged< Object > WaitSync(Isolate *isolate, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, T value, bool use_timeout, int64_t rel_timeout_ns, CallType call_type)
static Tagged< Object > Wait(Isolate *isolate, WaitMode mode, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, T value, double rel_timeout_ms)
static int NumUnresolvedAsyncPromisesForTesting(Tagged< JSArrayBuffer > array_buffer, size_t addr)
static int NumWaitersForTesting(Tagged< JSArrayBuffer > array_buffer, size_t addr)
static V8_EXPORT_PRIVATE Tagged< Object > WaitWasm64(Isolate *isolate, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, int64_t value, int64_t rel_timeout_ns)
static const uint32_t kWakeAll
static void NotifyAsyncWaiter(FutexWaitListNode *node)
static Tagged< Object > WaitAsync(Isolate *isolate, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, T value, bool use_timeout, int64_t rel_timeout_ns, CallType call_type)
static Tagged< Object > WaitJs64(Isolate *isolate, WaitMode mode, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, int64_t value, double rel_timeout_ms)
static Tagged< Object > WaitJs32(Isolate *isolate, WaitMode mode, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, int32_t value, double rel_timeout_ms)
static V8_EXPORT_PRIVATE Tagged< Object > WaitWasm32(Isolate *isolate, DirectHandle< JSArrayBuffer > array_buffer, size_t addr, int32_t value, int64_t rel_timeout_ns)
static void IsolateDeinit(Isolate *isolate)
static void ResolveAsyncWaiterPromise(FutexWaitListNode *node)
static void CleanupAsyncWaiterPromise(FutexWaitListNode *node)
static V8_EXPORT_PRIVATE int Wake(Tagged< JSArrayBuffer > array_buffer, size_t addr, uint32_t num_waiters_to_wake)
static void ResolveAsyncWaiterPromises(Isolate *isolate)
base::ConditionVariable cond_
const std::unique_ptr< AsyncState > async_state_
void AddNode(FutexWaitListNode *node)
static FutexWaitListNode * DeleteAsyncWaiterNode(FutexWaitListNode *node)
void RemoveNode(FutexWaitListNode *node)
static void DeleteNodesForIsolate(Isolate *isolate, FutexWaitListNode **head, FutexWaitListNode **tail)
static bool NodeIsOnList(FutexWaitListNode *node, FutexWaitListNode *head)
static void * ToWaitLocation(Tagged< JSArrayBuffer > array_buffer, size_t addr)
base::SmallMap< std::map< void *, HeadAndTail >, 16 > location_lists_
FutexWaitList & operator=(const FutexWaitList &)=delete
base::SmallMap< std::map< Isolate *, HeadAndTail > > isolate_promises_to_resolve_
FutexWaitList(const FutexWaitList &)=delete
static V8_WARN_UNUSED_RESULT MaybeHandle< Object > Resolve(DirectHandle< JSPromise > promise, DirectHandle< Object > resolution)
Definition objects.cc:5109
static V8_WARN_UNUSED_RESULT Maybe< bool > CreateDataProperty(Isolate *isolate, DirectHandle< JSReceiver > object, DirectHandle< Name > key, DirectHandle< Object > value, Maybe< ShouldThrow > should_throw)
V8_INLINE bool is_null() const
static HandleType< OrderedHashSet >::MaybeType Add(Isolate *isolate, HandleType< OrderedHashSet > table, DirectHandle< Object > value)
static HandleType< OrderedHashSet > Shrink(Isolate *isolate, HandleType< OrderedHashSet > table)
static bool Delete(Isolate *isolate, Tagged< OrderedHashSet > table, Tagged< Object > key)
static constexpr int ToInt(const Tagged< Object > object)
Definition smi.h:33
static constexpr Tagged< Smi > FromInt(int value)
Definition smi.h:38
#define V8_INFINITY
Definition globals.h:23
Isolate * isolate
Node * node
ZoneVector< RpoNumber > & result
#define DEFINE_LAZY_LEAKY_OBJECT_GETTER(T, FunctionName,...)
LiftoffAssembler::CacheState state
int n
Definition mul-fft.cc:296
STL namespace.
constexpr int kInt64Size
Definition globals.h:402
V8_INLINE constexpr bool IsSmi(TaggedImpl< kRefType, StorageType > obj)
Definition objects.h:665
V8_INLINE DirectHandle< T > direct_handle(Tagged< T > object, Isolate *isolate)
constexpr int kInt32Size
Definition globals.h:401
static V ByteReverse(V value)
Definition utils.h:796
!IsContextMap !IsContextMap native_context
Definition map-inl.h:877
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
bool ToLocal(v8::internal::MaybeDirectHandle< v8::internal::Object > maybe, Local< T > *local)
Definition api.h:303
Maybe< T > Just(const T &t)
Definition v8-maybe.h:117
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK_GE(v1, v2)
Definition logging.h:488
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define USE(...)
Definition macros.h:293
#define V8_LIKELY(condition)
Definition v8config.h:661
#define V8_UNLIKELY(condition)
Definition v8config.h:660