diff --git a/src/execution/futex-emulation.cc b/src/execution/futex-emulation.cc index 00e0cb4b2a9..3f5d5123f98 100644 --- a/src/execution/futex-emulation.cc +++ b/src/execution/futex-emulation.cc @@ -446,6 +446,14 @@ DirectHandle FutexEmulation::WaitSyncImpl( // Reset the interrupted flag while still holding the mutex. node->interrupted_ = false; + // Take the node off the wait list while interrupts are handled. The + // interrupt handler may call back into the embedder, which can in turn + // re-enter this function and reuse this per-isolate {node}. The node + // state is restored once the mutex is re-acquired. + bool was_waiting = node->waiting_; + void* saved_wait_location = node->wait_location_; + wait_list->RemoveNode(node); + // Unlock the mutex here to prevent deadlock from lock ordering between // mutex and mutexes locked by HandleInterrupts. lock_guard.Unlock(); @@ -468,6 +476,10 @@ DirectHandle FutexEmulation::WaitSyncImpl( lock_guard.Lock(); + node->wait_location_ = saved_wait_location; + node->waiting_ = was_waiting; + wait_list->AddNode(node); + if (IsExceptionHole(interrupt_object)) { result = direct_handle(interrupt_object, isolate); break; diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 2ac267a4edb..71d0581a7d9 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -26177,6 +26177,59 @@ TEST(FutexInterruption) { timeout_thread.Join(); } +namespace { + +void ReenterAtomicsWaitCallback(v8::Isolate* isolate, void* data) { + // Perform a nested Atomics.wait at a different index while the outer + // wait is being interrupted. The per-isolate wait-list node must be + // reusable here without affecting the outer wait. + CompileRun("inner_result = Atomics.wait(i32a, 1, 0, 10);"); +} + +class FutexReenterInterruptThread : public v8::base::Thread { + public: + explicit FutexReenterInterruptThread(v8::Isolate* isolate) + : Thread(Options("FutexReenterInterruptThread")), isolate_(isolate) {} + + void Run() override { + v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100)); + isolate_->RequestInterrupt(&ReenterAtomicsWaitCallback, nullptr); + } + + private: + v8::Isolate* isolate_; +}; + +} // namespace + +TEST(FutexWaitReenteredFromInterrupt) { + i::v8_flags.allow_natives_syntax = true; + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope scope(isolate); + LocalContext env; + + CompileRun( + "var ab = new SharedArrayBuffer(8);" + "var i32a = new Int32Array(ab);" + "var inner_result;"); + + FutexReenterInterruptThread interrupt_thread(isolate); + CHECK(interrupt_thread.Start()); + + v8::Local result = CompileRun("Atomics.wait(i32a, 0, 0, 500);"); + interrupt_thread.Join(); + + // The inner wait was never notified, so it must time out. + ExpectString("inner_result", "timed-out"); + // The outer wait was never notified either; the inner wait must not be + // observed as a notification of the outer wait. + CHECK(result->StrictEquals(v8_str("timed-out"))); + // No stale entries should remain on the global wait list. + ExpectInt32("%AtomicsNumWaitersForTesting(i32a, 0)", 0); + ExpectInt32("%AtomicsNumWaitersForTesting(i32a, 1)", 0); + ExpectInt32("Atomics.notify(i32a, 0)", 0); +} + TEST(StackCheckTermination) { v8::Isolate* isolate = CcTest::isolate(); i::Isolate* i_isolate = CcTest::i_isolate();