Atomic Operations
debt(d7/e3/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints explicitly state automated detection is 'no'. The code pattern (Cache::get followed by increment then set) is a heuristic but no standard linter, SAST tool, or static analyzer reliably catches non-atomic read-modify-write patterns in PHP. These race conditions typically surface only under concurrent load during runtime testing or careful code review.
Closest to 'simple parameterised fix' (e3). The quick_fix states replacing read-modify-write with DB atomic increment (SET col = col + 1) or Redis INCR. For simple counters this is essentially a one-line swap (e1-e3), but for multi-step sequences requiring Redis Lua scripts or DB transactions, it touches more logic. Averaging across the common cases, e3 fits — it's a patterned replacement but may require understanding the atomicity boundary.
Closest to 'persistent productivity tax' (b5). Atomic operations apply across web, cli, and queue-worker contexts (all three listed in applies_to). Once a codebase has non-atomic patterns scattered through counters, rate limiters, and distributed coordination points, every future feature touching shared mutable state must consider atomicity. It's not architecture-defining (b7-b9), but it's a persistent tax that affects many work streams — especially in high-throughput systems.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception is that $counter++ is atomic in PHP — developers from single-threaded mental models or languages where increment is atomic will assume this works. PHP's process-per-request model makes it non-obvious that file-backed or cache-backed counters race across PHP-FPM workers. The 'obvious' approach (read, increment, write) is exactly the wrong one, and the common_mistakes reinforce that developers routinely assume PHP assignment is atomic across processes.
TL;DR
Explanation
An atomic operation is guaranteed to complete as a single indivisible unit — no interruption, no partial state. CPU-level: compare-and-swap (CAS), fetch-and-add. Database: UPDATE SET col = col + 1 (atomic in InnoDB). Redis: INCR, SETNX, all single commands are atomic; MULTI/EXEC transactions are atomic; Lua scripts are atomic. PHP: no native atomic primitives (single-threaded process model), but DB and Redis operations are atomic at the storage layer. Lock-free patterns use CAS: read current value → compute new → CAS(expected, new) → retry if failed. SQL INSERT … ON DUPLICATE KEY UPDATE is an atomic upsert.
Common Misconception
Why It Matters
Common Mistakes
- Using non-atomic read-modify-write for counters — use DB atomic increment or Redis INCR.
- Assuming PHP assignment is atomic — it's not across processes.
- Not using Redis MULTI/EXEC or Lua for multi-step atomic sequences.
Code Examples
// Non-atomic counter — race condition:
$views = Cache::get('views') + 1;
Cache::set('views', $views);
// Atomic Redis increment:
$views = $redis->incr('page:views');
// Atomic DB increment:
$pdo->exec('UPDATE pages SET views = views + 1 WHERE id = ?', [$pageId]);
// Atomic upsert:
$pdo->exec(
'INSERT INTO stats (page, views) VALUES (?, 1)
ON DUPLICATE KEY UPDATE views = views + 1', [$page]
);