Home / Articles

Inside libuv: The C Engine Behind Node.js Async IO

Inside libuv, the C library under Node.js: how the event loop, thread pool, handles and requests make non-blocking IO work, and where the threads really are.

Publisher: REACTAPP.TOP6 min read1352 words

This article is published in English.

Sources: Inside libuv: The C Engine That Powers Node.js Asynchronous I/O

This walkthrough rebuilds an operable path for: Inside libuv: The C Engine That Powers Node.js Asynchronous IO. Focus on contracts, checks, and code you can drop into a repo without guessing intent. For Overview, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

The Core Primitives: Handles and Requests

For The Core Primitives: Handles and Requests, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Pin dependency versions and record the image digest that ran the demo.

1. Handles

For 1. Handles, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Pin dependency versions and record the image digest that ran the demo.

2. Requests

For 2. Requests, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Pin dependency versions and record the image digest that ran the demo. For 2. Requests, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

uv_loop_t Structure: The Heart of the Machine

For uv_loop_t Structure: The Heart of the Machine, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Understand the event loop boundaries: what blocks the thread versus what waits on the kernel.

IO Polling Problem: Readiness vs. Completion

For IO Polling Problem: Readiness vs. Completion, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Understand the event loop boundaries: what blocks the thread versus what waits on the kernel.

Thread Pool: Faking Async for the Unfakeable

For Thread Pool: Faking Async for the Unfakeable, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Understand the event loop boundaries: what blocks the thread versus what waits on the kernel. For Thread Pool: Faking Async for the Unfakeable, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Timers: The Min-Heap Implementation

For Timers: The Min-Heap Implementation, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Measure latency under load before changing runtimes. Microbenchmarks rarely match production mix.

Phase Execution Order: A Closer Look at uv__run_idle

For Phase Execution Order: A Closer Look at uv__run_idle, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and cost next to functional results. Visibility early prevents surprise bills when the path moves from demo to shared environments. Measure latency under load before changing runtimes. Microbenchmarks rarely match production mix.

// Simplified libuv uv_run internal logic
while (true) {
    // 1. Run timers phase (drain min-heap)
    uv__run_timers(loop);

    // 2. Run pending callbacks (thread pool completions)
    uv__run_pending(loop);

    // 3. Run idle handles (internal use)
    uv__run_idle(loop);

    // 4. Run prepare handles (before I/O polling)
    uv__run_prepare(loop);

    // 5. Calculate poll timeout and block for I/O
    // (Timeout is based on the next timer in the min-heap)
    uv__io_poll(loop, timeout);

    // 6. Run check handles (setImmediate in Node.js)
    uv__run_check(loop);

    // 7. Run close callbacks
    uv__run_closing_handles(loop);

    // Check if loop should exit (no more active handles)
    if (loop->active_handles == 0) break;
}

Conclusion: The Unsung Hero of Server-Side JavaScript

For Conclusion: The Unsung Hero of Server-Side JavaScript, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Measure latency under load before changing runtimes. Microbenchmarks rarely match production mix. For Conclusion: The Unsung Hero of Server-Side JavaScript, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Operational checklist

For Operational checklist, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.

Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish.

Understand the event loop boundaries: what blocks the thread versus what waits on the kernel.

Write a short runbook: how to rotate keys, how to drain the queue, how to roll back the last change.

Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Understand the event loop boundaries: what blocks the thread versus what waits on the kernel.

Before promoting the stack, freeze versions, capture a golden transcript for the critical path, and confirm rollback steps. Shared environments need rate limits, tenancy checks, and a clear owner for secret rotation. Prefer boring reliability over clever one-off demos.