-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Node.js middleware: 'Possible EventEmitter memory leak' warning when serving static pages / files #15730
Description
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.
astro/packages/integrations/node/src/serve-app.ts
Lines 78 to 114 in cc9ee02
| 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.