-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow postMessage-ing AbortSignal #948
Comments
Wouldn't that mean that |
Yes. Although once
Is there any particular reason? Web APIs that accept And from what I've found, doing a macrotask to allow any asynchronous notifications to come through actually kills performance quite heavily in some cases (compared to |
Yeah, breaking |
That isn't true? |
I mean they don't necessarily go via
Yes this is what I'm currently using for aborting in compute loops in workers, but it is a bit annoying not to be able to use the same API on one side that is used throughout the rest of the code. Also my proposal doesn't actually violate run to completion, as it doesn't allow any additional JS code to run at the same time, in this regard is isn't significantly different to |
Just to clarify how I'd imagine this working, if I've added an explicit // Structured serialize steps
function serializeAbortSignal(abortSignal) {
if (abortSignal.aborted) {
return { type: "AbortSignal", aborted: true };
} else {
const messageChannel = new MessageChannel();
abortSignal.addEventListener("abort", () => {
// Update so aborted can be observed synchronously
Atomics.store(sharedAbort, 0, 1));
// And send it in case it's listening asynchronously
messageChannel.port1.postMessage("aborted");
messageChannel.port1.close();
});
const sharedAborted = new Uint8Array(new SharedArrayBuffer(1));
return {
type: "AbortSignal",
aborted: false,
abortPort: messageChannel.port2,
sharedAborted,
};
}
}
// Structured deserialize steps
function deserializeAbortSignal(serializedAbortSignal) {
const abortSignal = new AbortSignal();
if (serializedAbortSignal.aborted) {
abortSignal.[[aborted]] = true;
return abortSignal;
}
const { abortPort, sharedAborted } = serializedAbortSignal;
// When a request to synchronize abort state with the main thread
// happens we need to potentially fire "abort", and return
abortSignal.[[synchronizeSteps]] = () => {
if (Atomics.load(sharedAborted, 0)) { // If aborted is set
// Set the aborted flag synchronously
abortSignal.[[aborted]] = true;
// Fire the abort event and run any handlers synchronously
abortSignal.dispatchEvent(new Event("abort"));
abortPort.close();
}
};
// Abort asynchronously if synchronizeAbort is not used
abortPort.addEventListener("message", () => {
if (abortSignal.[[aborted]]) return;
abortSignal.[[aborted]] = true;
abortSignal.dispatchEvent(new Event("abort"));
abortPort.close();
});
return abortSignal;
}
class AbortSignal extends EventTarget {
[[aborted]] = false;
[[synchronizeSteps]] = () => void;
get aborted() {
return this.[[aborted]];
}
// This asks the abort signal to synchronize with the other thread
// by default the synchronize steps are to do nothing, this is the
// case today and would remain the case when the abort signal does
// not come from another thread
synchronizeAborted() {
if (this.[[aborted]]) {
return true;
}
this.[[synchronizeSteps]]();
return this.[[aborted]];
}
} // inside a worker
self.addEventListener("message", (evt) => {
const { abortSignal } = evt.data;
while (true) {
// Synchronize abortSignal with the main thread and return true if aborted
if (abortSignal.synchronizeAborted()) {
break;
}
// ...heavy compute...
}
}); |
Just to offer a different perspective on this... in Node.js (which is, of course, a different environment than browsers) we have more of a notion of objects that can be shared across workers -- much like |
With the new For this we could have a design more like: AbortSignal serialization steps given _abortSignal_ and _serialized_:
1. If _abortSignal_ is **aborted**.
a. Set _serialized_.[[AbortReason]] to _abortSignal_'s **abort reason**.
b. Set _serialized_.[[MessagePort]] to **undefined**.
2. Else.
a. Let _port1_ be a new MessagePort in the current realm.
b. Let _port2_ be a new MessagePort in the current realm.
c. Entangle _port1_ and _port2_.
d. Set _serialized_.[[AbortReason]] to **undefined**.
e. Set _serialized_.[[MessagePort]] to the **sub-serialization** of **port2**.
f. Add the following **abort algorithm** to _abortSignal_:
a. Post a message consisting of _abortSignal_'s **abort reason** to _port1_.
b. Disentangle _port1_.
AbortSignal deserialization steps given _dataHolder_ and _value_:
1. If _dataHolder_.[[AbortReason]] is not **undefined**:
a. Abort _value_ with _dataHolder_.[[AbortReason]].
2. Else.
a. Assert _serialized_.[[MessagePort]] is a MessagePort.
b. Add a handler for _serialized_.[[MessagePort]]'s "message" event with the following steps:
i. **abort** _value_ with the *data* of the message.
ii. Disentangle _serialized_.[[MessagePort]] |
That seems reasonable to me and is compatible with what we are planning to do for Fetch (see whatwg/fetch#1343). (The current plan there is to serialize/deserialize abort reason and recreate the |
There is a challenge here since given that the structured clone algorithm doesn't handle custom Error objects well without losing data. Some platforms that might not be able to handle DOMException as a host object, for instance, might not be able to deserialize it as a DOMException on the receiving end. And if I set reason to an Error subclass (e.g. class FooError extends Error), it will come across as just an Error on the other side. That's not to say we should block making AbortSignal transferable, only that we need to acknowledge the limitations and lack of fidelity. (I believe we already have the same issue with communicating errors in the streams impl so it's not a new issue) |
I'm happy to make a PR if you want.
This is part of the wider issue of not having any userland way of having custom structured serialized things. How do existing structured clonables/transferables like Nowadays it might be more viable to have userland structured cloning by just pointing to an ES module for deserialization. With module blocks the ergonomics of it could even be rather nice as you could just return a module block from some hypothetical serialize method. This is just an idea on my part though and is well beyond the scope of this issue though. |
In node.js we implement a custom value serializer delegate that allows us to serialize and deserialize host objects. The key challenge is that those have to be backed in the prototype chain by an underlying native c++ object. Today I implemented a new version of DOMException for node.js that uses this mechanism and it works well. But, it would be generally better to have a way of supporting custom userland serialization. |
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
Just an update on this... we recently just landed early support for this in Node.js. When sending an |
This is essentially what I'd capture a PR, although I do notice the Node implementation has
This seems acceptable as communication with a worker (other than |
Yeah, the transferable bit is temporary. I expect to have that fixed in the
coming few weeks
…On Sat, Dec 18, 2021, 19:38 James Browning ***@***.***> wrote:
Just an update on this... we recently just landed early support for this
in Node.js. When sending an AbortSignal over postMessage, a new
AbortSignal is created on the receiving end connected to the original via
a MessagePort. When the original signal is triggered, it has an abort
algorithms that sends a message over the MessagePort that triggers the
other. Very simple and straightforward.
This is essentially what I'd capture a PR, although I do notice the Node
implementation has AbortSignal as a [Transferable] due to some technical
limitations, my intent was just to make AbortSignal serializable as the
AbortSignal will still be perfectly usable on the original thread (as it
is in Node anyway) which doesn't really feel like a "transferable". Do you
think this would be a problem for Node to change later?
The key caveat is that there is some latency between when the original
AbortSignal is triggered and when the newly created one is triggered due
to the message passing infrastructure (in Node.js there's one event loop
turn required)
This seems acceptable as communication with a worker (other than
SharedArrayBuffer) always requires sending a message anyway.
—
Reply to this email directly, view it on GitHub
<#948 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADLM6NA5FFHE4OVNMBLWULURVHTJANCNFSM4XOO2XVQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: nodejs#41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
Allows for using `AbortSignal` across worker threads and contexts. ```js const ac = new AbortController(); const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data.addEventListener('abort', () => { console.log('aborted!'); }); }; mc.port2.postMessage(ac.signal, [ac.signal]); ``` Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #41050 Refs: whatwg/dom#948 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Nagy <ronagy@icloud.com>
in the documentation of Node.js and HTML, the interface for transmitting a signal to the worker is not described, is this a problem with the documentation or the implementation yet? |
This is still just a suggestion, no browsers have implemented this and there is no spec.
Node.js implements something similar to the suggestion, which is documented. |
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Seems it's a non-standard feature. Dropping because it would still have required users to do extra work like providing their own MessageChannel implementation to override the included one. It's a feature that requires runtime integration, so I can only provide a basis and not a full implementation. whatwg/dom#948
Currently communicating to a worker that work should be aborted is somewhat annoying. This is especially true for work that may be run in a synchronous loop as one can not simply send forward a
MessageChannel
that signals aborting, but rather you need to send aSharedArrayBuffer
that can be synchronously read.I'd like to propose the ability to
.postMessage
anAbortSignal
so that we can communicate aborting to workers in a consistent way to aborting on the main thread. Ideally this would support the synchronous case as well (e.g. callingcontroller.abort()
from the original thread would synchronously updatesignal.aborted
in the other thread so that compute loops can simply doif (signal.aborted) { /* terminate work */ }
).If there's interest, I would be willing to create a PR for this.
The text was updated successfully, but these errors were encountered: