Memory Visibility & Cache Coherence
debt(d8/e5/b5/t7)
Closest to 'silent in production until users hit it' (d9), but slightly better (d8) because PHPStan is listed as a tool that can catch some patterns, though automated detection is explicitly marked 'no' in detection_hints. The code_pattern (static property used as cache in Swoole shared between coroutines) requires recognising a specific architectural pattern — this is largely invisible and won't surface until stale data causes observable bugs in production, often intermittently.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests swapping static PHP variables for Redis when using Swoole/RoadRunner — this is not a single-line patch but requires identifying all shared mutable state, replacing it with a coordinated store (Redis), and validating correctness. In Swoole/RoadRunner contexts this can touch multiple files and components, though it doesn't require full architectural rework.
Closest to 'persistent productivity tax' (b5). The issue applies across web, cli, and queue-worker contexts (all three listed in applies_to). Once a team adopts Swoole or RoadRunner, every use of static state or APCu becomes suspect and must be reviewed. This imposes an ongoing mental tax on every developer writing shared state logic, but doesn't reshape the entire system architecture.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception is explicit: PHP developers assume FPM process isolation eliminates all memory visibility problems, then are surprised when APCu staleness, Swoole coroutine sharing, or forked-process coordination causes subtle bugs. The behaviour directly contradicts the 'PHP is safe from concurrency issues' mental model most PHP developers hold, making this a serious cognitive trap without being universally catastrophic.
Also Known As
TL;DR
Explanation
Modern CPUs have L1/L2/L3 caches. Without synchronisation, CPU A's write to a variable may sit in its cache, invisible to CPU B. Memory barriers (fences) force cache flushes ensuring visibility. In high-level languages: Java's volatile, C's atomic, and Go's sync package handle barriers automatically. PHP's PHP-FPM model: each request runs in a separate process with no shared memory — memory visibility issues don't exist within a single request. They arise with: APCu (shared memory between workers), Swoole coroutines sharing state, and pcntl_fork() child processes.
Common Misconception
Why It Matters
Common Mistakes
- Assuming APCu reads are always fresh — there is a window where a cached value may be stale.
- Sharing mutable state between Swoole coroutines without coordination.
- Forked child processes modifying shared APCu state without coordination.
- Expecting process-local variables to be shared across FPM workers — each worker has its own memory.
Code Examples
// Incorrect assumption: APCu updates are instantly visible:
// Worker A writes:
apcu_store('config_version', 42);
// Worker B reads immediately after (different CPU core):
$version = apcu_fetch('config_version'); // Might still see 41!
// APCu uses shared memory but CPU cache may not be flushed yet
// Design around eventual visibility:
// 1. Use a version key with TTL to expire stale values:
apcu_store('config', $data, 60); // Expire after 60s max staleness
// 2. For critical consistency: use Redis (single-process server)
$redis->set('config_version', 42);
// 3. For Swoole: use channel or atomic for coordination:
$chan = new Swoole\Coroutine\Channel(1);
// Producer writes, consumer reads via channel — guaranteed visibility