How I Fixed A Memory Leak In Expose.sh, My NodeJS App
Tags: javascript, programming, tutorial, node
Published: 2021-12-08 21:34:00

#### Introduction
If you run Node in production, sooner or later you'll come across a common bug known as a memory leak.

This was the case with my current side project, [expose](https://expose.sh?utm_source=dev.to). I wrote a popular article [Six Ways To Drastically Boost Your Productivity As A Node Developer](https://dev.to/robbiecahill/six-ways-to-drastically-boost-your-productivity-as-a-node-developer-1mjd) which mentioned it, then suddenly the server was overloaded.

During the period of high load, I could see that a memory leak was forming.

#### What is `expose`?
`expose` is a command line app that makes its simple to give a public URL to any web based app you have running locally on your machine. So if you have a local API runnig at `http://localhost:8000` and you then run `expose 8000`, it will generate a public URL that you can then share.

It works by creating a websocket connection between the client and the expose service, which listens with websocket, http and https. When requests come in to the public URL, they are routed through the websocket connection to the client and then the client hits your server locally.

This has various uses like demoing early work without needing to deploy code anywhere and debugging webhook integrations.

You can install it for Linux, Mac and Windows <a href="https://expose.sh/install?utm_source=dev.to">here</a>

#### The leak
In the `expose` server, I have a Singleton class called `Proxy`, which is in TypeScript, the superset of JavaScript with type safety.

This class manages all client connections to the expose service. Anytime you run `expose` to get a public url for your project running on localhost, a Websocket connection is created between the client and the service. Those connections are stored in `Proxy.connections`.

This is a trimmed down version of the `Proxy` class. The real version has extra logic, such as finder methods to help route requests to the right client websocket so that you see your site, not someone elses when you hit the public URL.

```typescript
import Connection from "./connection";
import HostipWebSocket from "./websocket/host-ip-websocket";

export default class Proxy {
    private static instance : Proxy;

    connections : Array<Connection> = [];

    addConnection(hostname: string, websocket: HostipWebSocket, clientId: string): void {
        const connection : Connection = {
            hostname,
            clientId,
            websocket
        };

        this.connections.push(connection);
    }
....
More methods to find the right connections, avoid    duplicates etc...
....

    listConnections() {
        return this.connections;
    }

    public static getInstance(): Proxy {
        if (!Proxy.instance) {
            Proxy.instance = new Proxy();
        }

        return Proxy.instance;
    }
}
```

Every time a client connects, `addConnection()` is called. The problem here is that when they disconnect, the Websocket connection stays alive and their entry in `Proxy.connections` stays there.

So as more clients connect, the `Proxy.connections` array gets bigger and bigger. This is a classic memory leak.

Before the article, this wasn't such an issue as few people were connecting to and using the service. After the article, the server had to deal with more connections, then ran out of memory. I ended up upgrading the instance to a bigger one, which handled the load even with the memory leak.

#### Fixing the leak
Once the problem was apparent, I went about fixing the leak.

In `addConnection()`, I started tagging websocket connections with the client id of the connecting client.

```typescript
    addConnection(hostname: string, websocket: HostipWebSocket, clientId: string): void {
// Tag the connection so it can be found and destroyed later
// when the client disconnects
        websocket.exposeShClientId = clientId;

        const connection : Connection = {
            hostname,
            clientId,
            websocket
        };

        this.connections.push(connection);
    }

```

I also added a `deleteConnection()` method to the `Proxy` class to handle the actual deletion of connections, so they could then be cleaned up by the garbage collector.

```typescript
    deleteConnection(clientId: string) {
        for (let i = 0; i < this.connections.length; i++) {
            const connection = this.connections[i];

            if (connection.clientId === clientId) {
                this.connections.splice(i, 1);
            }
        }
    }
```
I then added a hook on the websocket connections so that when they close, their associated `Connection` is deleted
```typescript
    websocket.on('close', (code: number, reason: string) => {
        websocket.terminate();

        const proxy = Proxy.getInstance();

        proxy.deleteConnection(websocket.exposeShClientId);
    });
```

Once this was done, connections in `Proxy.connections` were cleaned up as clients disconnected. No more endlessly growing array and no more memory leak.

#### Conclusion
Memory leaks are common in Node as servers often run as a single process. Anything left over from each connection that grows will cause a memory leak.

So keep an eye out for them next time you see your instance running out of memory.

Tip: If you want to basically almost eliminate memory leaks, consider trying out PHP, my other favorite language. Each request is a separate process so it is basically stateless. It wouldn't work for `expose`, because the server needs to maintain state with the connections.

To introduce a memory leak into a PHP application would take alot of effort - not just a bug in the code but also very bad misconfiguration. This is one of the better parts of PHP as you are protected from these kinds of bugs.