2
0
mirror of https://github.com/tenrok/axios.git synced 2026-05-15 11:59:42 +03:00

fix(http2): fix possible race condition when handling http2 stream on almost timed out session by improving timeout out algorithm; (#7186)

This commit is contained in:
Dmitriy Mozgovoy
2025-10-27 00:46:40 +02:00
committed by GitHub
parent 08db960d9f
commit d000fbfd07
2 changed files with 62 additions and 21 deletions
+46 -10
View File
@@ -87,7 +87,15 @@ class Http2Sessions {
const session = connect(authority, options);
session.once('close', () => {
let removed;
const removeSession = () => {
if (removed) {
return;
}
removed = true;
let entries = authoritySessions, len = entries.length, i = len;
while (i--) {
@@ -99,9 +107,41 @@ class Http2Sessions {
}
}
}
});
};
Http2Sessions.setTimeout(session, options.sessionTimeout);
const originalRequestFn = session.request;
const {sessionTimeout} = options;
if(sessionTimeout != null) {
let timer;
let streamsCount = 0;
session.request = function () {
const stream = originalRequestFn.apply(this, arguments);
streamsCount++;
if (timer) {
clearTimeout(timer);
timer = null;
}
stream.once('close', () => {
if (!--streamsCount) {
timer = setTimeout(() => {
timer = null;
removeSession();
}, sessionTimeout);
}
});
return stream;
}
}
session.once('close', removeSession);
let entries = this.sessions[authority], entry = [
session,
@@ -112,12 +152,6 @@ class Http2Sessions {
return session;
}
static setTimeout(session, timeout = 1000) {
session && session.setTimeout(timeout, () => {
session.close();
});
}
}
const http2Sessions = new Http2Sessions();
@@ -284,10 +318,12 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
let rejected = false;
let req;
httpVersion = Number(httpVersion);
httpVersion = +httpVersion;
if (Number.isNaN(httpVersion)) {
throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`);
}
if (httpVersion !== 1 && httpVersion !== 2) {
throw TypeError(`Unsupported protocol version '${httpVersion}'`);
}
+16 -11
View File
@@ -2423,7 +2423,7 @@ describe('supports http with nodejs', function () {
});
it('should support request cancellation', async function (){
if (typeof AbortSignal !== 'function') {
if (typeof AbortSignal !== 'function' || !AbortSignal.timeout) {
this.skip();
}
@@ -2525,13 +2525,17 @@ describe('supports http with nodejs', function () {
it("should use different sessions for different authorities", async() => {
server = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000);
setTimeout(() => {
res.end('OK');
}, 2000);
}, {
useHTTP2: true
});
server2 = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000);
setTimeout(() => {
res.end('OK');
}, 2000);
}, {
useHTTP2: true,
port: SERVER_PORT2
@@ -2559,7 +2563,9 @@ describe('supports http with nodejs', function () {
it("should use different sessions for requests with different http2Options set", async() => {
server = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000);
setTimeout(() => {
res.end('OK')
}, 1000);
}, {
useHTTP2: true
});
@@ -2639,6 +2645,8 @@ describe('supports http with nodejs', function () {
}
});
const data1 = await getStream(response1.data);
await setTimeoutAsync(5000);
const response2 = await http2Axios.get(LOCAL_SERVER_URL, {
@@ -2648,15 +2656,12 @@ describe('supports http with nodejs', function () {
}
});
const data2 = await getStream(response2.data);
assert.notStrictEqual(response1.data.session, response2.data.session);
assert.deepStrictEqual(
await Promise.all([
getStream(response1.data),
getStream(response2.data)
]),
['OK', 'OK']
);
assert.strictEqual(data1, 'OK');
assert.strictEqual(data2, 'OK');
});
});
});