fix: always terminate active Node Streams (#43056)

`.destroy()` is an important method in the lifecycle of a Node.js
Readable stream. It is typically called to reclaim the resources
(e.g., close file descriptor). The only situations where calling
it manually isn't necessary are when the following events are
emitted first:

- `end`: natural end of a stream
- `error`: stream terminated due to a failure

Prior to this commit the ended state was incorrectly tracked together
with a pending internal error. It led to situations where the request
could get aborted during a read and then get marked as ended (having
pending error).

With this change we disentangle pending "error" and "destroyed" cases to
always properly terminate an active Node.js Readable stream.

Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
Fedor Indutny 2024-07-27 10:25:43 -07:00 committed by GitHub
parent 8db1563d73
commit 55e7a47d70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 9 deletions

View file

@ -1194,6 +1194,30 @@ describe('protocol module', () => {
expect(body).to.equal(text);
});
it('calls destroy on aborted body stream', async () => {
const abortController = new AbortController();
class TestStream extends stream.Readable {
_read () {
this.push('infinite data');
// Abort the request that reads from this stream.
abortController.abort();
}
};
const body = new TestStream();
protocol.handle('test-scheme', () => {
return new Response(stream.Readable.toWeb(body) as ReadableStream<ArrayBufferView>);
});
defer(() => { protocol.unhandle('test-scheme'); });
const res = net.fetch('test-scheme://foo', {
signal: abortController.signal
});
await expect(res).to.be.rejectedWith('This operation was aborted');
await expect(once(body, 'end')).to.be.rejectedWith('The operation was aborted');
});
it('accepts urls with no hostname in non-standard schemes', async () => {
protocol.handle('test-scheme', (req) => new Response(req.url));
defer(() => { protocol.unhandle('test-scheme'); });