Consider the following piece of code:

function isALive() {
    console.log('isAlive is called!');
    return new Promise<boolean>((resolve, _reject) => {
        getStatus((error, status) => {
            console.log('The status callback was called with:', error, status);
            if (error) {
                resolve(false);
            } else if (status.match(/Connected/)) {
                resolve(true);
            } else {
                resolve(false);
            }
        });
    });
}
A piece of code to check if a connection is still alive

The isAlive the function is called to check if the connection is still usable when reusing the same connection. If the connection is not alive, a new connection is created later on in the code and used instead.

I wrote that piece of code and wanted to test it by setting a breakpoint before the getStatus function is called, killing the connection from the server-side and checking if the getStatus callback returns an error.

To my surprise, the callback wasn't even called, it would simply exit, not only from the isAlive function but from Node itself, and all that without any error, even the process exit code was 0 which indicates that the process finished successfully.

After diving in the getStatus function, I didn't see anything suspicious, it would basically write something in the open socket (send something over the connection) to see if it comes back with a response.

That seemed fairly simple and innocent, how could that go wrong? It simply couldn't. Plus I also put breakpoints in that function and verified that socket.write is called successfully.

Well, what happens when I end the connection from the server-side after the getStatus function is called?

Turns out the library I was using doesn't have a check for when the connection ends, therefore when the connection gets terminated from the server-side, the socket gets destroyed without the library knowing anything about that.

Well that's a very unfortunate event for my app, as the only thing that stands between my app and termination is that open connection, when it ends and the file-descriptor is destroyed, Node doesn't have anything else to wait for, therefore believes the app is done and exits successfully.

To prevent that from happening, I had to create a PR on node-ftp for a fix, but I was in hurry and didn't want to wait for a few months until that fix is released, so I went ahead and created a workaround.

The workaround is simple and not so efficient, but works:

function isALive() {
    console.log('isAlive is called!');
    return new Promise<boolean>((resolve, _reject) => {
    	const timeout = setTimeout(() => resolve(false), connectionTimeout);
        getStatus((error, status) => {
        	clearTimeout(timeout);
            console.log('The status callback was called with:', error, status);
...
}
isAlive with a timeout

By creating a timeout, now even after the socket is closed, Node is still waiting on something, so it won't exit. Still, because the connection end event is not handled by the FTP library, it wouldn't notice until my timeout has triggered. So instead of immediately knowing the connection to the server is terminated, it still has to wait a few seconds, which is unfortunate but works for the purpose I was using the library for.