-
Notifications
You must be signed in to change notification settings - Fork 539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Requests does not timeout when bodyTimeout & headersTimeout have values lower than 1 second #1950
Comments
This is expected. The timeout accuracy is ~1s. So if you put a timeout of 100ms and the request finishes in 300 ms then it's still within the expected range. |
I'm not sure I understand, sorry about that 😢 If we expect a HTTP request to fail with bodyTimeout of 2 seconds (the server will respond in 3 seconds), it's natural to believe that requests with bodyTimeout of 500 ms will fail to. If it's FAD, can you point me somewhere to the docs? 🙏 |
I'll try to explain it again: We have a server that will respond in 3 seconds
|
Ah, that doesn't sound right. Can you provide a minimal reproducible sample? |
Sorry about copy paste 😞 The value of timeout is 100ms and the server will respond in 3 seconds. If you want another code sample, please let me know 🙏 test('agent should timeout request (lower value timeouts)', t => {
t.plan(4)
const timeout = 100
const wanted = 'payload'
const server = http.createServer((req, res) => {
t.equal('/', req.url)
t.equal('GET', req.method)
t.equal(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
setTimeout(() => {
res.end(wanted)
}, 3 * 1000)
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const dispatcher = new Agent({ bodyTimeout: timeout, headersTimeout: timeout })
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(({ statusCode, headers, body }) => {
t.fail('Request should timeout')
})
.catch(err => {
t.type(err, errors.HeadersTimeoutError)
})
})
}) |
This passes for me on main: 'use strict'
const { test } = require('tap')
const { Agent, request, errors } = require('..')
const { createServer } = require('http')
test('agent should timeout request (lower value timeouts)', t => {
t.plan(4)
const timeout = 100
const wanted = 'payload'
const server = createServer((req, res) => {
t.equal('/', req.url)
t.equal('GET', req.method)
t.equal(`localhost:${server.address().port}`, req.headers.host)
res.setHeader('Content-Type', 'text/plain')
setTimeout(() => {
res.end(wanted)
}, 3 * 1000)
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const dispatcher = new Agent({ bodyTimeout: timeout, headersTimeout: timeout })
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(({ statusCode, headers, body }) => {
t.fail('Request should timeout')
})
.catch(err => {
t.type(err, errors.HeadersTimeoutError)
})
})
}) undici$ npx tap test/request-timeout3.js
PASS test/request-timeout3.js 4 OK 3s
🌈 SUMMARY RESULTS 🌈
Suites: 1 passed, 1 of 1 completed
Asserts: 4 passed, of 4
Time: 3s
undici$ |
You example is working! Can you try with 500ms server response timeout? 🙏 Sorry about that 😢 I'm missing something and I'm wasting your time for nothing?
|
With 500 ms it's within the margin of timeout accuracy. That's expected. |
The same result with 950ms (or values lower than this) - request will succeed I really think it is something related with fast timers. I really appreciate your time and your help! |
What is FAD? |
It is related to fast timers and it is expected behavior... |
FAD=functioning as designed Let me give a more simpler example:
const { test } = require('tap')
const { Agent, request, errors } = require('..')
const http = require('http')
test('agent timeout request (lower value timeouts)', t => {
t.plan(1)
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
setTimeout(() => {res.end('OK')}, 1500)
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const dispatcher = new Agent({ headersTimeout: 500 })
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(() => t.fail('Request should timeout'))
.catch((err) => t.type(err, errors.HeadersTimeoutError))
})
})
Request timed out and it's ok! Everything is working as expected The same example, but now, the server is responding in 750ms:
const { test } = require('tap')
const { Agent, request, errors } = require('..')
const http = require('http')
test('agent timeout request (lower value timeouts)', t => {
t.plan(1)
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
setTimeout(() => {res.end('OK')}, 750)
})
t.teardown(server.close.bind(server))
server.listen(0, () => {
const dispatcher = new Agent({ headersTimeout: 500 })
request(`http://localhost:${server.address().port}`, { dispatcher })
.then(() => t.fail('Request should timeout'))
.catch((err) => t.type(err, errors.HeadersTimeoutError))
})
})
Request timeout ignored! This is clearly a bug Starting with version 5.18.0, a new feature - fast timers - is introduced! But beside semantic versioning, this is an strange behaviour and it's not transparent at all. 🆗 v5.17.1 - 2 test passed
@ronag, @mcollina (my apologies for summoning like this - like a said in the begging, this is used in @fastify/reply-from and it's hard to have a workaround), please consider a fix for this because we use these settings in testing environments and fast services 🙏 I'm happy to contribute with a PR if you show me the way or some guidence 👍 |
This is functioning as expected|designed, and I don't understand your use case. A timeout shorter than 1 second is not very meaningful in practical scenarios. @mcollina @KhafraDev do you have any opinions? |
I agree that this is not a very common scenario 😢 |
What is the production situation you want to handle? I'm lost. |
Proxy servers with upstreams that should respond as fast as possible, under 500ms. Also, in our CI/CD pipelines we run tests and we configure services with the lowest possible values for timeouts. |
I understand that fast timers brings some performance, but these features should not introduce breaking changes. If it's something you want to stick with, please consider to add some docs around it to be more transparent 🙏 Either way, I thank you all for your time and patience 💯 |
Thanks, the use case is helpful: now I understand the problem. @ronag I think we might want to put the new fast timers behind an option, or possibly have a way to handle this situation without a regression |
I think the easiest is to not use fast timers if the timeout is set to something less than 1 second. |
Thank you! Please let me know if there is anything I can help with 🙏 |
We have an HTTP proxy were we are using fastify and @fastify/reply-from.
In order the fix some vulnerabilities for undici, we updated to the latest version and had some failed tests.
The issue is that some requests does not timeout when bodyTimeout & headersTimeout have values lower than 1 second.
We identified that the issue is related to undici@5.18.0, when faster timers where introduced (especially 1 second value used fast timer timeout - https://github.com/nodejs/undici/blob/main/lib/timers.js#L44).
In order to test this, we added a new test in https://github.com/nodejs/undici/blob/main/lib/agent.js.
We run tests using Node 18.14.1.
This test will pass because the timeout is 1000 ms.
If we change the timeout to 100 ms, this test will fail.
If we change https://github.com/nodejs/undici/blob/main/lib/timers.js#L44 timeout value to 100ms, the test will pass.
I'm not sure what the correct fix should be and this is the reason why I've created an issue and not a PR.
I hope it's more clear to you and a fix is easy to implement!
Thank you in advance 🙏
The text was updated successfully, but these errors were encountered: