Stage 3 Draft / September 25, 2020

Atomics.waitAsync

We provide a new API, Atomics.waitAsync, that an agent can use to wait on a shared memory location (to later be awoken by some agent calling Atomics.notify on that location) without waiting synchronously (ie, without blocking). Notably this API is useful in agents whose [[CanBlock]] attribute is false, such as the main thread of a web browser document, but the API is not restricted to such agents.

The API is promise-based. Very high performance is not a requirement, but good performance is desirable.

1 Semantics

1.1 HostResolveInAgent ( agentSignifier, promiseCapability, resolution)

This is a new section.

HostResolveInAgent is an implementation-defined abstract operation that takes three arguments, an agent signifier agentSignifier, a PromiseCapability Record promiseCapability, and a value resolution. The host's responsibility is to resolve promiseCapability in the agent signified by agentSignifier with resolution in finite time. The host may delay resolving promiseCapability in agentSignifier, e.g. for resource management reasons, but the promise must eventually be resolved.

1.2 GetWaiterList ( block, i )

A Waiter Record is a Record value used to denote a particular call to Atomics.wait or Atomics.waitAsync. It has fields as defined by Table 1.

Table 1: Waiter Record Fields
Field Name Value Meaning
[[AgentSignifier]] An agent signifier The agent that called Atomics.wait or Atomics.waitAsync.
[[PromiseCapability]] A PromiseCapability Record or undefined If denoting a call to Atomics.waitAsync, the resulting promise, otherwise undefined.
[[Timeout]] A non-negative Number The timeout in milliseconds.
[[Result]] "ok" or "timed-out" The return value of the call.

A WaiterList is a semantic object that contains an ordered List of those agents that are waiting on a location (block, i) in shared memory; block is a Shared Data Block and i a byte offset into the memory of block. A WaiterList object also optionally contains a Synchronize event denoting the previous leaving of its critical section.Record value used to explain waiting and notification of agents via Atomics.wait, Atomics.waitAsync, and Atomics.notify. It has fields as defined by Table 2.

Table 2: WaiterList Record Fields
Field Name Value Meaning
[[Waiters]] An ordered List of Waiter Records The calls to Atomics.wait or Atomics.waitAsync that are waiting on the location with which this WaiterList is associated.
[[MostRecentLeaveEvent]] A Synchronize event or undefined The event of the most recent leaving of its critical section, or undefined if its critical section has never been entered.

There can be multiple Waiter Records in a WaiterList with the same agent signifier.

The agent cluster has a store of WaiterList objectsRecords; the store is indexed by (block, i), where block is a Shared Data Block and i a byte offset into the memory of block. WaiterLists are agent-independent: a lookup in the store of WaiterLists by (block, i) will result in the same WaiterList object in any agent in the agent cluster.

Operations on a WaiterList—adding and removing waiting agents, traversing the list of agents, suspending and notifying agents on the list, setting and retrieving the Synchronize event—may only be performed by agents that have entered the WaiterList's critical section.

Note

Conceptually, agents that call either Atomics.wait or Atomics.waitAsync are appended to WaiterList. For calls to Atomics.waitAsync, the appended Waiter Record's [[PromiseCapability]] field contains the Promise returned by the call. For calls to Atomics.wait, the appended Waiter Record's [[PromiseCapability]] field is null.

Waiting agents are notified in FIFO order for fairness. There is a single FIFO queue shared by both synchronous and asynchronous waiters.

The abstract operation GetWaiterList takes two arguments, a Shared Data Block block and a nonnegative integer i. It performs the following steps:

  1. Assert: block is a Shared Data Block.
  2. Assert: i and i + 3 are valid byte offsets within the memory of block.
  3. Assert: i is divisible by 4.
  4. Return the WaiterList that is referenced by the pair (block, i).

1.3 EnterCriticalSection ( WL )

The abstract operation EnterCriticalSection takes one argument, a WaiterList WL. It performs the following steps:

  1. Assert: The calling agent is not in the critical section for any WaiterList.
  2. Wait until no agent is in the critical section for WL, then enter the critical section for WL (without allowing any other agent to enter).
  3. If WL has a Synchronize event.[[MostRecentLeaveEvent]] is undefined, then
    1. NOTE: A WL whose critical section has been entered at least once has a Synchronize event set by LeaveCriticalSection.
    2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
    3. Let eventsRecord be the Agent Events Record in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
    4. Let entererEventList be eventsRecord.[[EventList]].
    5. Let enterEvent be a new Synchronize event.
    6. Append enterEvent to entererEventList.
    7. Let leaveEvent be the Synchronize event in WL.
    8. Append (leaveEvent, enterEvent) to eventRecords.[[AgentSynchronizesWith]].

1.4 LeaveCriticalSection ( WL )

The abstract operation LeaveCriticalSection takes one argument, a WaiterList WL. It performs the following steps:

  1. Assert: The calling agent is in the critical section for WL.
  2. Let execution be the [[CandidateExecution]] field of the calling surrounding's Agent Record.
  3. Let eventsRecord be the Agent Events Record in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  4. Let leaverEventList be eventsRecord.[[EventList]].
  5. Let leaveEvent be a new Synchronize event.
  6. Append leaveEvent to leaverEventList.
  7. Set the Synchronize event in WL to leaveEventWL.[[MostRecentLeaveEvent]] to leaveEvent.
  8. Leave the critical section for WL.

1.5 TriggerTimeout( WL, waiterRecord )

This is a new abstract operation.

The abstract operation TriggerTimeout takes two arguments, a WaiterList WL and a Waiter Record waiterRecord. It performs the following steps:

  1. Assert: waiterRecord.[[Timeout]] is finite.
  2. Perform EnterCriticalSection(WL).
  3. If waiterRecord is in WL.[[Waiters]], then
    1. Set waiterRecord.[[Result]] to "timed-out".
    2. Perform RemoveWaiter(WL, waiterRecord).
    3. Perform NotifyWaiter(WL, waiterRecord).
  4. Perform LeaveCriticalSection(WL).

1.6 AddWaiter ( WL, WwaiterRecord )

The abstract operation AddWaiter takes two arguments, a WaiterList WL and an agent signifier Wa Waiter Record waiterRecord. It performs the following steps:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is not on the list of waiters in any WaiterList.
  3. Assert: There is no Waiter Record in WL.[[Waiters]] whose [[PromiseCapability]] field is waiterRecord.[[PromiseCapability]] and [[AgentSignifier]] field is waiterRecord.[[AgentSignifier]].
  4. Add W to the end of the list of waiters in WL.
  5. Append waiterRecord as the last element of WL.[[Waiters]]
  6. If waiterRecord.[[Timeout]] is finite, then in parallel,
    1. Wait waiterRecord.[[Timeout]] milliseconds.
    2. Perform TriggerTimeout(WL, waiterRecord).

1.7 RemoveWaiter ( WL, WwaiterRecord )

The abstract operation RemoveWaiter takes two arguments, a WaiterList WL and an agent signifier Wa Waiter Record waiterRecord. It performs the following steps:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: WwaiterRecord is on the list of waiters in WL.[[Waiters]].
  3. Remove WwaiterRecord from the list of waiters in WL.[[Waiters]].

Change this function not to take a timeout argument. Timeouts are now handled in the caller. (Not intended as a normative change.)

1.8 Suspend ( WL, W, timeout )

The abstract operation Suspend takes threetwo arguments, a WaiterList WL,and an agent signifier W, and a nonnegative, non-NaN Number timeout. It performs the following steps:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is equal to AgentSignifier().
  3. Assert: W is on the list of waiters.There is a Waiter Record in WL.[[Waiters]] whose [[AgentSignifier]] field is W and whose [[PromiseCapability]] field is undefined.
  4. Assert: AgentCanSuspend() is true.
  5. Perform LeaveCriticalSection(WL) and suspend W for up to timeout milliseconds, performing the combined operation in such a way that a notification that arrives after the critical section is exited but before the suspension takes effect is not lost. W can notify either because the timeout expired or because it wasbe notified explicitly by another agent calling NotifyWaiter(WL, waiterRecord, ...), and not for any other reasons at all.
  6. Perform EnterCriticalSection(WL).
  7. If W was notified explicitly by another agent calling NotifyWaiter(WL, W), return true.
  8. Return false.

1.9 NotifyWaiter ( WL, WwaiterRecord )

The abstract operation NotifyWaiter takes two arguments, a WaiterList WL and an agent signifier Wa Waiter Record waiterRecord. It performs the following steps:

  1. Assert: The calling agent is in the critical section for WL.
  2. Assert: W is on the list of waiters in WL.
  3. Assert: waiterRecord.[[Result]] is either the String "ok" or the String "timed-out".
  4. Notify the agent W.
  5. If waiterRecord.[[PromiseCapability]] is undefined, then
    1. NOTE: An undefined promise capability denotes a blocking wait.
    2. Notify the agent waiterRecord.[[AgentSignifier]].
  6. Else,
    1. Perform HostResolveInAgent(waiterRecord.[[AgentSignifier]], waiterRecord.[[PromiseCapability]], waiterRecord.[[Result]])
    2. NOTE: An agent must not access another agent's promise capability in any capacity beyond passing it to the host.
Note

The embedding may delay notifying Wthe agent whose signifier is waiterRecord.[[AgentSignifier]], e.g. for resource management reasons, but Wthat agent must eventually be notified in order to guarantee forward progress.

1.10 DoWait ( mode, typedArray, index, value, timeout )

This is a new abstract operation.

The abstract operation DoWait takes five arguments, mode which is one of (sync, async), and values typedArray, idnex, value, and timeout. It performs the following steps:

  1. Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
  2. Let i be ? ValidateAtomicAccess(typedArray, index).
  3. Let arrayTypeName be typedArray.[[TypedArrayName]].
  4. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
  5. Otherwise, let v be ? ToInt32(value).
  6. Let q be ? ToNumber(timeout).
  7. If q is NaN, let t be +∞, else let t be max(q, 0).
  8. If mode is sync, then
    1. Let B be AgentCanSuspend().
    2. If B is false, throw a TypeError exception.
  9. Let block be buffer.[[ArrayBufferData]].
  10. Let offset be typedArray.[[ByteOffset]].
  11. Let indexedPosition be (i × 4) + offset.
  12. Let WL be GetWaiterList(block, indexedPosition).
  13. Let promiseCapability be undefined.
  14. Let resultObject be undefined.
  15. If mode is async, then
    1. Set promiseCapability to ! NewPromiseCapability(%Promise%).
    2. Set resultObject to ! OrdinaryObjectCreate(%Object.prototype%).
  16. Perform EnterCriticalSection(WL).
  17. Let w be ! AtomicLoad(typedArray, i).
  18. If v is not equal to w, then
    1. Perform LeaveCriticalSection(WL).
    2. If mode is sync, then
      1. Return the String "not-equal".
    3. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
    4. Perform ! CreateDataPropertyOrThrow(resultObject, "value", "not-equal").
    5. Return resultObject.
  19. If t is 0 and mode is async, then
    1. NOTE: There is no special handling of synchronous immediate timeouts. Asynchronous immediate timeouts have special handling in order to fail fast and avoid Promise machinery.
    2. Perform LeaveCriticalSection(WL).
    3. Perform ! CreateDataPropertyOrThrow(resultObject, "async", false).
    4. Perform ! CreateDataPropertyOrThrow(resultObject, "value", "timed-out").
    5. Return resultObject.
  20. Let W be AgentSignifier().
  21. Let waiterRecord be a new Waiter Record { [[AgentSignifier]]: W, [[PromiseCapability]]: promiseCapability, [[Timeout]]: t, [[Result]]: "ok" }.
  22. Perform AddWaiter(WL, waiterRecord).
  23. If mode is sync, then
    1. Perform Suspend(WL, W).
  24. Perform LeaveCriticalSection(WL).
  25. If mode is sync, then
    1. Return waiterRecord.[[Result]].
  26. Perform ! CreateDataPropertyOrThrow(resultObject, "async", true).
  27. Perform ! CreateDataPropertyOrThrow(resultObject, "value", promiseCapability.[[Promise]]).
  28. Return resultObject.

1.11 Atomics.wait ( typedArray, index, value, timeout )

Atomics.wait puts the calling agent in a wait queue and puts it to sleep until it is notified or the sleep times out. The following steps are taken:

  1. Let buffer be ? ValidateSharedIntegerTypedArray(typedArray, true).
  2. Let i be ? ValidateAtomicAccess(typedArray, index).
  3. If arrayTypeName is "BigInt64Array", let v be ? ToBigInt64(value).
  4. Otherwise, let v be ? ToInt32(value).
  5. Let q be ? ToNumber(timeout).
  6. If q is NaN, let t be +∞, else let t be max(q, 0).
  7. Let B be AgentCanSuspend().
  8. If B is false, throw a TypeError exception.
  9. Let block be buffer.[[ArrayBufferData]].
  10. Let offset be typedArray.[[ByteOffset]].
  11. Let indexedPosition be (i × 4) + _offset.
  12. Let WL be GetWaiterList(block, indexedPosition).
  13. Perform EnterCriticalSection(WL).
  14. Let w be ! AtomicLoad(typedArray, i).
  15. If v is not equal to w, then
    1. Perform LeaveCriticalSection(WL).
    2. Return the String "not-equal".
  16. Let W be AgentSignifier().
  17. Perform AddWaiter(WL, W).
  18. Let notified be Suspend(WL, W, t).
  19. If notified is true, then
    1. Assert: W is not on the list of waiters in WL.
  20. Else,
    1. Perform RemoveWaiter(WL, W).
  21. Perform LeaveCriticalSection(WL).
  22. If notified is true, return the String "ok".
  23. Return the String "timed-out".
  24. Return DoWait(sync, typedArray, index, value, timeout).

1.12 Atomics.waitAsync ( typedArray, index, value, timeout )

This is a new method.

Atomics.waitAsync returns a Promise that is resolved when the calling agent is notified or the sleep times out. The following steps are taken:

  1. Return DoWait(async, typedArray, index, value, timeout).