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
5 #include "src/execution/futex-emulation.h"
6
7 #include <limits>
8
9 #include "src/api/api-inl.h"
10 #include "src/base/logging.h"
11 #include "src/base/macros.h"
12 #include "src/execution/isolate.h"
13 #include "src/execution/vm-state-inl.h"
14 #include "src/handles/handles-inl.h"
15 #include "src/numbers/conversions.h"
16 #include "src/objects/bigint.h"
17 #include "src/objects/js-array-buffer-inl.h"
18 #include "src/objects/js-promise-inl.h"
19 #include "src/objects/objects-inl.h"
20 #include "src/tasks/cancelable-task.h"
21
22 namespace v8 {
23 namespace internal {
24
25 using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;
26
27 class FutexWaitList {
28 public:
29 FutexWaitList() = default;
30
31 void AddNode(FutexWaitListNode* node);
32 void RemoveNode(FutexWaitListNode* node);
33
ToWaitLocation(const BackingStore * backing_store,size_t addr)34 static int8_t* ToWaitLocation(const BackingStore* backing_store,
35 size_t addr) {
36 return static_cast<int8_t*>(backing_store->buffer_start()) + addr;
37 }
38
39 // Deletes "node" and returns the next node of its list.
DeleteAsyncWaiterNode(FutexWaitListNode * node)40 static FutexWaitListNode* DeleteAsyncWaiterNode(FutexWaitListNode* node) {
41 DCHECK_NOT_NULL(node->isolate_for_async_waiters_);
42 auto next = node->next_;
43 if (node->prev_ != nullptr) {
44 node->prev_->next_ = next;
45 }
46 if (next != nullptr) {
47 next->prev_ = node->prev_;
48 }
49 delete node;
50 return next;
51 }
52
DeleteNodesForIsolate(Isolate * isolate,FutexWaitListNode ** head,FutexWaitListNode ** tail)53 static void DeleteNodesForIsolate(Isolate* isolate, FutexWaitListNode** head,
54 FutexWaitListNode** tail) {
55 // For updating head & tail once we've iterated all nodes.
56 FutexWaitListNode* new_head = nullptr;
57 FutexWaitListNode* new_tail = nullptr;
58 auto node = *head;
59 while (node != nullptr) {
60 if (node->isolate_for_async_waiters_ == isolate) {
61 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
62 node = DeleteAsyncWaiterNode(node);
63 } else {
64 if (new_head == nullptr) {
65 new_head = node;
66 }
67 new_tail = node;
68 node = node->next_;
69 }
70 }
71 *head = new_head;
72 *tail = new_tail;
73 }
74
75 // For checking the internal consistency of the FutexWaitList.
76 void Verify();
77 // Verifies the local consistency of |node|. If it's the first node of its
78 // list, it must be |head|, and if it's the last node, it must be |tail|.
79 void VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
80 FutexWaitListNode* tail);
81 // Returns true if |node| is on the linked list starting with |head|.
82 static bool NodeIsOnList(FutexWaitListNode* node, FutexWaitListNode* head);
83
84 private:
85 friend class FutexEmulation;
86
87 struct HeadAndTail {
88 FutexWaitListNode* head;
89 FutexWaitListNode* tail;
90 };
91 // Location inside a shared buffer -> linked list of Nodes waiting on that
92 // location.
93 std::map<int8_t*, HeadAndTail> location_lists_;
94
95 // Isolate* -> linked list of Nodes which are waiting for their Promises to
96 // be resolved.
97 std::map<Isolate*, HeadAndTail> isolate_promises_to_resolve_;
98
99 DISALLOW_COPY_AND_ASSIGN(FutexWaitList);
100 };
101
102 namespace {
103 // `g_mutex` protects the composition of `g_wait_list` (i.e. no elements may be
104 // added or removed without holding this mutex), as well as the `waiting_`
105 // and `interrupted_` fields for each individual list node that is currently
106 // part of the list. It must be the mutex used together with the `cond_`
107 // condition variable of such nodes.
108 base::LazyMutex g_mutex = LAZY_MUTEX_INITIALIZER;
109 base::LazyInstance<FutexWaitList>::type g_wait_list = LAZY_INSTANCE_INITIALIZER;
110 } // namespace
111
~FutexWaitListNode()112 FutexWaitListNode::~FutexWaitListNode() {
113 // Assert that the timeout task was cancelled.
114 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, timeout_task_id_);
115 }
116
CancelTimeoutTask()117 bool FutexWaitListNode::CancelTimeoutTask() {
118 if (timeout_task_id_ != CancelableTaskManager::kInvalidTaskId) {
119 auto return_value = cancelable_task_manager_->TryAbort(timeout_task_id_);
120 timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
121 return return_value != TryAbortResult::kTaskRunning;
122 }
123 return true;
124 }
125
NotifyWake()126 void FutexWaitListNode::NotifyWake() {
127 DCHECK(!IsAsync());
128 // Lock the FutexEmulation mutex before notifying. We know that the mutex
129 // will have been unlocked if we are currently waiting on the condition
130 // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
131 // locked it yet. In that case, we set the interrupted_
132 // flag to true, which will be tested after the mutex locked by a future wait.
133 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
134
135 // if not waiting, this will not have any effect.
136 cond_.NotifyOne();
137 interrupted_ = true;
138 }
139
140 class ResolveAsyncWaiterPromisesTask : public CancelableTask {
141 public:
ResolveAsyncWaiterPromisesTask(CancelableTaskManager * cancelable_task_manager,Isolate * isolate)142 ResolveAsyncWaiterPromisesTask(CancelableTaskManager* cancelable_task_manager,
143 Isolate* isolate)
144 : CancelableTask(cancelable_task_manager), isolate_(isolate) {}
145
RunInternal()146 void RunInternal() override {
147 FutexEmulation::ResolveAsyncWaiterPromises(isolate_);
148 }
149
150 private:
151 Isolate* isolate_;
152 };
153
154 class AsyncWaiterTimeoutTask : public CancelableTask {
155 public:
AsyncWaiterTimeoutTask(CancelableTaskManager * cancelable_task_manager,FutexWaitListNode * node)156 AsyncWaiterTimeoutTask(CancelableTaskManager* cancelable_task_manager,
157 FutexWaitListNode* node)
158 : CancelableTask(cancelable_task_manager), node_(node) {}
159
RunInternal()160 void RunInternal() override {
161 FutexEmulation::HandleAsyncWaiterTimeout(node_);
162 }
163
164 private:
165 FutexWaitListNode* node_;
166 };
167
NotifyAsyncWaiter(FutexWaitListNode * node)168 void FutexEmulation::NotifyAsyncWaiter(FutexWaitListNode* node) {
169 // This function can run in any thread.
170
171 g_mutex.Pointer()->AssertHeld();
172
173 // Nullify the timeout time; this distinguishes timed out waiters from
174 // woken up ones.
175 node->async_timeout_time_ = base::TimeTicks();
176
177 g_wait_list.Pointer()->RemoveNode(node);
178
179 // Schedule a task for resolving the Promise. It's still possible that the
180 // timeout task runs before the promise resolving task. In that case, the
181 // timeout task will just ignore the node.
182 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
183 auto it = isolate_map.find(node->isolate_for_async_waiters_);
184 if (it == isolate_map.end()) {
185 // This Isolate doesn't have other Promises to resolve at the moment.
186 isolate_map.insert(std::make_pair(node->isolate_for_async_waiters_,
187 FutexWaitList::HeadAndTail{node, node}));
188 auto task = std::make_unique<ResolveAsyncWaiterPromisesTask>(
189 node->cancelable_task_manager_, node->isolate_for_async_waiters_);
190 node->task_runner_->PostNonNestableTask(std::move(task));
191 } else {
192 // Add this Node into the existing list.
193 node->prev_ = it->second.tail;
194 it->second.tail->next_ = node;
195 it->second.tail = node;
196 }
197 }
198
AddNode(FutexWaitListNode * node)199 void FutexWaitList::AddNode(FutexWaitListNode* node) {
200 DCHECK_NULL(node->prev_);
201 DCHECK_NULL(node->next_);
202 auto it = location_lists_.find(node->wait_location_);
203 if (it == location_lists_.end()) {
204 location_lists_.insert(
205 std::make_pair(node->wait_location_, HeadAndTail{node, node}));
206 } else {
207 it->second.tail->next_ = node;
208 node->prev_ = it->second.tail;
209 it->second.tail = node;
210 }
211
212 Verify();
213 }
214
RemoveNode(FutexWaitListNode * node)215 void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
216 auto it = location_lists_.find(node->wait_location_);
217 DCHECK_NE(location_lists_.end(), it);
218 DCHECK(NodeIsOnList(node, it->second.head));
219
220 if (node->prev_) {
221 node->prev_->next_ = node->next_;
222 } else {
223 DCHECK_EQ(node, it->second.head);
224 it->second.head = node->next_;
225 }
226
227 if (node->next_) {
228 node->next_->prev_ = node->prev_;
229 } else {
230 DCHECK_EQ(node, it->second.tail);
231 it->second.tail = node->prev_;
232 }
233
234 // If the node was the last one on its list, delete the whole list.
235 if (node->prev_ == nullptr && node->next_ == nullptr) {
236 location_lists_.erase(it);
237 }
238
239 node->prev_ = node->next_ = nullptr;
240
241 Verify();
242 }
243
Wake()244 void AtomicsWaitWakeHandle::Wake() {
245 // Adding a separate `NotifyWake()` variant that doesn't acquire the lock
246 // itself would likely just add unnecessary complexity..
247 // The split lock by itself isn’t an issue, as long as the caller properly
248 // synchronizes this with the closing `AtomicsWaitCallback`.
249 {
250 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
251 stopped_ = true;
252 }
253 isolate_->futex_wait_list_node()->NotifyWake();
254 }
255
256 enum WaitReturnValue : int { kOk = 0, kNotEqual = 1, kTimedOut = 2 };
257
258 namespace {
259
WaitJsTranslateReturn(Isolate * isolate,Object res)260 Object WaitJsTranslateReturn(Isolate* isolate, Object res) {
261 if (res.IsSmi()) {
262 int val = Smi::ToInt(res);
263 switch (val) {
264 case WaitReturnValue::kOk:
265 return ReadOnlyRoots(isolate).ok_string();
266 case WaitReturnValue::kNotEqual:
267 return ReadOnlyRoots(isolate).not_equal_string();
268 case WaitReturnValue::kTimedOut:
269 return ReadOnlyRoots(isolate).timed_out_string();
270 default:
271 UNREACHABLE();
272 }
273 }
274 return res;
275 }
276
277 } // namespace
278
WaitJs32(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,double rel_timeout_ms)279 Object FutexEmulation::WaitJs32(Isolate* isolate, WaitMode mode,
280 Handle<JSArrayBuffer> array_buffer, size_t addr,
281 int32_t value, double rel_timeout_ms) {
282 Object res =
283 Wait<int32_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
284 return WaitJsTranslateReturn(isolate, res);
285 }
286
WaitJs64(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,double rel_timeout_ms)287 Object FutexEmulation::WaitJs64(Isolate* isolate, WaitMode mode,
288 Handle<JSArrayBuffer> array_buffer, size_t addr,
289 int64_t value, double rel_timeout_ms) {
290 Object res =
291 Wait<int64_t>(isolate, mode, array_buffer, addr, value, rel_timeout_ms);
292 return WaitJsTranslateReturn(isolate, res);
293 }
294
WaitWasm32(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int32_t value,int64_t rel_timeout_ns)295 Object FutexEmulation::WaitWasm32(Isolate* isolate,
296 Handle<JSArrayBuffer> array_buffer,
297 size_t addr, int32_t value,
298 int64_t rel_timeout_ns) {
299 return Wait<int32_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
300 rel_timeout_ns >= 0, rel_timeout_ns);
301 }
302
WaitWasm64(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,int64_t value,int64_t rel_timeout_ns)303 Object FutexEmulation::WaitWasm64(Isolate* isolate,
304 Handle<JSArrayBuffer> array_buffer,
305 size_t addr, int64_t value,
306 int64_t rel_timeout_ns) {
307 return Wait<int64_t>(isolate, WaitMode::kSync, array_buffer, addr, value,
308 rel_timeout_ns >= 0, rel_timeout_ns);
309 }
310
311 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,double rel_timeout_ms)312 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
313 Handle<JSArrayBuffer> array_buffer, size_t addr,
314 T value, double rel_timeout_ms) {
315 DCHECK_LT(addr, array_buffer->byte_length());
316
317 bool use_timeout = rel_timeout_ms != V8_INFINITY;
318 int64_t rel_timeout_ns = -1;
319
320 if (use_timeout) {
321 // Convert to nanoseconds.
322 double timeout_ns = rel_timeout_ms *
323 base::Time::kNanosecondsPerMicrosecond *
324 base::Time::kMicrosecondsPerMillisecond;
325 if (timeout_ns > static_cast<double>(std::numeric_limits<int64_t>::max())) {
326 // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
327 // infinite.
328 use_timeout = false;
329 } else {
330 rel_timeout_ns = static_cast<int64_t>(timeout_ns);
331 }
332 }
333 return Wait(isolate, mode, array_buffer, addr, value, use_timeout,
334 rel_timeout_ns);
335 }
336
337 namespace {
WaitTimeoutInMs(double timeout_ns)338 double WaitTimeoutInMs(double timeout_ns) {
339 return timeout_ns < 0
340 ? V8_INFINITY
341 : timeout_ns / (base::Time::kNanosecondsPerMicrosecond *
342 base::Time::kMicrosecondsPerMillisecond);
343 }
344 } // namespace
345
346 template <typename T>
Wait(Isolate * isolate,WaitMode mode,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns)347 Object FutexEmulation::Wait(Isolate* isolate, WaitMode mode,
348 Handle<JSArrayBuffer> array_buffer, size_t addr,
349 T value, bool use_timeout, int64_t rel_timeout_ns) {
350 if (mode == WaitMode::kSync) {
351 return WaitSync(isolate, array_buffer, addr, value, use_timeout,
352 rel_timeout_ns);
353 }
354 DCHECK_EQ(mode, WaitMode::kAsync);
355 return WaitAsync(isolate, array_buffer, addr, value, use_timeout,
356 rel_timeout_ns);
357 }
358
359 template <typename T>
WaitSync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns)360 Object FutexEmulation::WaitSync(Isolate* isolate,
361 Handle<JSArrayBuffer> array_buffer, size_t addr,
362 T value, bool use_timeout,
363 int64_t rel_timeout_ns) {
364 VMState<ATOMICS_WAIT> state(isolate);
365 base::TimeDelta rel_timeout =
366 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
367
368 // We have to convert the timeout back to double for the AtomicsWaitCallback.
369 double rel_timeout_ms = WaitTimeoutInMs(static_cast<double>(rel_timeout_ns));
370 AtomicsWaitWakeHandle stop_handle(isolate);
371
372 isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
373 addr, value, rel_timeout_ms, &stop_handle);
374
375 if (isolate->has_scheduled_exception()) {
376 return isolate->PromoteScheduledException();
377 }
378
379 Handle<Object> result;
380 AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
381
382 do { // Not really a loop, just makes it easier to break out early.
383 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
384
385 std::shared_ptr<BackingStore> backing_store =
386 array_buffer->GetBackingStore();
387 DCHECK(backing_store);
388 FutexWaitListNode* node = isolate->futex_wait_list_node();
389 node->backing_store_ = backing_store;
390 node->wait_addr_ = addr;
391 auto wait_location =
392 FutexWaitList::ToWaitLocation(backing_store.get(), addr);
393 node->wait_location_ = wait_location;
394 node->waiting_ = true;
395
396 // Reset node->waiting_ = false when leaving this scope (but while
397 // still holding the lock).
398 FutexWaitListNode::ResetWaitingOnScopeExit reset_waiting(node);
399
400 std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(wait_location);
401 if (p->load() != value) {
402 result = handle(Smi::FromInt(WaitReturnValue::kNotEqual), isolate);
403 callback_result = AtomicsWaitEvent::kNotEqual;
404 break;
405 }
406
407 base::TimeTicks timeout_time;
408 base::TimeTicks current_time;
409
410 if (use_timeout) {
411 current_time = base::TimeTicks::Now();
412 timeout_time = current_time + rel_timeout;
413 }
414
415 g_wait_list.Pointer()->AddNode(node);
416
417 while (true) {
418 bool interrupted = node->interrupted_;
419 node->interrupted_ = false;
420
421 // Unlock the mutex here to prevent deadlock from lock ordering between
422 // mutex and mutexes locked by HandleInterrupts.
423 lock_guard.Unlock();
424
425 // Because the mutex is unlocked, we have to be careful about not dropping
426 // an interrupt. The notification can happen in three different places:
427 // 1) Before Wait is called: the notification will be dropped, but
428 // interrupted_ will be set to 1. This will be checked below.
429 // 2) After interrupted has been checked here, but before mutex is
430 // acquired: interrupted is checked again below, with mutex locked.
431 // Because the wakeup signal also acquires mutex, we know it will not
432 // be able to notify until mutex is released below, when waiting on
433 // the condition variable.
434 // 3) After the mutex is released in the call to WaitFor(): this
435 // notification will wake up the condition variable. node->waiting() will
436 // be false, so we'll loop and then check interrupts.
437 if (interrupted) {
438 Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
439 if (interrupt_object.IsException(isolate)) {
440 result = handle(interrupt_object, isolate);
441 callback_result = AtomicsWaitEvent::kTerminatedExecution;
442 lock_guard.Lock();
443 break;
444 }
445 }
446
447 lock_guard.Lock();
448
449 if (node->interrupted_) {
450 // An interrupt occurred while the mutex was unlocked. Don't wait yet.
451 continue;
452 }
453
454 if (stop_handle.has_stopped()) {
455 node->waiting_ = false;
456 callback_result = AtomicsWaitEvent::kAPIStopped;
457 }
458
459 if (!node->waiting_) {
460 result = handle(Smi::FromInt(WaitReturnValue::kOk), isolate);
461 break;
462 }
463
464 // No interrupts, now wait.
465 if (use_timeout) {
466 current_time = base::TimeTicks::Now();
467 if (current_time >= timeout_time) {
468 result = handle(Smi::FromInt(WaitReturnValue::kTimedOut), isolate);
469 callback_result = AtomicsWaitEvent::kTimedOut;
470 break;
471 }
472
473 base::TimeDelta time_until_timeout = timeout_time - current_time;
474 DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
475 bool wait_for_result =
476 node->cond_.WaitFor(g_mutex.Pointer(), time_until_timeout);
477 USE(wait_for_result);
478 } else {
479 node->cond_.Wait(g_mutex.Pointer());
480 }
481
482 // Spurious wakeup, interrupt or timeout.
483 }
484
485 g_wait_list.Pointer()->RemoveNode(node);
486 } while (false);
487
488 isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
489 rel_timeout_ms, nullptr);
490
491 if (isolate->has_scheduled_exception()) {
492 CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
493 result = handle(isolate->PromoteScheduledException(), isolate);
494 }
495
496 return *result;
497 }
498
FutexWaitListNode(const std::shared_ptr<BackingStore> & backing_store,size_t wait_addr,Handle<JSObject> promise,Isolate * isolate)499 FutexWaitListNode::FutexWaitListNode(
500 const std::shared_ptr<BackingStore>& backing_store, size_t wait_addr,
501 Handle<JSObject> promise, Isolate* isolate)
502 : isolate_for_async_waiters_(isolate),
503 backing_store_(backing_store),
504 wait_addr_(wait_addr),
505 wait_location_(
506 FutexWaitList::ToWaitLocation(backing_store.get(), wait_addr)),
507 waiting_(true) {
508 auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
509 task_runner_ = V8::GetCurrentPlatform()->GetForegroundTaskRunner(v8_isolate);
510 cancelable_task_manager_ = isolate->cancelable_task_manager();
511
512 v8::Local<v8::Promise> local_promise = Utils::PromiseToLocal(promise);
513 promise_.Reset(v8_isolate, local_promise);
514 promise_.SetWeak();
515 Handle<NativeContext> native_context(isolate->native_context());
516 v8::Local<v8::Context> local_native_context =
517 Utils::ToLocal(Handle<Context>::cast(native_context));
518 native_context_.Reset(v8_isolate, local_native_context);
519 native_context_.SetWeak();
520
521 // Add the Promise into the NativeContext's atomics_waitasync_promises set, so
522 // that the list keeps it alive.
523 Handle<OrderedHashSet> promises(native_context->atomics_waitasync_promises(),
524 isolate);
525 promises = OrderedHashSet::Add(isolate, promises, promise).ToHandleChecked();
526 native_context->set_atomics_waitasync_promises(*promises);
527 }
528
529 template <typename T>
WaitAsync(Isolate * isolate,Handle<JSArrayBuffer> array_buffer,size_t addr,T value,bool use_timeout,int64_t rel_timeout_ns)530 Object FutexEmulation::WaitAsync(Isolate* isolate,
531 Handle<JSArrayBuffer> array_buffer,
532 size_t addr, T value, bool use_timeout,
533 int64_t rel_timeout_ns) {
534 DCHECK(FLAG_harmony_atomics_waitasync);
535 base::TimeDelta rel_timeout =
536 base::TimeDelta::FromNanoseconds(rel_timeout_ns);
537
538 Factory* factory = isolate->factory();
539 Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
540
541 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
542
543 // 17. Let w be ! AtomicLoad(typedArray, i).
544 std::atomic<T>* p = reinterpret_cast<std::atomic<T>*>(
545 static_cast<int8_t*>(backing_store->buffer_start()) + addr);
546 if (p->load() != value) {
547 // 18. If v is not equal to w, then
548 // a. Perform LeaveCriticalSection(WL).
549 // ...
550 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
551 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
552 // "not-equal").
553 // e. Return resultObject.
554 CHECK(
555 JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
556 factory->false_value(), Just(kDontThrow))
557 .FromJust());
558 CHECK(JSReceiver::CreateDataProperty(
559 isolate, result, factory->value_string(),
560 factory->not_equal_string(), Just(kDontThrow))
561 .FromJust());
562 return *result;
563 }
564
565 if (use_timeout && rel_timeout_ns == 0) {
566 // 19. If t is 0 and mode is async, then
567 // ...
568 // b. Perform LeaveCriticalSection(WL).
569 // c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
570 // d. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
571 // "timed-out").
572 // e. Return resultObject.
573 CHECK(
574 JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
575 factory->false_value(), Just(kDontThrow))
576 .FromJust());
577 CHECK(JSReceiver::CreateDataProperty(
578 isolate, result, factory->value_string(),
579 factory->timed_out_string(), Just(kDontThrow))
580 .FromJust());
581 return *result;
582 }
583
584 Handle<JSObject> promise_capability = factory->NewJSPromise();
585 FutexWaitListNode* node =
586 new FutexWaitListNode(backing_store, addr, promise_capability, isolate);
587
588 {
589 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
590 g_wait_list.Pointer()->AddNode(node);
591 }
592 if (use_timeout) {
593 node->async_timeout_time_ = base::TimeTicks::Now() + rel_timeout;
594 auto task = std::make_unique<AsyncWaiterTimeoutTask>(
595 node->cancelable_task_manager_, node);
596 node->timeout_task_id_ = task->id();
597 node->task_runner_->PostNonNestableDelayedTask(std::move(task),
598 rel_timeout.InSecondsF());
599 }
600
601 // 26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
602 // 27. Perform ! CreateDataPropertyOrThrow(resultObject, "value",
603 // promiseCapability.[[Promise]]).
604 // 28. Return resultObject.
605 CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->async_string(),
606 factory->true_value(), Just(kDontThrow))
607 .FromJust());
608 CHECK(JSReceiver::CreateDataProperty(isolate, result, factory->value_string(),
609 promise_capability, Just(kDontThrow))
610 .FromJust());
611 return *result;
612 }
613
Wake(Handle<JSArrayBuffer> array_buffer,size_t addr,uint32_t num_waiters_to_wake)614 Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
615 uint32_t num_waiters_to_wake) {
616 DCHECK_LT(addr, array_buffer->byte_length());
617
618 int waiters_woken = 0;
619 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
620 auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
621
622 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
623
624 auto& location_lists = g_wait_list.Pointer()->location_lists_;
625 auto it = location_lists.find(wait_location);
626 if (it == location_lists.end()) {
627 return Smi::zero();
628 }
629 FutexWaitListNode* node = it->second.head;
630 while (node && num_waiters_to_wake > 0) {
631 bool delete_this_node = false;
632 std::shared_ptr<BackingStore> node_backing_store =
633 node->backing_store_.lock();
634
635 if (!node->waiting_) {
636 node = node->next_;
637 continue;
638 }
639 // Relying on wait_location_ here is not enough, since we need to guard
640 // against the case where the BackingStore of the node has been deleted and
641 // a new BackingStore recreated in the same memory area.
642 if (backing_store.get() == node_backing_store.get()) {
643 DCHECK_EQ(addr, node->wait_addr_);
644 node->waiting_ = false;
645
646 // Retrieve the next node to iterate before calling NotifyAsyncWaiter,
647 // since NotifyAsyncWaiter will take the node out of the linked list.
648 auto old_node = node;
649 node = node->next_;
650 if (old_node->IsAsync()) {
651 NotifyAsyncWaiter(old_node);
652 } else {
653 // WaitSync will remove the node from the list.
654 old_node->cond_.NotifyOne();
655 }
656 if (num_waiters_to_wake != kWakeAll) {
657 --num_waiters_to_wake;
658 }
659 waiters_woken++;
660 continue;
661 }
662 DCHECK_EQ(nullptr, node_backing_store.get());
663 if (node->async_timeout_time_ == base::TimeTicks()) {
664 // Backing store has been deleted and the node is still waiting, and
665 // there's no timeout. It's never going to be woken up, so we can clean
666 // it up now. We don't need to cancel the timeout task, because there is
667 // none.
668
669 // This cleanup code is not very efficient, since it only kicks in when
670 // a new BackingStore has been created in the same memory area where the
671 // deleted BackingStore was.
672 DCHECK(node->IsAsync());
673 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
674 delete_this_node = true;
675 }
676 if (node->IsAsync() && node->native_context_.IsEmpty()) {
677 // The NativeContext related to the async waiter has been deleted.
678 // Ditto, clean up now.
679
680 // Using the CancelableTaskManager here is OK since the Isolate is
681 // guaranteed to be alive - FutexEmulation::IsolateDeinit removes all
682 // FutexWaitListNodes owned by an Isolate which is going to die.
683 if (node->CancelTimeoutTask()) {
684 delete_this_node = true;
685 }
686 // If cancelling the timeout task failed, the timeout task is already
687 // running and will clean up the node.
688 }
689
690 if (delete_this_node) {
691 auto old_node = node;
692 node = node->next_;
693 g_wait_list.Pointer()->RemoveNode(old_node);
694 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId,
695 old_node->timeout_task_id_);
696 delete old_node;
697 } else {
698 node = node->next_;
699 }
700 }
701
702 return Smi::FromInt(waiters_woken);
703 }
704
CleanupAsyncWaiterPromise(FutexWaitListNode * node)705 void FutexEmulation::CleanupAsyncWaiterPromise(FutexWaitListNode* node) {
706 // This function must run in the main thread of node's Isolate. This function
707 // may allocate memory. To avoid deadlocks, we shouldn't be holding g_mutex.
708
709 DCHECK(FLAG_harmony_atomics_waitasync);
710 DCHECK(node->IsAsync());
711
712 Isolate* isolate = node->isolate_for_async_waiters_;
713 auto v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
714
715 if (!node->promise_.IsEmpty()) {
716 Handle<JSPromise> promise = Handle<JSPromise>::cast(
717 Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
718 // Promise keeps the NativeContext alive.
719 DCHECK(!node->native_context_.IsEmpty());
720 Handle<NativeContext> native_context = Handle<NativeContext>::cast(
721 Utils::OpenHandle(*node->native_context_.Get(v8_isolate)));
722
723 // Remove the Promise from the NativeContext's set.
724 Handle<OrderedHashSet> promises(
725 native_context->atomics_waitasync_promises(), isolate);
726 bool was_deleted = OrderedHashSet::Delete(isolate, *promises, *promise);
727 DCHECK(was_deleted);
728 USE(was_deleted);
729 promises = OrderedHashSet::Shrink(isolate, promises);
730 native_context->set_atomics_waitasync_promises(*promises);
731 } else {
732 // NativeContext keeps the Promise alive; if the Promise is dead then
733 // surely NativeContext is too.
734 DCHECK(node->native_context_.IsEmpty());
735 }
736 }
737
ResolveAsyncWaiterPromise(FutexWaitListNode * node)738 void FutexEmulation::ResolveAsyncWaiterPromise(FutexWaitListNode* node) {
739 // This function must run in the main thread of node's Isolate.
740 DCHECK(FLAG_harmony_atomics_waitasync);
741
742 auto v8_isolate =
743 reinterpret_cast<v8::Isolate*>(node->isolate_for_async_waiters_);
744
745 // Try to cancel the timeout task (if one exists). If the timeout task exists,
746 // cancelling it will always succeed. It's not possible for the timeout task
747 // to be running, since it's scheduled to run in the same thread as this task.
748
749 // Using the CancelableTaskManager here is OK since the Isolate is guaranteed
750 // to be alive - FutexEmulation::IsolateDeinit removes all FutexWaitListNodes
751 // owned by an Isolate which is going to die.
752 bool success = node->CancelTimeoutTask();
753 DCHECK(success);
754 USE(success);
755
756 if (!node->promise_.IsEmpty()) {
757 DCHECK(!node->native_context_.IsEmpty());
758 Local<v8::Context> native_context = node->native_context_.Get(v8_isolate);
759 v8::Context::Scope contextScope(native_context);
760 Handle<JSPromise> promise = Handle<JSPromise>::cast(
761 Utils::OpenHandle(*node->promise_.Get(v8_isolate)));
762 Handle<String> result_string;
763 // When waiters are notified, their async_timeout_time_ is reset. Having a
764 // non-zero async_timeout_time_ here means the waiter timed out.
765 if (node->async_timeout_time_ != base::TimeTicks()) {
766 DCHECK(node->waiting_);
767 result_string =
768 node->isolate_for_async_waiters_->factory()->timed_out_string();
769 } else {
770 DCHECK(!node->waiting_);
771 result_string = node->isolate_for_async_waiters_->factory()->ok_string();
772 }
773 MaybeHandle<Object> resolve_result =
774 JSPromise::Resolve(promise, result_string);
775 DCHECK(!resolve_result.is_null());
776 USE(resolve_result);
777 }
778 }
779
ResolveAsyncWaiterPromises(Isolate * isolate)780 void FutexEmulation::ResolveAsyncWaiterPromises(Isolate* isolate) {
781 // This function must run in the main thread of isolate.
782 DCHECK(FLAG_harmony_atomics_waitasync);
783
784 FutexWaitListNode* node;
785 {
786 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
787
788 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
789 auto it = isolate_map.find(isolate);
790 DCHECK_NE(isolate_map.end(), it);
791
792 node = it->second.head;
793 isolate_map.erase(it);
794 }
795
796 // The list of nodes starting from "node" are no longer on any list, so it's
797 // ok to iterate them without holding the mutex. We also need to not hold the
798 // mutex while calling CleanupAsyncWaiterPromise, since it may allocate
799 // memory.
800 HandleScope handle_scope(isolate);
801 while (node) {
802 DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
803 DCHECK(!node->waiting_);
804 ResolveAsyncWaiterPromise(node);
805 CleanupAsyncWaiterPromise(node);
806 // We've already tried to cancel the timeout task for the node; since we're
807 // now in the same thread the timeout task is supposed to run, we know the
808 // timeout task will never happen, and it's safe to delete the node here.
809 DCHECK_EQ(CancelableTaskManager::kInvalidTaskId, node->timeout_task_id_);
810 node = FutexWaitList::DeleteAsyncWaiterNode(node);
811 }
812 }
813
HandleAsyncWaiterTimeout(FutexWaitListNode * node)814 void FutexEmulation::HandleAsyncWaiterTimeout(FutexWaitListNode* node) {
815 // This function must run in the main thread of node's Isolate.
816 DCHECK(FLAG_harmony_atomics_waitasync);
817 DCHECK(node->IsAsync());
818
819 {
820 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
821
822 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
823 if (!node->waiting_) {
824 // If the Node is not waiting, it's already scheduled to have its Promise
825 // resolved. Ignore the timeout.
826 return;
827 }
828 g_wait_list.Pointer()->RemoveNode(node);
829 }
830
831 // "node" has been taken out of the lists, so it's ok to access it without
832 // holding the mutex. We also need to not hold the mutex while calling
833 // CleanupAsyncWaiterPromise, since it may allocate memory.
834 HandleScope handle_scope(node->isolate_for_async_waiters_);
835 ResolveAsyncWaiterPromise(node);
836 CleanupAsyncWaiterPromise(node);
837 delete node;
838 }
839
IsolateDeinit(Isolate * isolate)840 void FutexEmulation::IsolateDeinit(Isolate* isolate) {
841 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
842
843 // Iterate all locations to find nodes belonging to "isolate" and delete them.
844 // The Isolate is going away; don't bother cleaning up the Promises in the
845 // NativeContext. Also we don't need to cancel the timeout tasks, since they
846 // will be cancelled by Isolate::Deinit.
847 {
848 auto& location_lists = g_wait_list.Pointer()->location_lists_;
849 auto it = location_lists.begin();
850 while (it != location_lists.end()) {
851 FutexWaitListNode*& head = it->second.head;
852 FutexWaitListNode*& tail = it->second.tail;
853 FutexWaitList::DeleteNodesForIsolate(isolate, &head, &tail);
854 // head and tail are either both nullptr or both non-nullptr.
855 DCHECK_EQ(head == nullptr, tail == nullptr);
856 if (head == nullptr) {
857 location_lists.erase(it++);
858 } else {
859 ++it;
860 }
861 }
862 }
863
864 {
865 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
866 auto it = isolate_map.find(isolate);
867 if (it != isolate_map.end()) {
868 auto node = it->second.head;
869 while (node) {
870 DCHECK_EQ(isolate, node->isolate_for_async_waiters_);
871 node->timeout_task_id_ = CancelableTaskManager::kInvalidTaskId;
872 node = FutexWaitList::DeleteAsyncWaiterNode(node);
873 }
874 isolate_map.erase(it);
875 }
876 }
877
878 g_wait_list.Pointer()->Verify();
879 }
880
NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)881 Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
882 size_t addr) {
883 DCHECK_LT(addr, array_buffer->byte_length());
884 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
885
886 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
887
888 auto wait_location = FutexWaitList::ToWaitLocation(backing_store.get(), addr);
889 auto& location_lists = g_wait_list.Pointer()->location_lists_;
890 auto it = location_lists.find(wait_location);
891 if (it == location_lists.end()) {
892 return Smi::zero();
893 }
894 int waiters = 0;
895 FutexWaitListNode* node = it->second.head;
896 while (node != nullptr) {
897 std::shared_ptr<BackingStore> node_backing_store =
898 node->backing_store_.lock();
899 if (backing_store.get() == node_backing_store.get() && node->waiting_) {
900 waiters++;
901 }
902
903 node = node->next_;
904 }
905
906 return Smi::FromInt(waiters);
907 }
908
NumAsyncWaitersForTesting(Isolate * isolate)909 Object FutexEmulation::NumAsyncWaitersForTesting(Isolate* isolate) {
910 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
911
912 int waiters = 0;
913 for (const auto& it : g_wait_list.Pointer()->location_lists_) {
914 FutexWaitListNode* node = it.second.head;
915 while (node != nullptr) {
916 if (node->isolate_for_async_waiters_ == isolate && node->waiting_) {
917 waiters++;
918 }
919 node = node->next_;
920 }
921 }
922
923 return Smi::FromInt(waiters);
924 }
925
NumUnresolvedAsyncPromisesForTesting(Handle<JSArrayBuffer> array_buffer,size_t addr)926 Object FutexEmulation::NumUnresolvedAsyncPromisesForTesting(
927 Handle<JSArrayBuffer> array_buffer, size_t addr) {
928 DCHECK_LT(addr, array_buffer->byte_length());
929 std::shared_ptr<BackingStore> backing_store = array_buffer->GetBackingStore();
930
931 NoHeapAllocationMutexGuard lock_guard(g_mutex.Pointer());
932
933 int waiters = 0;
934 auto& isolate_map = g_wait_list.Pointer()->isolate_promises_to_resolve_;
935 for (const auto& it : isolate_map) {
936 FutexWaitListNode* node = it.second.head;
937 while (node != nullptr) {
938 std::shared_ptr<BackingStore> node_backing_store =
939 node->backing_store_.lock();
940 if (backing_store.get() == node_backing_store.get() &&
941 addr == node->wait_addr_ && !node->waiting_) {
942 waiters++;
943 }
944
945 node = node->next_;
946 }
947 }
948
949 return Smi::FromInt(waiters);
950 }
951
VerifyNode(FutexWaitListNode * node,FutexWaitListNode * head,FutexWaitListNode * tail)952 void FutexWaitList::VerifyNode(FutexWaitListNode* node, FutexWaitListNode* head,
953 FutexWaitListNode* tail) {
954 #ifdef DEBUG
955 if (node->next_ != nullptr) {
956 DCHECK_NE(node, tail);
957 DCHECK_EQ(node, node->next_->prev_);
958 } else {
959 DCHECK_EQ(node, tail);
960 }
961 if (node->prev_ != nullptr) {
962 DCHECK_NE(node, head);
963 DCHECK_EQ(node, node->prev_->next_);
964 } else {
965 DCHECK_EQ(node, head);
966 }
967
968 if (node->async_timeout_time_ != base::TimeTicks()) {
969 DCHECK(FLAG_harmony_atomics_waitasync);
970 DCHECK(node->IsAsync());
971 }
972
973 DCHECK(NodeIsOnList(node, head));
974 #endif // DEBUG
975 }
976
Verify()977 void FutexWaitList::Verify() {
978 #ifdef DEBUG
979 for (const auto& it : location_lists_) {
980 FutexWaitListNode* node = it.second.head;
981 while (node != nullptr) {
982 VerifyNode(node, it.second.head, it.second.tail);
983 node = node->next_;
984 }
985 }
986
987 for (const auto& it : isolate_promises_to_resolve_) {
988 auto node = it.second.head;
989 while (node != nullptr) {
990 VerifyNode(node, it.second.head, it.second.tail);
991 DCHECK_EQ(it.first, node->isolate_for_async_waiters_);
992 node = node->next_;
993 }
994 }
995 #endif // DEBUG
996 }
997
NodeIsOnList(FutexWaitListNode * node,FutexWaitListNode * head)998 bool FutexWaitList::NodeIsOnList(FutexWaitListNode* node,
999 FutexWaitListNode* head) {
1000 auto n = head;
1001 while (n != nullptr) {
1002 if (n == node) {
1003 return true;
1004 }
1005 n = n->next_;
1006 }
1007 return false;
1008 }
1009
1010 } // namespace internal
1011 } // namespace v8
1012