-
-
Notifications
You must be signed in to change notification settings - Fork 87
Description
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
.