Skip to content

v18.18.2 -> v18.19.0 regression: "fetch failed" when HTTP server stopped and started on same port #51165

Open
@mitchhentgesspotify

Description

@mitchhentgesspotify

Version

v18.19.0

Platform

Darwin G34X7RD99C 23.2.0 Darwin Kernel Version 23.2.0: Wed Nov 15 21:53:18 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6000 arm64

Subsystem

fetch

What steps will reproduce the bug?

Run the following script:

const http = require('node:http');

const runServerAndFetch = async () => {
  const sockets = [];
  const server = http.createServer((req, res) => {
    res.end();
  });

  server.on('request', req => {
    sockets.push(req.socket);
  });
  await new Promise(res => {
    server.listen(10000, res);
  });

  await fetch(`http://localhost:10000`);

  await new Promise(res => {
    setTimeout(() => { // Interestingly, there's no error if this `setTimeout` is removed with its contents inlined.
      sockets.forEach(socket => socket.end());
      server.close(res);
    }, 0);
  });
};

const main = async () => {
  await runServerAndFetch();
  await runServerAndFetch();
};

main();

How often does it reproduce? Is there a required condition?

Consistent reproduce

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

Ideally, both times the server is started and a fetch(...) is made, it should receive a 200 happily, and the script should exit without any output.

What do you see instead?

node:internal/deps/undici/undici:11730
    Error.captureStackTrace(err, this);
          ^

TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11730:11)
    at async runServerAndFetch (/Users/mhentges/dev/backstage-quickstart/node.js:16:3)
    at async main (/Users/mhentges/dev/backstage-quickstart/node.js:28:3) {
  cause: SocketError: other side closed
      at Socket.onSocketEnd (node:internal/deps/undici/undici:8280:26)
      at Socket.emit (node:events:529:35)
      at endReadableNT (node:internal/streams/readable:1400:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: '::1',
      localPort: 56324,
      remoteAddress: undefined,
      remotePort: undefined,
      remoteFamily: undefined,
      timeout: undefined,
      bytesWritten: 340,
      bytesRead: 122
    }
  }
}

Node.js v18.19.0

Additional information

The reproduce example I've used is simplified and is not conventionally handling request lifecycles properly.
Importantly, this bug can be reproduced using the library the example reproduce is based on:

const http = require('node:http');
const stoppable = require('stoppable');

const runServerAndFetch = async () => {
  const server = http.createServer((req, res) => {
    res.end();
  });
  const stoppableServer = stoppable(server);

  await new Promise(res => {
    server.listen(10000, res);
  });

  await fetch(`http://localhost:10000`);

  await new Promise(res => {
    stoppableServer.stop(res);
  });
};

const main = async () => {
  await runServerAndFetch();
  await runServerAndFetch();
};

main();

Metadata

Metadata

Assignees

No one assigned

    Labels

    fetchIssues and PRs related to the Fetch APIhttpIssues or PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions