← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Event Loop Internals

Concurrency PHP 8.1+ Advanced
debt(d7/e5/b5/t9)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints list PHPStan but mark automated as 'no', meaning the tool does not automatically flag blocking calls inside async code. Identifying sleep(), file_get_contents(), or synchronous DB calls inside ReactPHP/Amp loops requires careful code review or observing the symptom (entire loop stalling) at runtime. No linter rule fires automatically, placing this firmly at d7.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix acknowledges this is not a one-line swap: every blocking I/O call (synchronous DB queries, file reads, sleep) must be replaced with async equivalents throughout the affected component or service. This commonly touches multiple files within a queue-worker or CLI context, but is not a full cross-cutting architectural rework.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The term applies only to cli and queue-worker contexts (not all PHP contexts), so its reach is narrower than a system-wide concern. However, within async PHP codebases, the single-threaded event loop model shapes every I/O decision — developers must continuously reason about what blocks and what yields — imposing a persistent productivity tax on all work in that context.

t9 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'catastrophic trap — the obvious way is always wrong' (t9). The misconception is explicitly stated: developers believe the event loop makes things run simultaneously (parallel), but it is a single thread processing one callback at a time. This directly contradicts intuition built from multi-threaded or OS-level concurrency models. The 'obvious' action — calling any synchronous I/O — silently stalls all other pending work, matching the t9 anchor exactly.

About DEBT scoring →

Also Known As

event loop libuv epoll async I/O

TL;DR

The mechanism that enables single-threaded async programs — a loop that checks for completed I/O events, runs their callbacks, then checks again.

Explanation

The event loop monitors file descriptors (sockets, pipes) via OS primitives (epoll, kqueue, select). When an I/O operation completes, the loop places the callback on the task queue. The loop processes the task queue — running callbacks until empty — then checks for new I/O events. Node.js uses libuv; PHP's ReactPHP and Revolt use the same OS primitives. PHP Fibers integrate with event loops by suspending execution when awaiting I/O and resuming when the loop signals completion.

Diagram

flowchart TD
    CALL[Call Stack] -->|empty?| CHECK{Event Loop
Check}
    CHECK -->|Macro tasks| MAC[setTimeout<br/>setInterval<br/>I/O callbacks]
    CHECK -->|Micro tasks| MIC[Promise.then<br/>await resolution]
    MIC -->|runs first| CALL
    MAC -->|runs after microtasks| CALL
    IO[I/O ops<br/>fetch, fs, db] -->|non-blocking| KRNL[OS Kernel]
    KRNL -->|complete| MAC
style MICRO fill:#238636,color:#fff
style MACRO fill:#1f6feb,color:#fff
style CALL fill:#6e40c9,color:#fff

Common Misconception

The event loop is magic that makes everything run simultaneously — it is a single thread processing one callback at a time; 'concurrent' means interleaved, not parallel.

Why It Matters

Understanding the event loop explains why one blocking operation freezes all async code, why CPU-bound work must be offloaded, and why callback order is deterministic.

Common Mistakes

  • Long synchronous operations in the event loop — CPU-bound work blocks all other pending callbacks.
  • Not understanding microtask vs macrotask queues — Promise callbacks (microtasks) run before setTimeout (macrotasks).
  • Blocking system calls inside async code — file_get_contents() blocks the entire event loop; use async I/O.
  • Not releasing control back to the event loop in generators — yield must be used to allow other tasks to run.

Code Examples

✗ Vulnerable
// Blocking the event loop:
Revolut\EventLoop\run(function(): void {
    $data = file_get_contents('https://api.example.com/data'); // BLOCKS!
    // While waiting (500ms), NO other async tasks can run
    // Even simple timers and heartbeats stall
    echo $data;
});
✓ Fixed
// Non-blocking — event loop runs other tasks while waiting:
use Amp\Http\Client\HttpClientBuilder;
use function Amp\async;

AmplEventLoop\run(function(): void {
    $client = HttpClientBuilder::buildDefault();
    // Multiple concurrent HTTP requests — loop handles all:
    $futures = array_map(
        fn($url) => async(fn() => $client->request(new Request($url))),
        $urls
    );
    $responses = Amp\Future\awaitAll($futures); // Concurrent, non-blocking
});

Added 15 Mar 2026
Edited 5 Apr 2026
Views 123
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
1 ping S 0 pings M 1 ping T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 1 ping W 0 pings T 0 pings F 2 pings S 0 pings S 0 pings M 1 ping T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M
No pings yet today
No pings yesterday
PetalBot 11 Amazonbot 10 ChatGPT 8 Ahrefs 7 SEMrush 7 Perplexity 6 Google 6 Bing 5 Scrapy 4 Majestic 3 Unknown AI 3 Applebot 2 Meta AI 1 Twitter/X 1 Brave Search 1
crawler 69 crawler_json 5 pre-tracking 1
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Synchronous concurrency Synchronous means each step of your program waits for the previous step to finish before starting. The code runs in order, one line at a time.

Almost all code you write starts out synchronous, and understanding this default is what makes async, threads, and concurrency make sense later. Without it, you cannot see what problem those tools actually solve.

💡 If a step takes noticeable time and nothing else can happen while it waits, that step is synchronous — decide if that's what you want.

Ask Codex about Synchronous →
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: High
⚡ Quick Fix
Understanding the event loop helps diagnose async PHP issues — a blocking call (sleep, synchronous DB) blocks the entire loop, not just that coroutine; use async versions of all I/O
📦 Applies To
PHP 8.1+ any cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
sleep() or synchronous blocking calls inside ReactPHP/Amp async code halting the entire event loop
Auto-detectable: ✗ No phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update

References


✓ schema.org compliant