Skip to content
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

headersTimeout on Agent appears to be broken since 6.20.0 #3798

Open
Stono opened this issue Nov 2, 2024 · 3 comments
Open

headersTimeout on Agent appears to be broken since 6.20.0 #3798

Stono opened this issue Nov 2, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@Stono
Copy link

Stono commented Nov 2, 2024

Bug Description

Using fetch with an undici agent with configuration such as new Agent({ headersTimeout: 1}) on 6.19.8 would correctly throw a UND_ERR_HEADERS_TIMEOUT error whereas on 6.20.0 and 6.20.1 the request just hangs.

This was caught by some tests we have around our http client that ensures timeouts work, and they started failing after the undici bump.

Reproducible By

As above really.

Expected Behavior

UND_ERR_HEADERS_TIMEOUT to throw on a headers timeout.

Logs & Screenshots

Environment

node v22.5.1
undici v6.20.x

Additional context

@Stono Stono added the bug Something isn't working label Nov 2, 2024
@ronag
Copy link
Member

ronag commented Nov 2, 2024

Du you have a full repro?

@Stono
Copy link
Author

Stono commented Nov 2, 2024

import express from 'express'
import should from 'should'
import { Agent, fetch } from 'undici'

describe.only('undici', function () {
  it('headers timeouts', async () => {
    const app = express()
    app.get('/delayed', (req, res) => {
      setTimeout(() => {
        res.json({ ok: true })
      }, 5_000)
    })
    app.listen(3002)

    const fetchPromise = async () => {
      const dispatcher = new Agent({
        connectTimeout: 5000,
        bodyTimeout: 300000,
        headersTimeout: 1
      })

      const result = await fetch('http://127.0.0.1:3002/delayed', {
        redirect: 'manual',
        headers: { accept: 'application/json' },
        method: 'GET',
        keepalive: true,
        dispatcher
      })
      return result.status
    }
    const timeoutPromise = new Promise((resolve) =>
      setTimeout(() => resolve('undici broken?'), 500)
    )

    const result = await Promise.race([fetchPromise(), timeoutPromise])
    should(result).not.eql('undici broken?')
  })
})

On 6.19.8:
Screenshot 2024-11-02 at 19 22 36

On 6.20.1:
Screenshot 2024-11-02 at 19 23 16

@metcoder95
Copy link
Member

metcoder95 commented Nov 5, 2024

The behaviour is indeed changed, I couldn't reproduce it your code in isolation; but I was able to figure out that this might has to be something with the work done for the fast timers.

As, if e.g. headersTimeout is set to 1 and the server takes just a few ms to respond with the headers, undici still treats that response as valid; the difference should be notable to the timeout take effect.

For instance, my guess is that maybe mocha does something with timers that messes the fast timers implementation and causes undici to drift some the expected behaviour.

import { createServer } from 'node:http'
import { once } from 'node:events'
import { Agent, fetch } from './index.js' // main & v6.x behaves the same

const app = createServer((req, res) => {
  setTimeout(() => {
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.end('hello world')
  }, 10) // Change to 500 or 1000 to cause a UND_ERR_HEADERS_TIMEOUT
})

app.listen(0)

await once(app, 'listening')

const dispatcher = new Agent({
  connectTimeout: 1,
  bodyTimeout: 1,
  headersTimeout: 1
})

const result = await fetch(`http://127.0.0.1:${app.address().port}/delayed`, {
  redirect: 'manual',
  headers: { accept: 'text/plain' },
  method: 'GET',
  keepalive: true,
  dispatcher
})

console.log(result.status, await result?.text())

Between v6.19 and v6.20 the fast timers were changed. v6.19.8...v6.20.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants