PABCD Initiative Documentation Hub pabcd_initiative codexclaw cli-jaw

Async Debugging Reference

Source: dev-debugging/references/async-debugging.md

Async Debugging Reference

Concurrency bugs are hard because they are non-deterministic. This reference

covers detection patterns and fix strategies for the most common async issues.

---

Race Conditions

Symptoms: Intermittent failures. Tests pass individually but fail in suite.

Behavior changes with timing (fast machine vs slow CI). Order-dependent results.

Detection:

  1. Add artificial delays (setTimeout, asyncio.sleep) between suspected operations — if behavior changes, timing is involved
  2. Log timestamps at each async operation — look for unexpected ordering
  3. Run under load (concurrent requests) to amplify the window
  4. Check: are two operations reading/writing shared state without coordination?

Fix Patterns:

PatternWhen to Use
Mutex / LockSingle writer, exclusive access to shared state
Queue serializationOperations must execute in order (use a job queue)
Optimistic lockingDB: add version column, reject stale writes
Atomic operationsSimple counters/flags (e.g., INCR in Redis)
Compare-and-swapLock-free update: read → compute → write-if-unchanged

---

Deadlocks

Symptoms: Process hangs without error. No timeout fires. CPU idle but

request never completes. Appears under specific concurrency patterns only.

Detection:

  1. Thread dump: kill -3 <pid> (JVM), process._getActiveHandles() (Node.js)
  2. Check for circular wait: A holds lock X, waits for Y; B holds Y, waits for X
  3. async_hooks (Node.js): trace which async operations are pending
  4. Database: SELECT * FROM pg_locks WHERE NOT granted; (PostgreSQL)

Fix Patterns:

FixDescription
Lock orderingAlways acquire locks in the same global order (alphabetical, by ID)
Timeout + retryAdd timeout to lock acquisition; retry with backoff
Lock-free designReplace locks with message passing or event sourcing
Reduce lock scopeHold locks for the minimum critical section only

---

Event Loop Blocking

Symptoms: All requests slow simultaneously. Timeouts under load. Server

responds in bursts. Single slow endpoint degrades everything.

Detection:

  1. Event loop lag metric: perf_hooks.monitorEventLoopDelay() (Node.js)
  2. --prof flag: generate V8 CPU profile, look for long synchronous frames
  3. blocked-at npm package: reports where the event loop blocked and for how long
  4. Check for: JSON.parse on large payloads, fs.readFileSync, crypto operations, large loops

Fix Patterns:

FixDescription
Worker threadsOffload CPU-bound work to worker_threads
Chunk workBreak large loops into batches with setImmediate between
Stream processingReplace readFileSync / large JSON.parse with streaming
Move to backgroundUse job queue (BullMQ, Celery) for expensive operations

---

Promise / Callback Issues

Unhandled Rejections: Promise rejects but no .catch() or try/catch around await.

// BAD: silent failure
fetchUser(id).then(user => process(user));

// GOOD: explicit error handling
fetchUser(id).then(user => process(user)).catch(err => {
  logger.error('fetchUser failed', { id, error: err.message });
});

Lost Error Context: Wrapping errors without preserving the original stack.

// BAD: original stack lost
catch (err) { throw new Error('Processing failed'); }

// GOOD: chain cause
catch (err) { throw new Error('Processing failed', { cause: err }); }

Callback Hell / Unstructured Async: Deeply nested callbacks make error

propagation impossible to follow. Refactor to async/await with try/catch.

---

Common Async Anti-Patterns

PatternSymptomFix
await in a loopRequests serialize — N items take N x latencyPromise.all() or Promise.allSettled() for independent operations
Fire-and-forget without error handlingSilent failures, lost dataAlways attach .catch() or try/catch — log at minimum
Shared mutable state across async opsIntermittent wrong valuesIsolate state per request, or use atomic operations
Missing await on async functionReturns Promise instead of value, downstream TypeErrorLint rule: require-await, check return types
setTimeout for sequencingBrittle, fails under loadUse proper coordination: events, queues, or await
Catching and swallowing errorsBug disappears but root cause persistsCatch, log with context, re-throw or handle meaningfully

---

Node.js Specific Tools

ToolPurpose async_hooksTrace async operation lifecycle (init, before, after, destroy). Identify leaked contexts.
wtfnodeDetect what is keeping the process alive (open handles, timers, sockets)
why-is-node-runningSimilar to wtfnode — prints active handles preventing exit
clinic.jsSuite: Doctor (event loop), Bubbleprof (async flow), Flame (CPU)
--trace-warningsShows stack trace for unhandled rejections and deprecation warnings
process._getActiveHandles()Quick REPL check for open handles (sockets, timers, servers)
node --inspect + Performance tabRecord async timeline, identify gaps and queuing