Watch at EaseStories. Answers. Perspective.

Technology · PRACTICAL GUIDE

Node.js EMFILE: find the descriptor-limit owner before raising ulimit

Capture the failing process, its inherited limit and live descriptor growth. Then separate a low ceiling from a leak, concurrency burst or watcher pressure.

Original editorial illustration comparing shell, service, process and live-use evidence.
Original editorial illustration: find the owner of the descriptor boundary before changing a limit. Watch at Ease

EMFILE is an ownership problem before it is a tuning problem. A shell, a systemd service and a restarted process can have different evidence. Preserve the failing PID before a restart erases the strongest clues.

Freeze the failing process before restart

Record the exact command or service, parent launcher, PID, timestamp and first EMFILE stack line. Do not begin with a reboot or dependency reinstall: both can clear the descriptor table and hide whether the count was stable, rising or spiking.

Read the limit the process actually inherited

On Linux, read /proc/PID/limits for Max open files and compare it with the interactive shell's ulimit -Sn and -Hn. A child inherits resource limits from its parent; a shell change made after systemd launched a service does not retroactively change that service.

Measure live descriptors and growth

Count entries in /proc/PID/fd and sample more than once. Group symbolic-link targets into regular files, sockets, pipes and anonymous descriptors. A count pinned near a stable ceiling supports a limit branch; steady growth supports a leak; a short spike tied to parallel work supports a concurrency branch.

Separate watcher pressure from ordinary files

A stack ending in fs.watch or FSEvent marks a watcher surface, not automatic proof of one universal fix. Compare watched scope, ignored directories and process descriptor count. On Linux also distinguish per-process descriptors from separate inotify accounting before changing either boundary.

Change the correct owner once

If the service inherited an unnecessarily low soft limit, use its launcher or unit configuration and restart that service. If the count grows without returning, close the leaked file, socket, timer or watcher. If a burst causes the peak, bound concurrency and close promptly. Do not raise an unrelated shell limit and call the incident fixed.

Prove completion under the original launcher

Rerun the same workload from the same shell, supervisor, CI runner or service unit. Capture the new PID limit, peak descriptor count and post-workload baseline. Completion means EMFILE no longer occurs and the descriptor count returns to an explained steady state—not merely that one restart passed.

Original editorial illustration separating a low ceiling, leak, burst and watcher pressure.
Original editorial illustration: one descriptor snapshot separates four different remedies. Watch at Ease