Skip to content

Node.js middleware: 'Possible EventEmitter memory leak' warning when serving static pages / files #15730

@fa-sharp

Description

@fa-sharp

Astro Info

Astro                    v6.0.0-beta.17
Node                     v22.21.1
System                   macOS (arm64)
Package Manager          pnpm
Output                   static
Adapter                  @astrojs/node
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

When using the latest (beta) version of Astro and using the Node.js adapter as middleware, I'm seeing an EventEmitter memory leak warning from Node.js when serving static pages and files.

For a minimal example, I setup a Fastify server as shown in the docs, and created a static page. Simply refreshing the static page a couple times triggers the memory leak warning.

The full log is:

(node:12345) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 
11 close listeners added to [Socket]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit

I believe the problem stems from the following code. The createRequest function creates some socket listeners which are added to the request object. Later, the writeResponse function cleans up those listeners. However, if Astro doesn't actually handle/respond to the request, it just calls the next function (line 106-7) without calling the writeResponse function, and so the socket listeners aren't cleaned up.

return async (req, res, next, locals) => {
let request: Request;
try {
request = createRequest(req, {
allowedDomains: app.getAllowedDomains?.() ?? [],
});
} catch (err) {
logger.error(`Could not render ${req.url}`);
console.error(err);
res.statusCode = 500;
res.end('Internal Server Error');
return;
}
// Redirects are considered prerendered routes in static mode, but we want to
// handle them dynamically, so prerendered routes are included here.
const routeData = app.match(request, true);
// But we still want to skip prerendered pages.
if (routeData && !(routeData.type === 'page' && routeData.prerender)) {
const response = await als.run(request.url, () =>
app.render(request, {
addCookieHeader: true,
locals,
routeData,
prerenderedErrorPageFetch,
}),
);
await writeResponse(response, res);
} else if (next) {
return next();
} else {
const response = await app.render(request, {
addCookieHeader: true,
prerenderedErrorPageFetch,
});
await writeResponse(response, res);
}

If I have some time, I can try to take a deeper look and submit a PR.

What's the expected result?

The middleware should properly cleanup the socket listeners, even if it is just passing the request to the next function (e.g. for a static file/page).

Link to Minimal Reproducible Example

https://github.com/fa-sharp/astro-middleware-leak

Participation

  • I am willing to submit a pull request for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    - P4: importantViolate documented behavior or significantly impacts performance (priority)pkg: astroRelated to the core `astro` package (scope)pkg: nodeRelated to Node adapter (scope)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions