File Descriptors (FD): What They Are, How They Work, and Why You Hit EMFILE

If you’ve ever seen an error like:
EMFILE: too many open files
you’ve already met one of the most fundamental pieces of an operating system: the file descriptor (FD).
This blog explains:
what an FD is
how its lifecycle works
why your system ran out of them during image export
how memory differs from FD (second problem you hit)
and how Excel image export actually behaves internally (third problem)
🧠 What is a File Descriptor (FD)?
A file descriptor is a small integer that the operating system gives your process when it opens a file or resource.
Think of it as a ticket number:
You ask the OS: “Open this file”
OS says: “Here’s FD = 7”
You use 7 to read/write
When done → you close it
🧾 Example
const fs = require('fs');
const fd = fs.openSync('data.txt', 'r'); // fd = 7 (example)
const buffer = Buffer.alloc(100);
fs.readSync(fd, buffer, 0, 100, 0);
fs.closeSync(fd);
📦 What counts as a file?
In Unix systems:
Everything is treated like a file
| Resource | Uses FD? |
|---|---|
| Disk file | ✅ |
| HTTP request | ✅ |
| Socket | ✅ |
| Stream | ✅ |
🔄 FD Lifecycle (Step-by-Step)
[1] Request resource
↓
[2] OS opens it
↓
[3] OS assigns FD (integer)
↓
[4] Your app uses FD
↓
[5] You close FD
↓
[6] OS frees FD
📊 Diagram
Application Operating System
----------- -----------------
open() ─────────────▶ Allocate FD (e.g., 5)
Open file/socket
read(fd=5) ◀──────────▶ Read data
write(fd=5) ◀─────────▶ Write data
close(5) ─────────────▶ Release FD
Free slot
🔢 Why FD Limits Exist
Each process has a limit:
ulimit -n
Typical values:
1024
4096
65535 (tuned systems)
💥 What is EMFILE?
EMFILE = Too Many Open Files
It means:
❗ You opened more resources than allowed at the same time
🧨 Real Example (Your Case)
50 concurrent downloads
Each opens:
1 HTTP socket (FD)
1 file write (FD)
👉
50 × (socket + file) = ~100 FDs
With multiple jobs:
5 jobs → ~500 FDs → EMFILE
❌ Bad Pattern
await Promise.all(urls.map(download));
👉 Opens everything at once
✅ Good Pattern
for (const url of urls) {
await download(url);
}
👉 Controlled FD usage
🧠 NEW TOPIC 1: FD vs Memory (Critical Difference)
This is where many people get confused.
📊 Comparison
| Concept | FD | Memory |
|---|---|---|
| What it is | Handle | Data |
| Managed by | OS | Your app |
| Limit | OS limit (ulimit) | RAM |
| Error | EMFILE | OOM (Out of Memory) |
🔍 Example
const buffer = await fs.readFile('image.jpg');
What happens:
open file → FD used
read file → buffer created in RAM
close file → FD released
👉 After this:
FD = ❌ gone
Buffer = ✅ still in memory
💥 Key Insight
❗ Closing a file does NOT free memory
🧨 Your second problem (memory)
Even after fixing EMFILE:
workbook.addImage({ buffer })
👉 ExcelJS stores ALL buffers internally
So:
6000 images × 150KB ≈ 900MB RAM
👉 This leads to memory pressure, not FD issue
🧠 Simple analogy
FD = number of doors open 🚪
Memory = stuff inside the room 📦
Closing doors doesn’t remove the stuff.
🧠 NEW TOPIC 2: Excel Image Rendering Limitation (Your 3rd Issue)
You noticed:
After ~3000 rows → images overlap at top
🔍 Root cause
Excel calculates image positions using:
Row height → converted to EMU
1pt = 12,700 EMU
Each image Y position = sum of all previous row heights
⚠️ Limitation
Excel internally uses:
32-bit signed integer
Max = 2,147,483,647
📊 Calculation
If:
row height = 50pt
→ 50 × 12700 = 635,000 EMU per row
Then:
2,147,483,647 / 635,000 ≈ 3,382 rows
💥 What happens after that?
Value overflows → becomes negative
→ Excel places image at wrong position (top)
→ all images overlap
📊 Visual
Correct:
Row 1 → Y = 0
Row 2 → Y = 635k
Row 3000 → Y = valid
After overflow:
Row 3500 → Y = negative ❗
→ jumps to top
→ overlap
❌ Why your “row height fix” felt bad
You reduced:
row height = 16pt
👉 avoids overflow BUT:
image still 50px
row smaller than image
UI breaks
🏁 Proper Solutions (Summary)
✅ For FD issue
limit concurrency
avoid massive
Promise.all
✅ For Memory issue
compress images
reduce unique images
limit concurrent exports
✅ For Excel overlap issue
Best:
👉 Split sheets
Sheet 1 → 0–3000 rows
Sheet 2 → 3001–6000
Alternative:
reduce image size
or fallback to URLs
🧠 Final Mental Model
Problem 1 → FD (too many things open at once)
Problem 2 → Memory (too much data kept)
Problem 3 → Excel limit (internal integer overflow)
💬 One-line takeaway
FD issues are about “how many things are open at once”, memory issues are about “how much data you keep”, and Excel issues are about “format limitations you cannot bypass.”



