Implement the Atomics.waitAsync proposal
Categories
(Core :: JavaScript Engine, enhancement, P3)
Tracking
()
Tracking | Status | |
---|---|---|
firefox140 | --- | fixed |
People
(Reporter: afmenez, Assigned: yulia)
References
(Blocks 8 open bugs, )
Details
(Keywords: dev-doc-complete, parity-chrome, parity-safari, Whiteboard: webcompat:risk-moderate )
User Story
platform-scheduled:2025-06-30
Attachments
(30 files, 22 obsolete files)
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review |
Comment 1•7 years ago
|
||
Updated•7 years ago
|
Updated•7 years ago
|
Comment 3•5 years ago
|
||
The first round of tests for Atomics.waitAsync have landed in Test262: https://github.com/tc39/test262/tree/master/test/built-ins/Atomics/waitAsync
Comment 4•5 years ago
|
||
Would be cool to see this for Firefox as well. Chrome is shipping waitAsync in the next release (87) and will be enabled by default. For reference https://bugs.chromium.org/p/v8/issues/detail?id=10239.
Any updates on this?
Assignee | ||
Comment 5•4 years ago
|
||
There is still some spec work that we are waiting on, to ensure that this can integrate well with wasm.
Comment 6•4 years ago
|
||
(In reply to Yulia Startsev from comment #5)
There is still some spec work that we are waiting on, to ensure that this can integrate well with wasm.
To clarify, wasm needs to do some work to support a compatible feature (and it's very early days for that), but I doubt that that work will have any significant impact on what the JS spec looks like.
Assignee | ||
Comment 7•4 years ago
|
||
Ah, my mistake. Then it might appear sooner rather than later.
Assignee | ||
Updated•4 years ago
|
Assignee | ||
Updated•4 years ago
|
Assignee | ||
Comment 8•4 years ago
•
|
||
:ccullen had some interest in this feature, so I will write a few pointers on getting started. This is a tricky feature, but doable and might be really interesting.
For Inspiration, here is how chrome approached the problem, and this revision gives an overall snapshot. Their design document and design revision is also good reading to see what problems they encountered.
V8 did a nice write up for this: https://v8.dev/features/atomics -- there is a good example there to help us get started. Some historical examples can be found here: https://github.com/tc39/proposal-atomics-wait-async/blob/master/example.html
Host hook: https://github.com/whatwg/html/pull/4613/files -- referenced in the specification here: https://tc39.es/proposal-atomics-wait-async/#sec-hostresolveinagent.
The polyfill for this uses a worker to implement the non-blocking behavior on the main thread: https://github.com/tc39/proposal-atomics-wait-async/blob/master/polyfill.js but I think we want to do a full implementation for this. But it might be a good place to look, just to get an idea of the intended behavior.
You can compare async.wait with doWait to get a grasp of things have changed, in relation to our code, as well as how the challenges that chrome ran into while doing their implementation. Also useful reading is the spec text on shared memory access.
Comment 9•3 years ago
|
||
Updated•3 years ago
|
Comment 10•3 years ago
|
||
Depends on D144759
Comment 11•3 years ago
|
||
Depends on D144760
Comment 12•3 years ago
|
||
Depends on D144761
Comment 13•3 years ago
|
||
Depends on D144762
Comment 14•3 years ago
|
||
Depends on D144763
Comment 15•3 years ago
|
||
Depends on D144764
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Comment 16•3 years ago
|
||
Depends on D144765
Comment 17•3 years ago
|
||
Depends on D145042
Comment 18•3 years ago
|
||
Depends on D145043
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Updated•3 years ago
|
Comment 19•3 years ago
|
||
Redirect a needinfo that is pending on an inactive user to the triage owner.
:sdetar, since the bug has recent activity, could you have a look please?
For more information, please visit auto_nag documentation.
Updated•3 years ago
|
Updated•3 years ago
|
Comment 20•3 years ago
|
||
Comment 21•3 years ago
|
||
The waiter list for Atomics.wait is homogeneous. With the addition of Atomics.waitAsync, the waiter list will contain both sync and async waiters. (Waiters must be notified in the order in which they waited, so it has to be a single list.) While we're refactoring this code, I added a dedicated header node to the waiter list, which lets us remove a node from the list without needing a reference to the SharedArrayRawBuffer.
Depends on D159743
Comment 22•3 years ago
|
||
We currently wait until all OffThreadPromiseTasks are resolved before shutting down the runtime. This is not ideal for something like waitAsync: if the notify never comes, then we will never shut down. This patch adds a mechanism for OffThreadPromiseTasks to opt into being cancellable, in which case we will delete them immediately on shutdown (after calling prepareForCancel
on them) instead of blocking.
Depends on D159744
Comment 23•3 years ago
|
||
Atomics.notify can be called from any context, but the promise created for Atomics.waitAsync must be resolved in the context that created it. OffThreadPromiseTask will dispatch the message to the correct event loop.
When the runtime shuts down -- for example, when a worker is terminated -- we clear out the async waiters associated with that runtime. The interaction between agent termination and waiters is currently somewhat underspecified (see https://github.com/whatwg/html/issues/8276). This approach is consistent with Chrome's existing implementation.
Depends on D159745
Comment 24•3 years ago
|
||
This moves some code around and renumbers steps in preparation for the next patch.
Depends on D159746
Comment 25•3 years ago
|
||
This implements Atomics.waitAsync, excluding cases with non-zero timeouts. The remainder of this patch stack is dedicated to adding timeout support.
Breaking out the critical section into its own function made locking easier. The AutoEnterUnsafeOOMRegion is not strictly necessary yet, but a subsequent patch will add code to initialize timeouts, and my pitiful human brain was not up to the task of safely cleaning up if we OOMed halfway through initializing.
Depends on D159747
Comment 26•3 years ago
|
||
Synchronous timeouts can wake up the sleeping thread, but we need a separate thread to be woken when asynchronous timeouts fire. In the future we may be able to use the DOM TimeoutManager in browser builds, but for now (and for shell builds) I'm adding an internal TimeoutManager, with a single process-wide timeout thread.
Depends on D159748
Comment 27•3 years ago
|
||
We want to use a priority queue to keep track of pending timeouts, but the natural way to prioritize them (lowest timestamp first) is not compatible with taking the highest priority item. Instead of messing around with negative TimeDurations, it seems nicest to just update PriorityQueue to be more flexible.
This patch also adds a highest
helper to peek at the highest priority item without removing it.
Depends on D159749
Comment 28•3 years ago
|
||
Depends on D159750
Comment 29•3 years ago
|
||
When a waiter is notified, or when the corresponding runtime shuts down, we have to be able to cancel a timeout. A linear scan is not the most efficient way to implement this, but it looks like the DOM TimeoutManager has O(n) insertions (it uses a linked list), so this is probably fast enough for now.
If it turns out that O(n) removal is too slow, we can get O(log(n)) removal by keeping a hashtable mapping keys to heap indices, and updating the keys whenever we swap elements. This would require refactoring PriorityQueue to expose swaps, so I'm leaving it for later.
Depends on D159751
Comment 30•3 years ago
|
||
assertHoldsCancelLocks
is intended to minimize footguns from a tricky potential race condition.
CancelTimeout is useful when you have a timeout task that is racing with some other event. (In the case of Atomics.waitAsync, this is a call to Atomics.notify.) When the event occurs, we want to cancel the timeout. If the timeout fires, we want to disable the event. Because the timeout fires on a separate thread, the run
method presumably needs to claim a lock to disable the event. (For waitAsync, this is the FutexAPI lock.)
However, consider what happens in the following series of events:
- Thread T calls Atomics.notify and claims the FutexAPI lock.
- A timeout expires and the timeout thread is woken up.
- The timeout task is removed from the priority queue. Its
run
method is called. - The
run
method must claim the FutexAPI lock to clean up the waiter. We block waiting for thread T to release the lock. - On thread T, we call CancelTimeout to cancel the timeout.
The timeout task has already been removed from the priority queue, so we can't cancel it. We need some way to decide whether the notify or the timeout won the race, and coordinate the cleanup.
I think the least error-prone solution is to say that the timeout always loses the race. We set a cancelled
flag on the TimeoutTask, and the run
method is responsible for checking that flag before doing anything. Awkwardly, that flag must be guarded by the implementation-specific mutex (eg FutexAPI), not the internal timeout manager lock. To make sure implementations of TimeoutTask get this right, assertHoldsCancelLocks
is called when cancelling a timeout, and again when checking the flag.
The next patch has an example of what this looks like in practice.
Alternatives I considered and rejected:
- We could instead decide that the timeout always wins this race. In this case the return value of
CancelTimeout
would indicate whether the timeout had already fired, and the caller (eg Atomics.notify) would be responsible for doing the right thing. The current approach seemed like it put less of a burden on users of the API. - It would be nice if we could return opaque cancellation tokens from
AddTimeoutToTimeoutManager
and pass those back in toCancelTimeout
, instead of the TimeoutTask itself. I couldn't find a way to make that work, because we need access to the TimeoutTask to callassertHoldsCancelLocks
.
Depends on D159752
Comment 31•3 years ago
|
||
Depends on D159753
Comment 32•3 years ago
|
||
Right now, we don't support await
in worker threads in the shell: we never drain the job queue, so enqueued promise resolutions never fire.
Depends on D159754
Comment 33•3 years ago
|
||
The alternative to this patch is having each AsyncWaiter increment the refcount of the underlying SharedArrayRawBuffer, keeping it alive as long as there's an unnotified waiter.
Depends on D159755
Comment 34•3 years ago
|
||
Depends on D159756
Comment 35•2 years ago
|
||
Hey! Curious, what is the status on this?
Comment 36•2 years ago
|
||
The status is that I wrote a stack of patches that pass the test262 tests, but was not 100% confident of correctness, because there's a bunch of tricky multithreading going on. I got distracted with other work and didn't have time to shepherd them through review. We currently aren't prioritizing this very heavily, since AFAIK there's not a lot of real-world demand, but the work is mostly done, so if we had a compelling reason to prioritize this, I don't think it would take too long to get it into shape.
Comment 37•2 years ago
|
||
I am the team lead at StackBlitz for WebContainer, a Node.js runtime for the browser that allows you to run Node.js natively in the browser. I think WebContainer makes for a compelling use case especially because this has been shipped in Chrome and Safari. WebContainer is very performance critical and the fact that Firefox doesn't yet implement Atomics.waitAsync
means that our kernel has to rely on a polyfill which uses a dedicated worker. The problem is that every worker adds more pressure to the tab and consumes resources, and userland code often times spawns a handful of processes (workers) as well. So anything that is more "native" such as Atomics.waitAsync
that would avoid spawning more workers has a positive effect on the resource consumption of WebContainer and would also positively contribute to the performance. A decent chunk of our users use Firefox and it's unfortunate that for Firefox we still need to rely on the polyfill.
CC @bholley
Assignee | ||
Comment 38•2 years ago
|
||
Safari currently doesn't support this feature. The only engine supporting it at the moment is v8. If your web container is working without a polyfill in both Safari and Chrome, perhaps you mean a different feature? Can you give us more details about the difference in how you are running it in Safari and Firefox?
Comment 39•2 years ago
|
||
Yea it's not yet in stable for Safari but it's been shipped to TP. I mean we are still working on support for Safari and for the time being we still have to reply on the polyfill because waitAsync
hasn't been shipped for stable yet.
Assignee | ||
Comment 40•2 years ago
•
|
||
Part of the reason for that may be because the waitAsync specification is still stage 3. They do not ship until a feature is in stage 4 for complex features, as they expressed in committee recently.
As you mention in the safari bug, you are able to polyfill in firefox, but were unable to do so in safari -- https://bugs.webkit.org/show_bug.cgi?id=241414 -- in other words the situation was more severe in the case of safari in that your web containers did not work via the polyfill. In the case here, you have a working polyfill, but it is not as performant as it should be. This is of course not ideal.
We consider performance to be an important part of the web. However at the moment we have our focus on performance elsewhere, meaning that our ability to do feature work related to TC39 is impacted for a large chunk of this year. Given that this is a stage 3 proposal, and an adequate though not ideal solution exists, it is unlikely that our current prioritization will change right now. We will discuss your case at our next meeting. As Iain mentioned, the feature is almost finished and once we have time for it again it will likely go quickly. We of course welcome contributions, and if you have some engineers with expertise in this space who have cycles to address Iain's correctness concerns, it may go faster.
From reading the safari bug, you still have blockers on safari for fully implementing your web components work, namely shared array buffer cloning. Is this still true?
Comment 41•2 years ago
|
||
Note that the current synchronous implementation of Atomics.wait
is causing troubles to DevTools which is unable to execute its JS codebase while the worker is paused by this method. I provided details, with a minimal test reproducing the issue in bug 1821250 comment 16.
If the async implementation prevent these troubles, it would be an argument to move forward with the async flavor.
Otherwise some help would be appreciated to make the DevTools/DOM codebase work with both API flavors.
I'm wondering if the direct usage of pthread_cond_wait
isn't bypassing the dom/ codebase, which ends up being paused unexpectedly.
Note that wasm-bindgen, which is the typical/popular way to use Rust in a web page through WASM uses a shim to provide async wait based on sync wait:
https://github.com/rustwasm/wasm-bindgen/blob/9958e2ec44651fd7b02b023439118328181b1733/crates/futures/src/task/worker.js#L4
https://github.com/rustwasm/wasm-bindgen/blob/9958e2ec44651fd7b02b023439118328181b1733/crates/futures/src/task/wait_async_polyfill.rs#L55-L56
This may broaden the issue with DevTools.
Reporter | ||
Updated•2 years ago
|
Updated•2 years ago
|
Comment 42•2 years ago
|
||
Depends on D159757
Comment 43•2 years ago
|
||
Depends on D186818
Comment 44•2 years ago
|
||
Hey all. I wanted mention that Atomics.waitAsync
is now fully supported in both Chrome and Safari. We are running into issues with WebContainer due to the missing support for a native version of this and the polyfill that uses a dedicated worker doesn't fully work as waitAsync
cannot be accurately polyfilled. It also causes an overhead of dedicated workers which is very unfortunate for an environment like WebContainer which is already putting some strain on the process. I think it'd be really nice if Firefox would support this natively as well.
Updated•2 years ago
|
Comment 45•1 year ago
|
||
Comment 46•1 year ago
|
||
The waiter list for Atomics.wait is homogeneous. With the addition of Atomics.waitAsync, the waiter list will contain both sync and async waiters. (Waiters must be notified in the order in which they waited, so it has to be a single list.) While we're refactoring this code, I added a dedicated header node to the waiter list, which lets us remove a node from the list without needing a reference to the SharedArrayRawBuffer.
Comment 47•1 year ago
|
||
We currently wait until all OffThreadPromiseTasks are resolved before shutting down the runtime. This is not ideal for something like waitAsync: if the notify never comes, then we will never shut down. This patch adds a mechanism for OffThreadPromiseTasks to opt into being cancellable, in which case we will delete them immediately on shutdown (after calling prepareForCancel
on them) instead of blocking.
Comment 48•1 year ago
|
||
Atomics.notify can be called from any context, but the promise created for Atomics.waitAsync must be resolved in the context that created it. OffThreadPromiseTask will dispatch the message to the correct event loop.
When the runtime shuts down -- for example, when a worker is terminated -- we clear out the async waiters associated with that runtime. The interaction between agent termination and waiters is currently somewhat underspecified (see https://github.com/whatwg/html/issues/8276). This approach is consistent with Chrome's existing implementation.
Comment 49•1 year ago
|
||
This moves some code around and renumbers steps in preparation for the next patch.
Comment 50•1 year ago
|
||
This implements Atomics.waitAsync, excluding cases with non-zero timeouts. Most of the rest of this patch stack is dedicated to adding timeout support.
Breaking out the critical section into its own function made locking easier.
Comment 51•1 year ago
|
||
Atomics.waitAsync creates a promise that can be resolved via Atomics.notify, or after an optional timeout. The timeout is implemented using the HostEnqueueTimeoutJob hook, which queues a global task (not a microtask).
This patch adds a delay parameter to the DispatchToEventLoop callback, and implements it in cases where the infrastructure already exists (the main thread and worklets). The next patch adds an implementation in worker threads. A later patch in the stack adds an implementation for the shell.
Comment 52•1 year ago
|
||
I don't really know what I'm doing with the CC macros, but this seemed to work.
Comment 53•1 year ago
|
||
We want to use a priority queue to keep track of pending timeouts, but the natural way to prioritize them (lowest timestamp first) is not compatible with taking the highest priority item. Instead of messing around with negative TimeDurations, it seems nicest to just update PriorityQueue to be more flexible.
This patch also adds a highest
helper to peek at the highest priority item without removing it.
Comment 54•1 year ago
|
||
The task/microtask implementation in the shell is a bit of a mess. In particular, where the browser expects the microtask queue to be drained before running any tasks, the shell (which only supports a very limited set of tasks) drains the task queue before draining the microtask queue. I made a few attempts at fixing this, but ran into problems. For example, the setTimeout mechanism in the test262 Atomics.notify/wait/waitAsync shell code implements timeouts using a chain of promises. If we dispatch delayed tasks to an outer task queue, and wait for the microtask queue to drain before draining the outer task queue, then the delayed task can't fire until after the promise-based setTimeout has expired.
This patch is slightly less principled, but has the benefit of actually working. At some point we should consider fixing the task implementation in the shell.
Comment 55•1 year ago
|
||
Comment 56•1 year ago
|
||
Right now, we don't support await
in worker threads in the shell: we never drain the job queue, so enqueued promise resolutions never fire.
In addition, in test262 tests, we have to make sure we don't terminate the worker before it's done. This can be a problem if it's awaiting something being done in another thread. Conveniently, these tests all call $262.agent.leaving()
to signal that there's nothing left to do, so we can block waiting for that.
Comment 57•1 year ago
|
||
The alternative to this patch is having each AsyncWaiter increment the refcount of the underlying SharedArrayRawBuffer, keeping it alive as long as there's an unnotified waiter.
Comment 58•1 year ago
|
||
Comment 59•1 year ago
|
||
This change is necessary to make the test262 Atomics implementation of setTimeout work with timeout tasks. Without this change, setTimeout, which uses a chain of promises, would spin the microtask loop and prevent any delayed-dispatch tasks from resolving.
Comment 60•1 year ago
|
||
Comment 61•1 year ago
|
||
This may be too detailed, but I wrote it out in an attempt to convince myself that I knew what was going on.
Comment 62•1 year ago
|
||
After writing the main patch stack, I noticed step 3 of NotifyWaiter, which specifies that resolving a promise from the same thread that created it should go straight to the microtask queue, instead of enqueuing a task.
Comment hidden (off-topic) |
Updated•8 months ago
|
Comment 64•8 months ago
|
||
This is a simple test if anyone wants to confirm or understand how this works in other browsers and should work in Firefox.
// create a mutex with a value of 0 at index 0
const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);
var b0 = document.createElement('input');
b0.type = 'button';
b0.value = 'unlock mutex';
document.body.appendChild(b0);
b0.addEventListener('pointerdown', function(e) {
// set mutex value to 1 at index 0
Atomics.store(int32, 0, 1);
// notify the mutex (any wait or waitAsync) of index 0
Atomics.notify(int32, 0);
});
// wait for index 0 to change to 1
let never_name_res_intent = Atomics.waitAsync(int32, 0, 0);
if (never_name_res_intent.async) {
never_name_res_intent.value.then(function(fulfillment) {
console.log('mutex fulfillment', fulfillment);
console.log('mutex int32 changed from 0 to 1 at index 0.');
});
}
Updated•8 months ago
|
Updated•8 months ago
|
Updated•7 months ago
|
Assignee | ||
Comment 65•6 months ago
|
||
I am adopting this to hopefully get it across the line.
Updated•5 months ago
|
Assignee | ||
Comment 66•5 months ago
|
||
Depends on D212886
Updated•5 months ago
|
Assignee | ||
Comment 67•5 months ago
|
||
This clears the relationships between all AsyncFutexWaiter. We discussed this as "orphaning" the
list members and having them point to themselves as lists of 1 element. But on reflection I am not
sure we gain all that much from this approach and we may as well just remove it from the list, as
future removals from a list will just be no-ops. We could also add a "isLinked" bool to avoid
additional work here, if that feels cleaner.
If I missed some subtle reason why we want a list of 1 element here instead, let me know and I can
add that back in.
Depends on D212886
Assignee | ||
Comment 68•5 months ago
|
||
Depends on D242174
Assignee | ||
Comment 69•5 months ago
|
||
Assignee | ||
Comment 70•5 months ago
|
||
Depends on D242357
Updated•4 months ago
|
Updated•4 months ago
|
Assignee | ||
Comment 71•4 months ago
|
||
This patch is a major refactoring that introduces a concept of ownership of JS::Dispatchables via
UniquePtrs. Embeddings must now use UniquePtrs to reference Dispatchables passed by the engine.
Two static methods are introduced to Dispatchable: Run and ReleaseFailedTask.
Run is used to release the UniquePtr prior to running. We do this because we still rely on the
js_delete(this) call inside of OffThreadPromiseTask::run in cases of web assembly, namely cases when
we never dispatch to the embedding and need to clean up
1.
This will be refactored in a future step covered by Bug# 1959032.
ReleaseFailedTask similarily releases the task. This relies on the task having been registered with
the OffThreadPromiseRuntimeState class. As the only override of run
is in OffThreadPromiseTask,
thus far there have been no guarantees of how this is done. This will be addressed in the next
patch.
Depends on D212878
Assignee | ||
Comment 72•4 months ago
|
||
This patch removes the live state all together. The reasoning for this is as follows:
Previously, the live set had the following responsibilities:
- the set was a set of raw pointers. This functioned as a list of tasks that would potentially be
dispatched and had been initialized. - If we attempted to dispatch, but failed to do so (such as during shutdown), we would increment the
numFailed_ (previously termed numCanceled_) counter. However with the shift to using a UniquePtr
in the embedding side, we now transfer ownership of the UniquePtr when a task fails to the dead()
list. We no longer need the live list to manage this information, instead we can iterate over the
dead Fifo. - In the future, the plan was the use the live list for cancellable. However, this too is better
represented as an owned list, as the runtime can hold the token for cancellable tasks until it is
dispatched. This becomes clearer when we implement the WaitNotifyTask, see further in the patch queue.
Now, the live set has been replaced with a numRegistered_ counter. This is the number of tasks
currently registered and not yet completed. If a task fails, or is registered as cancellable, it
will go into one of two collections for tracking owned Dispatchables that potentially need to be
cleaned up on shutdown.
In the case of Cancellables, they are blocked from using the regular DispatchResolveAndDestroy
method. They now have a special method: releaseCancellableAndDispatch, that will remove itself from
the cancellable list, and transfer ownership of the UniquePtr to the embedding. If this fails, it
will follow the same failure route as for all other dispatchables.
In the case of dead Dispatchables: They can not be redispatched, instead they wait until shutdown to
be cleared. They are cleared in a very similar way as the old live list, but with less book-keeping.
We still cannot remove the js_delete pattern, as WASM relies on it.
Depends on D244936
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Comment 73•3 months ago
|
||
Comment 74•3 months ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/b650a663c8bf
https://hg.mozilla.org/mozilla-central/rev/33cf31b8a76d
https://hg.mozilla.org/mozilla-central/rev/ba7fceabe79d
https://hg.mozilla.org/mozilla-central/rev/42672e9179c7
https://hg.mozilla.org/mozilla-central/rev/3cd70f69a47b
https://hg.mozilla.org/mozilla-central/rev/4d05b5882f15
https://hg.mozilla.org/mozilla-central/rev/752f36fbf3bf
https://hg.mozilla.org/mozilla-central/rev/a14e8387886b
https://hg.mozilla.org/mozilla-central/rev/ba97fd6dea02
https://hg.mozilla.org/mozilla-central/rev/58143ce1c9e9
https://hg.mozilla.org/mozilla-central/rev/83420669a560
https://hg.mozilla.org/mozilla-central/rev/7bb5b4643f2e
https://hg.mozilla.org/mozilla-central/rev/688ce4dafd50
https://hg.mozilla.org/mozilla-central/rev/f1096734b177
https://hg.mozilla.org/mozilla-central/rev/eed8118ec3ef
https://hg.mozilla.org/mozilla-central/rev/437ffb94e211
https://hg.mozilla.org/mozilla-central/rev/89a8439c29a5
https://hg.mozilla.org/mozilla-central/rev/5833a4f27870
https://hg.mozilla.org/mozilla-central/rev/35fa69f66528
https://hg.mozilla.org/mozilla-central/rev/51eb71b80c66
Comment 75•3 months ago
|
||
Yulia, you're my hero.
Comment 76•3 months ago
|
||
FF140 MDN docs work for this can be tracked in https://github.com/mdn/content/issues/39615.
Just FMI, what's the logic of only checking the memory value when you first call the method and always returning OK if notify()
is called? Docs for this having minor changes in https://github.com/mdn/content/pull/39679 but it wasn't clear to me why you ever need to check this value.
Updated•3 months ago
|
Assignee | ||
Comment 77•3 months ago
|
||
I'm not sure what you mean by "checking the memory value when you first call the method". Do you mean this line?
"The Atomics.waitAsync()
static method verifies that a shared memory location contains a given value, immediately returning with "not-equal"
if the memory location does not match the given value, or "timed-out"
if the timeout was set to zero."
The "not-equal" value is used for both wait and waitAsync, my understanding is it works the same way as futex does on linux: https://www.man7.org/linux/man-pages/man7/futex.7.html
I think there is a pretty good explanation of how this works in this blog post: https://eli.thegreenplace.net/2018/basics-of-futexes/
Effectively, futexes are a lower level construct, which allow efficient user-space mutex implementation. The way that wait
and notify
operations play into this is that they allow a user to implement a simple, efficient "test and sleep" loop. In particular, it is designed this way so that there is minimal kernal involvement. The test is done at the beginning, followed by a sleep, after which we can perform the test again (or do other work). If the thread is woken using notify, then we know that the value has changed and should either check again or start doing other work. The semantics of the actual locking mechanism are left up to the user to define. Until that point we can continue to test and sleep (or bail out) until we get what we are looking for. For example, let's say we have two competing threads. One is reading data and the other is writing data. Let's say that reading data actually takes some time. We are waiting for the data we are reading to change. There is a possibility that, between us starting to read the data, and us waiting for the data to change, the data changes. In this case -- not-equal would be returned, as the test part of test-and-sleep failed. basically, "not-equal" is for the case where there was a change written from another thread just before we start to sleep.
What is important is that these steps are all atomic. That means that they cannot be interleaved or interrupted. If atomic.wait(.., .., 0) succeeds, that means that the value at the moment that the wait begins is definitely 0, even if it changes after. Once the thread times out, it means that we can wait again, so long as no interleaved instructions changed the value since we started waiting. There is a possibility of this, especially if a user forgets to use notify after storing a new value (but this would be bad and could lead to a hang). But it might also be possible in the long-read case described above, or if a user space mutex schedules other work on the thread in the interim.
Is that what you were asking? Multi-threaded code is pretty complex
Comment 78•3 months ago
•
|
||
@Yulia. Thank you, that is what I meant, and that explanation covers it.
FYI only, TL;DR
The user of this method wants to be woken up when the monitored value is changed (by any other thread).
My assumption was that the notification would be generated by some monitor code in the method, because I was thinking that the purpose was to monitor values. But the actual purpose is to synchronize threads.
So essentially we have a value in shared memory that indicates a sync point. A thread can use awaitSync() to check the value is not the expected sync value and immediately proceed. If it is the expected sync value then the thread waits for the timeout, after which it can poll again, or the promise to resolve with value "ok" which indicates another thread is calling notify()
- i.e. it is at the sync point.
The detail of how you'd actually set this up with multiple sync points is beyond me, but I think this broad understanding is probably enough for now.
Updated•3 months ago
|
Assignee | ||
Comment 79•3 months ago
|
||
My assumption was that the notification would be generated by some monitor code in the method, because I was thinking that the purpose was to monitor values. But the actual purpose is to synchronize threads.
That is right. A common pattern demonstrating this is a reader/writer pattern. You can have any number of readers and any number of writers, but the version I'm most familiar with is single producer multiple consumer.
I've created an example for you of a writer on the main thread, with several workers reading the data. It also demonstrates what the new feature enables users to do, which is to do a promise based wait on the main thread. This is better than the alternatives we had before.
Comment 80•3 months ago
|
||
Thank you so much - makes a lot more sense to me now. I've asked @Josh-Cena on MDN if he can integrate this as an even simpler example, but if not, I will try.
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Description
•