Course contentsShow
← Node.js
Lesson 65 of 1,9801. Node.js Foundations and Runtime InternalsFree lesson

Network Socket Lifecycle

Manage persistent network connections to prevent socket leaks.

In this lesson, you will learn how to manage the lifecycle of persistent network connections to ensure your application doesn't bleed memory through socket leaks.

Understanding the Socket Lifecycle

When you create a connection in Node.js—using modules like net or http—you are essentially asking the operating system to open a communication channel. This channel is a "socket." Because Node.js handles I/O asynchronously, these sockets don't just disappear when your code finishes executing; they stay open, consuming system resources, until they are explicitly closed or time out.

A socket follows a simple lifecycle:

  1. Creation: The socket is allocated and initialized.
  2. Activity: Data flows through the stream.
  3. Termination: The connection ends (either by your code, the client, or a network error).

The Danger of Leaks

A "socket leak" occurs when your code creates connections but fails to account for the termination phase. If you have a long-running process that opens sockets—perhaps to a database or a remote API—and you don't listen for 'close' or 'error' events, these objects remain in memory, and the underlying file descriptors remain held by the OS. Eventually, your process will run out of available file descriptors and crash.

Consider this basic pattern for keeping a socket in check:

const net = require('net');

const client = net.createConnection({ port: 8080 }, () => {
  console.log('Connected!');
});

// Always listen for end-of-life events
client.on('close', () => {
  console.log('Connection closed, resource released.');
});

client.on('error', (err) => {
  console.error('Socket encountered an error:', err);
  client.destroy(); // Ensure the socket is torn down
});

// Set a timeout to prevent "hanging" sockets
client.setTimeout(5000, () => {
  console.log('Socket timed out, destroying...');
  client.destroy();
});

By explicitly handling the close, error, and timeout events, you ensure that every socket you open has a clear path back to destruction. Treating every connection as a resource with a finite lifespan—rather than an infinite background task—is the key to stable network applications.

Key Takeaway: A socket is an active resource; failing to explicitly handle its termination via error, close, and timeout events will lead to memory leaks and resource exhaustion.