mirror of
https://github.com/axios/axios.git
synced 2026-04-11 02:11:50 +08:00
fix(fetch): cancel ReadableStream body after request stream capability probe (#7515)
The module-level capability probe in the fetch adapter creates a ReadableStream as a Request body to test for streaming support, but never cancels it. The Request constructor sets up an internal pull pipeline on the stream; since the stream is never consumed or cancelled, the [[pullAlgorithm]] Promise remains pending indefinitely, causing an async resource leak detectable by Node.js async_hooks and Vitest --detect-async-leaks. Extract the ReadableStream to a variable and call body.cancel() after the probe completes to properly tear down the stream's internal pipeline.
This commit is contained in:
parent
76794ac27a
commit
94e1543576
@ -66,8 +66,10 @@ const factory = (env) => {
|
||||
test(() => {
|
||||
let duplexAccessed = false;
|
||||
|
||||
const body = new ReadableStream();
|
||||
|
||||
const hasContentType = new Request(platform.origin, {
|
||||
body: new ReadableStream(),
|
||||
body,
|
||||
method: 'POST',
|
||||
get duplex() {
|
||||
duplexAccessed = true;
|
||||
@ -75,6 +77,8 @@ const factory = (env) => {
|
||||
},
|
||||
}).headers.has('Content-Type');
|
||||
|
||||
body.cancel();
|
||||
|
||||
return duplexAccessed && !hasContentType;
|
||||
});
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
makeEchoStream,
|
||||
} from '../../setup/server.js';
|
||||
import axios from '../../../index.js';
|
||||
import { getFetch } from '../../../lib/adapters/fetch.js';
|
||||
import stream from 'stream';
|
||||
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill.js';
|
||||
import util from 'util';
|
||||
@ -652,4 +653,32 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('capability probe cleanup', () => {
|
||||
it('should cancel the ReadableStream created during the request stream probe', () => {
|
||||
// The fetch adapter factory probes for request-stream support by creating
|
||||
// a ReadableStream as a Request body. Previously the stream was never
|
||||
// cancelled, leaving a dangling pull-algorithm promise (async resource leak
|
||||
// visible via `--detect-async-leaks` or Node.js async_hooks).
|
||||
//
|
||||
// Calling getFetch with a unique env triggers a fresh factory() execution
|
||||
// (including the probe). We spy on ReadableStream.prototype.cancel to
|
||||
// verify it is invoked during the probe.
|
||||
|
||||
const cancelSpy = vi.spyOn(ReadableStream.prototype, 'cancel');
|
||||
|
||||
try {
|
||||
// Unique fetch function ensures cache miss → factory() re-runs the probe.
|
||||
const uniqueFetch = async () => new Response('ok');
|
||||
getFetch({ env: { fetch: uniqueFetch } });
|
||||
|
||||
assert.ok(
|
||||
cancelSpy.mock.calls.length > 0,
|
||||
'ReadableStream.prototype.cancel should be called during the capability probe'
|
||||
);
|
||||
} finally {
|
||||
cancelSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user