Closing TCP/UDP Sockets With Timeouts and Error Handling in Nodejs

Koray Göçmen
The Startup
Published in
3 min readOct 21, 2020

--

I’m not a big fan of the built-in `dgram` and `net` libraries in Nodejs, but it’s really easy to create sockets and write networking applications with Nodejs. One of the biggest issues I’m constantly seeing is the lack of cleanup functionalities when using the socket libraries in Nodejs.

This code is from our DNS npm package that we use in Violetnorth

You can find the Violetnorth — DNS npm package here: github.com/violetnorth/dns

So I’m going to talk about a quick way to clean up sockets with timeouts and overall error handling when dealing with sockets.

If you don’t implement some sort of timeout, you are going to run out of available sockets especially when using TCP sockets.

Sockets usually hang and there needs to be timeout handling to close the socket if it’s hanging, which is not super apparent with Nodejs.

Here is an example function that uses a TCP socket to do a DNS lookup.

// TCP socket with timeoutconst dns = require("dns");
const net = require("net");
const dnsPacket = require("dns-packet");

const resolveTCP = (packet, addr, port = 53, timeout) => {
return new Promise((resolve, reject) => {
const socket = new net.Socket();

const id = setTimeout(() => {
clearTimeout(id);
socket.destroy();
reject("timed out");
return;
}, parseInt(timeout));

socket.connect(parseInt(port), addr, () => {
socket.write(packet);
});

let message = Buffer.alloc(4096);
socket.on("data", data => {
message = Buffer.concat([message, data], message.length + data.length);
});

socket.on("drain", () => {
clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
socket.destroy();
resolve(dnsPacket.decode(message));
return;
});

socket.on("end", () => {
clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
socket.destroy();
resolve(dnsPacket.decode(message));
return;
});

socket.on("error", err => {
reject(err);
return;
});

socket.on("close", function() {
resolve(dnsPacket.decode(message));
return;
});
});
};

Another example function that uses a UDP socket to do a DNS lookup.

// UDP socket with timeoutconst dns = require("dns");
const dgram = require("dgram");
const dnsPacket = require("dns-packet");

const _resolveUDP = (packet, addr, port = 53, timeout) => {
return new Promise((resolve, reject) => {
const socket = dgram.createSocket("udp4");

const id = setTimeout(() => {
clearTimeout(id);
socket.close();
reject("timed out");
return;
}, parseInt(timeout));

socket.on("message", message => {
clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
socket.close();
resolve(dnsPacket.decode(message));
return;
});

socket.on("error", err => {
clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
socket.close();
reject(err);
return;
});

socket.send(packet, 0, packet.length, parseInt(port), addr, err => {
if (err) {
clearTimeout(id); // Clear the timeout if you are going to close the socket manually.
socket.close();
reject(err);
}
});
});
};

The idea is basically to create a named timeout function which will close the socket after the specified timeout milliseconds, but you have to clear the timeout function every time you close the socket manually (in the case of an error for example).

Otherwise, you will see unhandled errors due to trying to close an already closed socket.

Anyways, that’s pretty much it, error handling and timeouts with Nodejs sockets.

--

--

Koray Göçmen
The Startup

University of Toronto, Computer Engineering, architected and implemented reliable infrastructures and worked as the lead developer for multiple startups.