[fetch-utils] use default agent with a 1s timeout and 3 attempts in connect phase (#35406)

Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com>
GitOrigin-RevId: cb1e854d61ce623fa557ae003dc7159b096e1f86
This commit is contained in:
Jakob Ackermann
2026-09-17 11:24:37 +00:00
committed by Copybot
co-authored by Andrew Rumble
parent 89f12e6b1f
commit 9519dc5e66
3 changed files with 146 additions and 35 deletions
@@ -1,5 +1,6 @@
import { expect } from 'chai'
import dns from 'node:dns'
import http from 'node:http'
import { once } from 'node:events'
import events from 'node:events'
import fs from 'node:fs'
@@ -364,6 +365,66 @@ describe('fetch-utils', function () {
'request to http://10.255.255.255/ failed, reason: connect timeout'
)
})
it('retries after a delay', { timeout: 5_000 }, async function () {
// The test should finish after 1.5s.
// t 0ms: first connect fails with ECONNREFUSED
// t 500ms: listen on port
// t1000ms: retry to connect again, works
// t1500ms: respond to request
const agent = new CustomHttpAgent({
connectTimeout: 100,
connectRetryInterval: 1_000,
})
const server = http.createServer((req, res) => {
setTimeout(() => {
res.write('hello')
res.end()
server.close()
}, 500)
})
// Grab a dynamic port, then close again so that the first connect
// attempt is refused before the server starts listening for real.
await once(server.listen(0), 'listening')
const { port } = server.address()
await new Promise(resolve => server.close(resolve))
const t0 = performance.now()
setTimeout(() => {
server.listen(port)
}, 500)
const body = await fetchString(`http://127.0.0.1:${port}`, { agent })
expect(body).to.equal('hello')
const t1 = performance.now()
expect(t1 - t0).to.be.at.least(1_500)
})
it('does not open a stray connection when the socket errors after connect', async function () {
const agent = new CustomHttpAgent({
connectTimeout: 100,
connectRetryInterval: 10,
})
const connections = []
const server = http.createServer(req => {
// Reset the established connection before responding. Doing this in
// the request handler guarantees the client finished the connect
// phase, so the ECONNRESET arrives after 'connect' has fired.
req.socket.resetAndDestroy()
})
server.on('connection', socket => connections.push(socket))
await once(server.listen(0), 'listening')
const { port } = server.address()
try {
await expect(
fetchString(`http://127.0.0.1:${port}/`, { agent })
).to.be.rejectedWith(FetchError)
// Leave time for a buggy connect retry to open a stray connection.
await wait(200)
expect(connections).to.have.length(1)
} finally {
for (const socket of connections) socket.destroy()
server.close()
}
})
})
describe('CustomHttpsAgent', function () {