2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-20 20:00:40 +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); 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; let entries = authoritySessions, len = entries.length, i = len;
while (i--) { 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 = [ let entries = this.sessions[authority], entry = [
session, session,
@@ -112,12 +152,6 @@ class Http2Sessions {
return session; return session;
} }
static setTimeout(session, timeout = 1000) {
session && session.setTimeout(timeout, () => {
session.close();
});
}
} }
const http2Sessions = new Http2Sessions(); const http2Sessions = new Http2Sessions();
@@ -284,10 +318,12 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
let rejected = false; let rejected = false;
let req; let req;
httpVersion = Number(httpVersion); httpVersion = +httpVersion;
if (Number.isNaN(httpVersion)) { if (Number.isNaN(httpVersion)) {
throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`); throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`);
} }
if (httpVersion !== 1 && httpVersion !== 2) { if (httpVersion !== 1 && httpVersion !== 2) {
throw TypeError(`Unsupported protocol version '${httpVersion}'`); 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 (){ it('should support request cancellation', async function (){
if (typeof AbortSignal !== 'function') { if (typeof AbortSignal !== 'function' || !AbortSignal.timeout) {
this.skip(); this.skip();
} }
@@ -2525,13 +2525,17 @@ describe('supports http with nodejs', function () {
it("should use different sessions for different authorities", async() => { it("should use different sessions for different authorities", async() => {
server = await startHTTPServer((req, res) => { server = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000); setTimeout(() => {
res.end('OK');
}, 2000);
}, { }, {
useHTTP2: true useHTTP2: true
}); });
server2 = await startHTTPServer((req, res) => { server2 = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000); setTimeout(() => {
res.end('OK');
}, 2000);
}, { }, {
useHTTP2: true, useHTTP2: true,
port: SERVER_PORT2 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() => { it("should use different sessions for requests with different http2Options set", async() => {
server = await startHTTPServer((req, res) => { server = await startHTTPServer((req, res) => {
setTimeout(() => res.end('OK'), 1000); setTimeout(() => {
res.end('OK')
}, 1000);
}, { }, {
useHTTP2: true useHTTP2: true
}); });
@@ -2639,6 +2645,8 @@ describe('supports http with nodejs', function () {
} }
}); });
const data1 = await getStream(response1.data);
await setTimeoutAsync(5000); await setTimeoutAsync(5000);
const response2 = await http2Axios.get(LOCAL_SERVER_URL, { 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.notStrictEqual(response1.data.session, response2.data.session);
assert.deepStrictEqual( assert.strictEqual(data1, 'OK');
await Promise.all([ assert.strictEqual(data2, 'OK');
getStream(response1.data),
getStream(response2.data)
]),
['OK', 'OK']
);
}); });
}); });
}); });