Audio DSP Realtime Safety Review
The cardinal rule: If you don't know how long it will take, don't do it.
The audio callback has a hard deadline — typically 1–5ms per buffer. Miss it once and the user hears a glitch. The three categories of violations that cause this are: allocations, locking, and system calls — all of which can block for unbounded time.
Step 1 — Find the realtime context
Identify the audio callback and every function it calls (transitively). Common names:
processBlock, process, render, getNextAudioBlock, audioDeviceIOCallback,
AURenderCallback, JackProcessCallback, and the output-stream closure in cpal/JUCE/PortAudio.
Step 2 — Scan for violations
Memory allocations (allocator paths may lock, grow arenas, touch new pages, or run unbounded bookkeeping):
new/delete/malloc/free/make_shared/make_uniquestd::stringconstruction, concatenation,to_string()- Container growth:
vector::push_backwithout pre-reserve,map::operator[]on new keys,unordered_map::insert,deque::push_back std::functionwith non-trivial captures (may allocate), throwing exceptions
Locking (priority inversion: the low-priority UI thread holds the lock your audio thread needs):
std::mutex,lock_guard,unique_lock,scoped_lock,shared_mutexpthread_mutex_lock,pthread_rwlock_*condition_variable::wait(),future::get(),semaphore::acquire()std::mutex::try_lock()+ RAII wrapper —try_lock()itself is non-blocking, but the RAII destructor callsunlock(), which does a system call to wake waiting threads. Not realtime-safe. Same forstd::unique_lock(mtx, std::try_to_lock).- Spinlocks without exponential back-off on the non-audio thread — busy-wait burns CPU,
causes starvation under contention, and drains battery on mobile devices.
If you must use a spinlock: audio thread calls only
try_lock()+ fallback; non-audio thread uses progressive back-off (spin →_mm_pause()→ batched pauses → occasional yield).
System calls / blocking I/O (kernel transitions stall for unbounded time):
printf,fprintf,cout,cerr,DBG()(JUCE debug),syslog,os_log- File I/O:
fopen,fclose,fread,fwrite,open,read,write sleep,usleep,nanosleep,std::this_thread::sleep_for,yield- Network:
connect,send,recv,select std::system(),assert()(in debug builds: prints + aborts)
Warnings (flag but don't block):
- First-touch page faults — allocate and pre-touch buffers in
prepareToPlay - Denormal floats — missing
ScopedNoDenormalsor_MM_SET_FLUSH_ZERO_MODEcauses 100× slowdown std::rand()— uses a global lock on some implementations
Step 3 — Write the review
## Audio Realtime Safety Review: `[file / function]`
### Verdict
[Safe | Has critical violations | Warnings only] — [one sentence summary]
### Critical Violations
**[Category]: [description]**
`file:line` — `offending code`
Why: [one sentence on the realtime risk]
Fix: [concrete suggestion]
### Warnings
[same format]
### What's Done Well
[correct patterns observed — pre-allocation, atomics, lock-free queues, etc.]
### Recommended Fixes (priority order)
1. ...
Quick fix table
| Violation | Realtime-safe alternative |
|---|---|
std::mutex for shared state | Lock-free std::atomic<T> for scalars after checking is_lock_free(); SPSC lock-free queue for structs |
std::mutex::try_lock() + RAII | unlock() in destructor does a syscall — not safe. Use std::atomic_flag spinlock with try_lock() only on audio thread + fallback |
| Spinlock with busy-wait on non-audio thread | Progressive back-off: spin 5× → _mm_pause() 10× → batched 10× _mm_pause() → occasional std::this_thread::yield() |
new/delete in callback | Allocate in prepareToPlay; use pre-allocated pool or ring buffer |
printf/DBG() | Write to a lock-free ring buffer; drain from a background thread |
std::string ops | std::array<char,N> + snprintf; format on UI thread |
vector growing | reserve() on init; fixed-capacity container (etl::vector, heapless::Vec) |
map::operator[] insertion | find() + pre-populate; or std::array with index lookup |
| Exception throwing | Validate in prepareToPlay; use error codes; never throw from callback |
std::function w/ captures | Function pointer + void*; or pre-store as member |
For deeper patterns (JUCE-specific, VST3, CLAP, Rust/cpal, Web Audio) see
references/realtime-violations.md.