undici icon indicating copy to clipboard operation
undici copied to clipboard

`fetch` may try to use a closed connection

Open robhogan opened this issue 6 months ago • 6 comments

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.

robhogan avatar Aug 21 '24 13:08 robhogan