Description
Problem Statement
My web server maintains a pool of long-lived resources. These resources are created on application startup and used in some of the requests. However, if we detect that a resource is unhealthy during a request, we'll remove it and create a new one.
These resources each maintain a long-lived socket. As best as I can tell, Sentry's hub/scope mechanisms maintain a reference to the incoming HTTP request and associate it with this socket. If the request object is very large (e.g. it contains a lot of data on res.locals
), this data will be retained effectively forever, even once the request finishes. This manifests as a memory leak.
I'm not entirely sure of how Sentry's internals work, but this appears to be related to Sentry's usage of Node domains to track request context across async operations. If I take a heap snapshot at runtime, I can see that the response is ultimately retained in a domain's members, and if I disable Sentry completely, the response is no longer retained in any domain.
I've created a small repo to illustrate this issue: https://github.com/nwalters512/sentry-domain-memory-leak. Follow these steps to see the issue:
- Clone the repo and run
yarn install
- Run
docker pull ubuntu
- Run
node --inspect index.js
- Attach a debugger and look at the memory consumption; it should be ~10MB
- Make a request to
http://localhost:80/
in your browser. - Look at the memory consumption in the debugger; it should be about 300MB and should stay that way even after manually triggering a garbage collection
- Stop the server, remove all Sentry-related code from
index.js
, and repeat the above steps. Note that memory consumption should fall back to ~10MB either immediately or after triggering a garbage collection.
I'm writing this as a feature request because I have a relatively clear idea of what I want from Sentry: the ability to run a piece of code completely outside of Sentry's domains/hubs/scopes. AFAICT from the docs, there's not currently a way to do this.
Solution Brainstorm
From an API standpoint, something like this would be nice:
await Sentry.disableScope(async () => {
await makeLongLivedResource();
});
Not knowing much about Sentry's internals, I can't really guess as to exactly how this could be implemented.