nodejs / nodejs/undici

`fetch` may try to use a closed connection

Open
#3,492 11 comments 22 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug fetch
Dominant language
JavaScript
Stars
7.7k
Forks
880
Avg merge
2d 16h
Merged PRs (30d)
68

Description

Version

v22.6.0

Platform
Darwin MacBook-Pro-6.local 23.5.0 Darwin Kernel Version 23.5.0: Wed May  1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000 arm64
Subsystem

http

What steps will reproduce the bug?
// fetch-test.js
const {createServer} = require('node:http');

const port = 8080;
const url = 'http://localhost:' + port;

const server = createServer((req, res) => res.end()).listen(port, async () => {
  await fetch(url);
  server.closeIdleConnections();

  setImmediate(async () => {
    await fetch(url); // Throws TypeError with cause UND_ERR_SOCKET or ECONNRESET
    server.close();
  });
});
How often does it reproduce? Is there a required condition?

Reproduces consistently for me but the error cause varies roughly evenly between ECONNRESET and UND_ERR_SOCKET (details below)

What is the expected behavior? Why is that the expected behavior?

fetch creates a new connection if there are none open, and the request succeeds.

What do you see instead?
node fetch-test.js
node:internal/deps/undici/undici:13178
      Error.captureStackTrace(err);
            ^

TypeError: fetch failed
    at node:internal/deps/undici/undici:13178:13
    at async Immediate.<anonymous> (/Users/robhogan/workspace/fetch-test.js:11:5) {
  [cause]: SocketError: other side closed
      at Socket.<anonymous> (node:internal/deps/undici/undici:6020:28)
      at Socket.emit (node:events:532:35)
      at endReadableNT (node:internal/streams/readable:1696:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: '::1',
      localPort: 57996,
      remoteAddress: undefined,
      remotePort: undefined,
      remoteFamily: undefined,
      timeout: undefined,
      bytesWritten: 338,
      bytesRead: 122
    }
  }
}

Node.js v22.6.0

OR

node fetch-test.js
node:internal/deps/undici/undici:13178
      Error.captureStackTrace(err);
            ^

TypeError: fetch failed
    at node:internal/deps/undici/undici:13178:13
    at async Immediate.<anonymous> (/Users/robhogan/workspace/fetch-test.js:11:5) {
  [cause]: Error: read ECONNRESET
      at TCP.onStreamRead (node:internal/stream_base_commons:218:20) {
    errno: -54,
    code: 'ECONNRESET',
    syscall: 'read'
  }
}

Node.js v22.6.0
Additional information

This seems to be quite sensitive to timing/the event loop in a way I haven't pinned down.

  • The setImmediate (or setTimeout(cb, 0)) is required to reproduce the issue.
  • Adding another setImmediate before the second fetch makes it succeed.
  • Adding {headers:{'Connection': 'close'}} to the first request succeeds.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the reproduction in fetch-test.js on the reported Node.js version, then inspect the HTTP and internal Undici paths shown in the stack trace. Compare connection handling around server.closeIdleConnections(), setImmediate(), and the second fetch; done means the second request succeeds without ECONNRESET or UND_ERR_SOCKET.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.