Course contentsShow
← Node.js
Lesson 1862 of 1,98017. Performance Tuning and ProfilingFree lesson

Auditing Asynchronous Resource Leaks

You will find orphan handles that prevent the event loop from exiting.

In this lesson, you will learn how to identify and debug "orphan" handles that keep the Node.js event loop alive even when your application logic appears to have finished.

In Node.js, the event loop keeps the process running as long as there are active handles (like open sockets, timers, or file watchers) or active requests. A common performance and resource-management issue occurs when an asynchronous operation is initiated but never properly closed or cleared. These "orphan" handles sit in the background, consuming memory and preventing the process from exiting, which can lead to silent memory leaks and zombie processes.

Locating Orphan Handles

When a Node.js process refuses to terminate despite your code finishing its execution, it means there is an active reference in the libuv handle queue. You can audit these by inspecting the process state using process._getActiveHandles() and process._getActiveRequests().

Consider this problematic snippet:

// A simple orphan handle scenario
setInterval(() => {
  console.log("Still running...");
}, 5000);

// We forgot to call clearInterval or unref() the timer

If you run this in a script, Node.js will hang indefinitely. To diagnose this in production or during stress testing, you can log the active handles:

const handles = process._getActiveHandles();
console.log(`There are ${handles.length} active handles holding the event loop open.`);

// You can iterate through them to see what they are
handles.forEach(h => console.log(h.constructor.name));

By auditing these, you might find an forgotten Socket connection, a database pool that hasn't been drained, or a setTimeout that wasn't cleared. Using handle.unref() is a common strategy to allow the event loop to exit even if a handle is still active, but the most robust approach is ensuring every resource—especially I/O listeners—has a corresponding cleanup path. Tracking the lifecycle of these handles is essential for keeping your event loop lean and your process lifecycle predictable.

Key Takeaway: Orphan handles are unintended background tasks that anchor the event loop; use process._getActiveHandles() to identify and prune them to ensure your application can terminate gracefully.