Is my closure in an expressjs middlware causing a memory leak?

41 views Asked by At

I have a middleware function that sets some utility clients on the request object. When the response finishes it destroys the connections.

export const setRequestUtils = (req: Request, res: Response, next: NextFunction) => {
  req.dynamoClient = new DynamoStorage();
  req.s3Client = new S3();

  res.on('close', () => {
    req.dynamoClient.destroy();
    req.s3Client.destroy();
  });
  next();
};

This is being hosted on an ECS cluster and I am noticing while the service is idle ECS will call the service. These calls seems to cause the memory usage on the cluster to rise

enter image description here

Is express or node keeping the req in the closure around and not garbage collecting it?

Edit:

The closure seems to be the problem. After I removed it the memory is consistently going back to its baseline after each ping from ECS

enter image description here

Is there a way to release the memory of the closure?

1

There are 1 answers

2
Nazrul Chowdhury On

In your middleware function you are attaching an event listener to the response object (res.on('close', ...)). This creates a closure that captures references to req, req.dynamoClient, and req.s3Client. These references could prevent garbage collection of these objects until the event listener is removed or the response object is destroyed. Since you are noticing memory issues, it is likely that these closures are accumulating over time, especially if ECS is making frequent calls to your service.
To mitigate this issue, you can remove the event listener once it's no longer needed. You can also try using res.once() instead of res.on() to ensure the event listener is only triggered once. This should prevent any unnecessary references from being held onto.

  res.once('close', () => {
    req.dynamoClient.destroy();
    req.s3Client.destroy();
    // remove event listeners to prevent memory leaks
    res.removeAllListeners('close');
  });