Skip to content

Commit 285a37e

Browse files
authored
fix(ECMAScript): Atomics Async Critical Section in the Main Thread (#976)
1 parent cf119ee commit 285a37e

2 files changed

Lines changed: 27 additions & 60 deletions

File tree

nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs

Lines changed: 27 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
use std::{
66
hint::assert_unchecked,
77
ops::ControlFlow,
8-
sync::{
9-
Arc,
10-
atomic::{AtomicBool, Ordering as StdOrdering},
11-
},
8+
sync::Arc,
129
thread::{self, JoinHandle},
1310
time::Duration,
1411
};
@@ -1421,6 +1418,7 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14211418
gc: NoGcScope<'gc, '_>,
14221419
) -> Value<'gc> {
14231420
let slot = buffer.as_slice(agent).slice_from(byte_index_in_buffer);
1421+
let data_block = buffer.get_data_block(agent).clone();
14241422
// 14. Let WL be GetWaiterList(block, byteIndexInBuffer).
14251423
// 15. If mode is sync, then
14261424
// a. Let promiseCapability be blocking.
@@ -1429,6 +1427,14 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14291427
// a. Let promiseCapability be ! NewPromiseCapability(%Promise%).
14301428
// b. Let resultObject be OrdinaryObjectCreate(%Object.prototype%).
14311429
// 17. Perform EnterCriticalSection(WL).
1430+
1431+
// SAFETY: buffer is a valid SharedArrayBuffer and cannot be detached. A 0-sized SAB would
1432+
// have a dangling data block, but Atomics.wait requires `byteIndex` to be within bounds,
1433+
// so a 0-sized SAB would have been rejected earlier with a RangeError.
1434+
let waiters = unsafe { data_block.get_or_init_waiters() };
1435+
let waiter_record = WaiterRecord::new_shared();
1436+
let mut guard = waiters.lock().unwrap();
1437+
14321438
// 18. Let elementType be TypedArrayElementType(typedArray).
14331439
// 19. Let w be GetValueFromBuffer(buffer, byteIndexInBuffer, elementType, true, seq-cst).
14341440
let v_not_equal_to_w = if IS_I64 {
@@ -1447,6 +1453,8 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14471453
// 20. If v ≠ w, then
14481454
if v_not_equal_to_w {
14491455
// a. Perform LeaveCriticalSection(WL).
1456+
drop(guard);
1457+
14501458
// b. If mode is sync, return "not-equal".
14511459
if !IS_ASYNC {
14521460
return BUILTIN_STRING_MEMORY.not_equal.into();
@@ -1464,6 +1472,7 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14641472
// timeouts. Asynchronous immediate timeouts have special handling
14651473
// in order to fail fast and avoid unnecessary Promise jobs.
14661474
// b. Perform LeaveCriticalSection(WL).
1475+
drop(guard);
14671476
// c. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
14681477
// d. Perform ! CreateDataPropertyOrThrow(resultObject, "value", "timed-out").
14691478
let result_object =
@@ -1484,32 +1493,10 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
14841493
// [[Result]]: "ok"
14851494
// }.
14861495
// 28. Perform AddWaiter(WL, waiterRecord).
1496+
guard.push_to_list(byte_index_in_buffer, waiter_record.clone());
14871497
// 29. If mode is sync, then
14881498
if !IS_ASYNC {
1489-
let data_block = buffer.get_data_block(agent);
1490-
// SAFETY: buffer is a valid SharedArrayBuffer and cannot be detached. A 0-sized SAB would
1491-
// have a dangling data block, but Atomics.wait requires `byteIndex` to be within bounds,
1492-
// so a 0-sized SAB would have been rejected earlier with a RangeError.
1493-
let waiters = unsafe { data_block.get_or_init_waiters() };
1494-
let waiter_record = WaiterRecord::new_shared();
1495-
let mut guard = waiters.lock().unwrap();
1496-
1497-
// Re-read value under critical section to avoid TOCTOU race.
1498-
let slot = data_block.as_racy_slice().slice_from(byte_index_in_buffer);
1499-
let v_changed = if IS_I64 {
1500-
let slot = unsafe { slot.as_aligned::<u64>().unwrap_unchecked() };
1501-
v as u64 != slot.load(Ordering::SeqCst)
1502-
} else {
1503-
let slot = unsafe { slot.as_aligned::<u32>().unwrap_unchecked() };
1504-
v as i32 as u32 != slot.load(Ordering::SeqCst)
1505-
};
1506-
if v_changed {
1507-
return BUILTIN_STRING_MEMORY.not_equal.into();
1508-
}
1509-
15101499
// a. Perform SuspendThisAgent(WL, waiterRecord).
1511-
guard.push_to_list(byte_index_in_buffer, waiter_record.clone());
1512-
15131500
if t == u64::MAX {
15141501
waiter_record.wait(guard);
15151502
} else {
@@ -1532,17 +1519,21 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>(
15321519
let promise = Global::new(agent, promise_capability.promise.unbind());
15331520
// 30. Else if timeoutTime is finite, then
15341521
// a. Perform EnqueueAtomicsWaitAsyncTimeoutJob(WL, waiterRecord).
1535-
let buffer = buffer.get_data_block(agent).clone();
1522+
1523+
let data_block_clone = data_block.clone();
15361524
enqueue_atomics_wait_async_job::<IS_I64>(
15371525
agent,
1538-
buffer,
1526+
data_block_clone,
15391527
byte_index_in_buffer,
1540-
v,
1528+
waiter_record.clone(),
15411529
t,
15421530
promise,
15431531
gc,
15441532
);
1533+
15451534
// 31. Perform LeaveCriticalSection(WL).
1535+
drop(guard);
1536+
15461537
// 33. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
15471538
// 34. Perform ! CreateDataPropertyOrThrow(resultObject, "value", promiseCapability.[[Promise]]).
15481539
let result_object =
@@ -1672,7 +1663,7 @@ impl WaitAsyncJob {
16721663
// c. Perform LeaveCriticalSection(WL).
16731664
let promise_capability = PromiseCapability::from_promise(promise, true);
16741665
let result = match result {
1675-
WaitResult::Ok | WaitResult::NotEqual => BUILTIN_STRING_MEMORY.ok.into(),
1666+
WaitResult::Ok => BUILTIN_STRING_MEMORY.ok.into(),
16761667
WaitResult::TimedOut => BUILTIN_STRING_MEMORY.timed_out.into(),
16771668
};
16781669
unwrap_try(promise_capability.try_resolve(agent, result, gc));
@@ -1688,43 +1679,21 @@ impl WaitAsyncJob {
16881679
/// unused.
16891680
fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
16901681
agent: &mut Agent,
1691-
buffer: SharedDataBlock,
1682+
data_block: SharedDataBlock,
16921683
byte_index_in_buffer: usize,
1693-
v: i64,
1684+
waiter_record: Arc<WaiterRecord>,
16941685
t: u64,
16951686
promise: Global<Promise>,
16961687
gc: NoGcScope,
16971688
) {
16981689
// 1. Let timeoutJob be a new Job Abstract Closure with no parameters that
16991690
// captures WL and waiterRecord and performs the following steps when
17001691
// called:
1701-
let signal = Arc::new(AtomicBool::new(false));
1702-
let s = signal.clone();
17031692
let handle = thread::spawn(move || {
17041693
// SAFETY: buffer is a cloned SharedDataBlock; non-dangling.
1705-
let waiters = unsafe { buffer.get_or_init_waiters() };
1706-
let waiter_record = WaiterRecord::new_shared();
1694+
let waiters = unsafe { data_block.get_or_init_waiters() };
17071695
let mut guard = waiters.lock().unwrap();
17081696

1709-
// Re-check the value under the critical section.
1710-
let slot = buffer.as_racy_slice().slice_from(byte_index_in_buffer);
1711-
let v_not_equal = if IS_I64 {
1712-
let slot = unsafe { slot.as_aligned::<u64>().unwrap_unchecked() };
1713-
v as u64 != slot.load(Ordering::SeqCst)
1714-
} else {
1715-
let slot = unsafe { slot.as_aligned::<u32>().unwrap_unchecked() };
1716-
v as i32 as u32 != slot.load(Ordering::SeqCst)
1717-
};
1718-
1719-
// Signal the main thread that we have the lock and are about to sleep.
1720-
s.store(true, StdOrdering::Release);
1721-
1722-
if v_not_equal {
1723-
return WaitResult::NotEqual;
1724-
}
1725-
1726-
guard.push_to_list(byte_index_in_buffer, waiter_record.clone());
1727-
17281697
if t == u64::MAX {
17291698
waiter_record.wait(guard);
17301699
} else {
@@ -1735,6 +1704,8 @@ fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
17351704
guard.remove_from_list(byte_index_in_buffer, waiter_record);
17361705

17371706
// 31. Perform LeaveCriticalSection(WL).
1707+
drop(guard);
1708+
17381709
// 32. If mode is sync, return waiterRecord.[[Result]].
17391710
return WaitResult::TimedOut;
17401711
}
@@ -1749,9 +1720,6 @@ fn enqueue_atomics_wait_async_job<const IS_I64: bool>(
17491720
_has_timeout: t != u64::MAX,
17501721
}))),
17511722
};
1752-
while !signal.load(StdOrdering::Acquire) {
1753-
// Wait until the thread has started up and is about to go to sleep.
1754-
}
17551723
// 2. Let now be the time value (UTC) identifying the current time.
17561724
// 3. Let currentRealm be the current Realm Record.
17571725
// 4. Perform HostEnqueueTimeoutJob(timeoutJob, currentRealm, 𝔽(waiterRecord.[[TimeoutTime]]) - now).

nova_vm/src/ecmascript/types/spec/data_block.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,6 @@ impl WaiterRecord {
480480
pub(crate) enum WaitResult {
481481
Ok,
482482
TimedOut,
483-
NotEqual,
484483
}
485484

486485
#[cfg(feature = "shared-array-buffer")]

0 commit comments

Comments
 (0)