-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
Support AbortSignals in async API #31971
Comments
I can see how a generalized cancellation mechanism would be useful; that's essentially what Probably the best way to get the conversation going is by opening a pull request that adds AbortSignal support in a few places, let's say The exact set of methods doesn't really matter as long as it's diverse enough to get a feel for the necessary changes.
|
There is has been work in TC39 to provide cancellation and cleanup protocols within the language, which is why I've avoided pushing too much on this. Using the web API might be fine, I just want to make sure we don't feel forced to pick that specific API. |
I have been experimenting a bit recently with abort signals and the usefulness it could provide (it is pretty hard actually). A few interesting examples are listed below:
In the app I also made use of a "home-made", process-abort-signal here: It serves the purpose that I can use the same "abort" logic that is used in a generic fashion also in a cli-context: Resulting in a CLI tool that allows graceful abortion of sub-processes in any given situation. |
@devsnek What work of the TC39 are you referring to? |
Hi, I'd like to work on this!
That sounds good, I'll start out with |
fyi, whatwg streams use AbortSignal also - not just only fetch.
anyhow - maybe we should focus on EventTarget first? |
I'd be in support of adding a cancellable primitive to some base operations such as Is EventTarget needed at all? I'd need to know more about adding it to streams and http. Note that: createReadStream('file.bin', { signal: req.signal }).pipe(res) Is extremely far from what anybody that serve files with Node.js core would do. Check out https://github.com/pillarjs/send to know more (at the time of this writing, it's 1129 lines of code). The snipped above works only because createReadStream('file.bin', { signal: req.signal }).pipe(uploadToSomewhereElse) This API needs to be designed correctly to be able to work with on streams. As an example, this would be able to tear down the full pipe on abort: createReadStream('file.bin').pipe(uploadToSomewhereElse, { signal: req.signal }) Note that all the above is already solved in Node.js with destroyOnAbort({ stream, signal }) To achieve our goal. I would like to see some forward-thinking on how this API will be used by the Node and JS ecosystem and the interaction with the rest of the Node.js Stream libraries. It might well be that we should create a repo and go through some code example before settling into how this API would work. cc @nodejs/streams @nodejs/http |
@mcollina Just about the EventTarget/EventEmitter part: When you construct an AbortController, that will create an AbortSignal, which would be some kind of event thing. I was imagining that this would initially be an EventEmitter, but if we could make it also act as an EventTarget, then this could improve Node/Web compatibility: for the case where you write some code that's cancellable and want to listen for the cancellation being triggered, you'd use |
I don't think we should expand on the await pipeline(
createReadStream('file.bin'),
uploadToSomewhereElse,
{ signal: req.signal }
) |
I'm generically ok with the above, I'd like to see an implementation of this and how it could be used in core. (We might work on this in a fork of Node.js similarly to how quic and esm have been developed). |
For a proof of concept, I'm intending to use a hacky, approximate, non-compliant implementation of AbortSignal that uses EventEmitter. Basically just this: class AbortSignal extends EventEmitter {
constructor() {
super();
this.aborted = false;
}
}
class AbortController {
constructor() {
this.signal = new AbortSignal();
}
abort() {
if (this.signal.aborted) return;
this.signal.aborted = true;
this.signal.emit('abort');
}
} The reason is to make this experiment agnostic to the question of whether the implementation in the end should accept AbortSignals that are EventTargets, EventEmitters, or both. Disclaimer, my judgement and experience with what should be in Node core and what shouldn't, is much less than probably anyone else on this thread. But it seems reasonable to me to treat passed-in AbortSignal objects as if they were EventTargets (that is, they need to have an |
Update on this... |
Coincidence, I am just now resuming work on my branch and rebasing it on top of your work. If it's considered wanted to split out mergeable parts of the work-in-progress, then I can soon make a PR that adds a wrapper to FSReq for |
I've pushed to my branch (https://github.com/nodejs/node/compare/master...ptomato:31971-abortcontroller?expand=1) and it now uses the built-in AbortController in the examples. I think the first two commits (exposing |
GetDOMException() was a private function in node_messaging, but we would like to use it to throw AbortErrors when cancelling something via an AbortController. Move it into environment.cc to be in the same place as GetPerContextExports(), and expose it internally in node_internals.h. Refs: nodejs#31971
This adds a FSReqBase::Cancel() method which calls uv_cancel(). This shows up in JS as a cancel() method on FSReqCallback and FSReqPromise. Refs: nodejs#31971
Just an update here that AbortController and Event are now live and mostly whatwg compatible (we're adding final tests and fixing things). Some Node.js APIs have already gotten AbortSignal support (like EventEmitters and promises versions of timers). This is a good area to contribute in :] |
uh? have have timers gotten support for abort signal? can you now do: setTimeout(() => {
}, 1000, { signal }) have never heard of "promises versions of timers" |
@jimmywarting this would be in conflict with JavaScript's setTimeout API:
|
yea, you are right! i just found the promise version of timers here in https://nodejs.org/dist/latest-v15.x/docs/api/timers.html#timers_timers_promises_api node_fetch previously had a timeout option but was deprecated/removed in v3 in favor of abortSignal + timer was not a spec'ed signal = some_node_util.timeoutSignal(1000) // or:
signal = AbortSignal.timeout(1000)
fetch(url {signal}) See this thread for more information whatwg/fetch#951 (comment) |
Hmm, I prefer to see the difference in the error message and created recently a |
They have in Like Martin said - we can't do this for setTimeout to not break our own code :] |
Actually, having a helper on AbortControllers to abort automatically after a time could be interesting/useful though it's probably a good idea to ask that of whatwg (we can extend it and ship a subclass but would rather not until working with spec bodies hasn't worked - and so far whatwg have been very helpful when we presented compelling cases for APIs that would make sense in browsers as well to them). |
Doing some more work on this, I summarized some findings here, it thought it may give some ideas on improved tooling: https://qiita.com/martinheidegger/items/3e6355e96e85fc1c841e |
We've landed a bunch of apis in fs/child_process/http/http2 with AbortSignal support since this btw, we also added signal support to addEventListener and are working on a way to chain AbortSignals correctly. |
Ooh, cool I just realized both your original examples already work in Node now :] : // Your example here
createReadStream('file.bin', { signal: req.signal }).pipe(res)
// Becomes (and is working today in Node
new Readable({ signal , ... rest}).pipe(res); // or, if you don't control creation
addAbortSignal(signal, getReadableSomehow()).pipe(res);
// And your example here
await readFile('file.bin', { signal: process.abortSignal });
// just works as is with `fsPromises.readFile` |
Given that progress on this is already well underway, I'm going to close this specific issue. |
AbortSignals are used in fetch to abort a request. They are useful to close requests if they don't happen to be necessary. AbortSignals are not widely used yet in the JavaScript ecosystem but they seem like they could be useful if they would find a wider adoption.
A node.js API that comes to mind is if a web framework would provide an abort signal that this could be comfortable to be used as a means to abort a file stream:
Other places this might be if there was an abort signal for the process that could be used instead of
process.on('SIGTERM', ...)
, ex.:Which could be an easy means to stop allocating money if a CLI tool is used to read a very large file by accident.
At the current point I am not sure at how many places this would be useful, or how useful it would be at each case, but I think opening the discussion has merit.
The text was updated successfully, but these errors were encountered: