dashboard: serialize mp3 decode to avoid libsndfile SIGBUS - #13
Open
ziipo wants to merge 1 commit into
Open
Conversation
The dashboard intermittently died with "Bus error: 10" ~1.4s after launch,
before training started. The faulting thread was inside libsndfile:
mpeg_init <- mpeg_open <- psf_open_file <- sf_open <- ffi_call
with pc = lr = far = 0x1 and ESR "PC alignment" -- the CPU jumped to 0x1 and
faulted on the instruction fetch. Register x2 pointed at a symbol named
`jmpbuf`, and three threads were inside libsndfile at the same moment
(psf_fread, mpeg_dec_seek, mpeg_init).
libsndfile 1.2.2's MPEG decoder reports errors by longjmp-ing through a
process-global jmpbuf (libmpg123), which is not thread-safe. Concurrent mp3
decodes race on it: one thread's setjmp context is overwritten by another's,
the longjmp lands on a stack frame that no longer exists, and the process
takes SIGBUS.
_load_audio is reached from three concurrency sources at once -- _spec_pool
(4 workers), the 16-worker pool at server.py:5867, and ThreadingMixIn on the
HTTP server -- so spectrogram rendering fans out enough simultaneous decodes
to hit the window regularly. Runs with more accumulated demo mp3s trip it
sooner.
Serialize the sf.read behind a module-level lock. The resample below it is
pure numpy/torch and stays outside, so only decoding is serialized.
Verified: 16 threads x 25 rounds (400 concurrent decodes) through the patched
_load_audio, three consecutive runs, no crash. Sequential decode of all 127
mp3s passed both before and after, confirming this is not file corruption.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
The dashboard intermittently died with
Bus error: 10roughly 1.4 s after launch, before training started. Once it began happening it appeared deterministic — every restart crashed — which initially looked like a corrupt file in the dataset.Root cause
It isn't file corruption. The faulting thread is inside libsndfile:
The register state is the tell:
pc = lr = far = 0x1, ESR reports PC alignment — the CPU jumped to address0x1and faulted on the instruction fetch itself.x2points at a symbol namedjmpbuf.psf_fread,mpeg_dec_seek, andmpeg_init.libsndfile 1.2.2's MPEG decoder reports errors by
longjmp-ing through a process-globaljmpbuf(libmpg123), which is not thread-safe. With concurrent mp3 decodes, thread A'ssetjmpcontext is overwritten by thread B before A'slongjmpfires; A then jumps into a stack frame that no longer exists, lands on garbage, and the process takes SIGBUS._load_audiois reached from three concurrency sources at once:_spec_pool(4 workers,server.py:3007)server.py:5867ThreadingMixInon the HTTP server (server.py:7847)Spectrogram rendering therefore fans out enough simultaneous decodes to hit the race regularly. Runs with more accumulated
demo_*.mp3files trip it sooner, which is what made it look dataset-specific — it was simply the run with the most demos.Fix
Serialize
sf.readbehind a module-level lock. The resample below it is pure numpy/torch and stays outside the lock, so only decoding is serialized.Verification
_load_audio, three consecutive runs — no crash.One caveat on rigor: I was never able to trigger the crash on demand in a harness, so this is not a before/after A/B. The diagnosis rests on the crash report's register state and the three-threads-in-libsndfile evidence, not on a reproduction.
Trade-offs / alternatives
This is a workaround for an upstream libsndfile/libmpg123 limitation, not a fix for a logic error in underfit. It also serializes all audio decoding, which is a real throughput cost for a dashboard whose job is rendering many spectrograms. Two narrower options if you'd prefer:
mpeg_initand are unaffected, so they could decode in parallel.Happy to rework it either way.
🤖 Generated with Claude Code