nodejs / undici

An HTTP/1.1 client, written from scratch for Node.js
https://nodejs.github.io/undici
MIT License
6.18k stars 538 forks source link

Fetch times out without AbortController triggered #1329

Open vinczedani opened 2 years ago

vinczedani commented 2 years ago

Bug Description

Hello, I'm trying to create a custom AWS lambda container runtime as a small hobby project. And since fetch is now part of the core NodeJS I wanted to give it a try. To get the lambda invocations, I need a request with a really long timeout, so I did set up an AbortController that will never be triggered:

function getNextInvocation() {
  return fetch(`http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next`, {
    signal: new AbortController().signal,
  });
}

When I uploaded my code to aws (as a container image). And tested it, the first execution was fine, but after a few minutes I retried and got:

TypeError: fetch failed
at Object.processResponse (node:internal/deps/undici/undici:7156:34)
at Fetch.fetchFinale (node:internal/deps/undici/undici:7486:21)
at Fetch.mainFetch (node:internal/deps/undici/undici:7380:21)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
cause: HeadersTimeoutError: Headers Timeout Error
at Timeout.onParserTimeout [as _onTimeout] (node:internal/deps/undici/undici:2109:33)
at listOnTimeout (node:internal/timers:561:11)
at processTimers (node:internal/timers:502:7) {
code: 'UND_ERR_HEADERS_TIMEOUT'
}
}

Reproducible By

Full snippet of my test code:

const {
  AWS_LAMBDA_RUNTIME_API
} = process.env;

function getNextInvocation() {
  return fetch(`http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next`, {
    signal: new AbortController().signal,
  });
}

function postResults(result, reqId) {
  return fetch(`http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/${reqId}/response`, {
    body: typeof result === 'object' ? JSON.stringify(result) : result,
    method: 'post'
  });
}

async function runIteration() {
  const invocation = await getNextInvocation();
  await postResults('OK', invocation.headers.get('Lambda-Runtime-Aws-Request-Id'));
}

async function loop() {
  while (true) {
    await runIteration();
  }
}

loop();

Expected Behavior

If the abort controller is never triggered, I'd expect to never receive a timeout error.

Logs & Screenshots

Environment

I uploaded the above code to AWS ECR and ran it in lambda as container image. My dockerfile is

FROM node:17.8-alpine

COPY *.js .

CMD ["node", "--experimental-fetch", "index.js"]

Additional context

mcollina commented 2 years ago

This would be expected given the AWS lambda runtime. The process got suspended and you had a timeout for a response. I think you'll have to catch with this error and retry the call.

vinczedani commented 2 years ago

So you say it is not possible to just wait infinitely for a possible response? I might not remember correctly but I think when using the https library in Node I was able to pass in timeout infinity and never see similar problems. I think when AWS shots down my lambda process it totally kills it, when when running it again for the first time after some delay, it boots up a new container from the image. In that case the code (in theory) should work as for the first invocation (when it succeeds)

Try catch + retry should work, I just wanted to avoid it, if its possible to solve it with passing an option parameter

martinheidegger commented 2 years ago

Lambdas do have a limit on execution time: https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html#function-configuration-deployment-and-execution 900seconds.

thantos commented 1 year ago

Seeing the same issue, was not happening using node16 with node-fetch, and now a high % of my lambda extension fetch calls fail.