Mutex & Locking
debt(d9/e5/b5/t7)
Closest to 'silent in production until users hit it' (d9). The detection_hints note automated=no and only a regex pattern for flock(). None of the misuses — failing to release in a finally block, using flock() as a distributed lock, or holding locks too long — produce compile-time or lint-time errors. Race conditions and lock leaks are silent until users encounter data corruption or hangs under load in production.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix lists three distinct remediation actions: wrapping releases in finally blocks, replacing flock() with Redis SET NX EX for distributed scenarios, and setting lock timeouts. The flock()-to-Redis migration in particular requires introducing a new dependency, updating configuration, and changing lock acquisition/release logic across all locking sites — more than a single-line swap but not necessarily a full architectural rework.
Closest to 'persistent productivity tax' (b5). Locking applies across web, cli, and queue-worker contexts, meaning any shared-state operation in all three runtime environments must account for the locking strategy. A wrong choice (e.g., flock() in a multi-server environment) silently misbehaves everywhere, and the correct distributed-lock pattern must be consistently applied. It is not quite load-bearing across the entire system shape (b7) but imposes meaningful ongoing discipline across many work streams.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The canonical misconception is explicit: developers familiar with mutex/locking concepts from single-server or OS contexts reasonably expect flock() to provide mutual exclusion across a PHP-FPM cluster, but it is per-server only. This directly contradicts reasonable expectations drawn from the concept's name and from how similar primitives work in other languages/environments, making it a serious trap rather than merely a documented gotcha.
TL;DR
Explanation
Mutex: binary lock — locked or unlocked. Only the holder can unlock it. Critical section: code protected by the mutex. In PHP: flock() for file-based mutex, Redis SET NX EX for distributed mutex, database advisory locks (GET_LOCK in MySQL). Key properties: atomicity of lock/unlock, single-owner, blocking (waits) vs non-blocking (returns false if unavailable). Distributed systems need distributed locks (Redis Redlock, ZooKeeper, DB advisory locks) since file locks only work within one server. Deadlock risk: never hold mutex A while acquiring mutex B (or maintain consistent order). Release in finally to ensure unlock even on exception.
Common Misconception
Why It Matters
Common Mistakes
- Not releasing mutex in a finally block — lock held forever on exception.
- Using flock() as a distributed lock across multiple servers — doesn't work.
- Holding a mutex too long — reduces concurrency, increases contention.
Code Examples
$fp = fopen('lock.txt', 'c');
flock($fp, LOCK_EX);
doWork(); // Exception here leaves lock held forever!
flock($fp, LOCK_UN);
fclose($fp);
// Always release in finally:
$fp = fopen('lock.txt', 'c');
try {
flock($fp, LOCK_EX);
doWork();
} finally {
flock($fp, LOCK_UN);
fclose($fp);
}
// Distributed mutex via Redis:
$acquired = $redis->set('lock:job', 1, ['NX', 'EX' => 30]);
if (!$acquired) return; // Another process holds it
try { doWork(); } finally { $redis->del('lock:job'); }