Skip to content

Hard crash under Node 24 when handling aborted requests #428

@gwicks

Description

@gwicks

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

5.4.0

Plugin version

12.2.0

Node.js version

24.4.1

Operating system

Linux

Operating system version (i.e. 20.04, 11.3, 10)

Alpine 3.21 (ARM64)

Description

After upgrading to Node 24.x, we started having erratic hard crashes in production, seemingly when connections to the upstream server aren't closing quite right.

 Node.js v24.4.1 
   at process.processTicksAndRejections (node:internal/process/task_queues:90:21) 
  at emitErrorCloseNT (node:internal/streams/destroy:129:3) 
   at emitErrorNT (node:internal/streams/destroy:170:8) 
Emitted 'error' event on BodyReadable instance at: 
 at wasm://wasm/0002f80e:wasm-function[20]:0x67b2 
  at wasm://wasm/0002f80e:wasm-function[10]:0x474 
at wasm_on_headers_complete (/usr/src/app/node_modules/undici/lib/dispatcher/client-h1.js:108:30) 
 at Parser.onHeadersComplete (/usr/src/app/node_modules/undici/lib/dispatcher/client-h1.js:514:27) 
at Request.onHeaders (/usr/src/app/node_modules/undici/lib/core/request.js:244:29) at RequestHandler.onHeaders (/usr/src/app/node_modules/undici/lib/api/api-request.js:140:14) 
at RequestHandler.runInAsyncScope (node:async_hooks:214:14) 
  at /usr/src/app/node_modules/@fastify/reply-from/lib/request.js:187:7 
 at /usr/src/app/node_modules/@fastify/reply-from/index.js:299:9 
 at /usr/src/app/node_modules/@fastify/reply-from/index.js:215:20 
TypeError: res.stream.close is not a function 
  throw er; // Unhandled 'error' event 
node:events:485 

Looking deeper at the code, it looks like Node 24 changes the structure of the stream API and removes the close method. Additionally, we are using the HTTP1 backend. The upstream server in my case is a kind of rickety Ruby on Rails legacy server, and it prone to periodically dropping requests/closing unexpectedly under certain conditions. Previously, this was handled just fine.

Here's some of my config for @fastify/http-proxy for reference that's feeding into reply-from, can't include all of it as it's somewhat sensitive.

      onResponse: async (
        request,
        reply,
        res: RawReplyDefaultExpression<RawServerBase>
      ) => {
        if (request.currentSubspan) {
          request.currentSubspan.setAttribute(
            "reply.elapsedTime",
            reply.elapsedTime
          );
          if (reply.statusCode >= 400) {
            request.totalRailsErrorRespsonseCounter.add(1, {
              status_code: reply.statusCode,
              error_type: "upstream_error",
            });
            request.currentSubspan
              .setStatus({ code: SpanStatusCode.ERROR })
              .end();
          } else {
            request.currentSubspan.setStatus({ code: SpanStatusCode.OK }).end();
          }
          request.totalRailsRequestCounter.add(1);
          request.currentSubspan = null;
        }
        if ((res as unknown as UndiciProxyResponse)?.stream) {
          reply.send((res as unknown as UndiciProxyResponse)?.stream);
        } else {
          reply.send(res);
        }
      },

Link to code that reproduces the bug

N/A, relies on requests being aborted under HTTP/1 and Node 24.x

Expected Behavior

It should gracefully handle an aborted connection without throwing an unhandled exception.

One possible fix for the offending code might be something like

  if (sourceHttp2 && typeof res.stream.close === 'function') {
    // HTTP/2 stream
    res.stream.close(NGHTTP2_CANCEL)
  } else if (typeof res.stream.destroy === 'function') {
    // HTTP/1.1 stream or other stream types
    res.stream.destroy()
  }

Inside requestImpl.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions