Summary
roc-send aborts with an internal roc_panic() (heap arena corruption) when the PulseAudio input stream (pulse://<sink>.monitor) is reported as broken and the automatic stream-restart attempt itself fails. This happens reproducibly when the monitored sink goes idle (no client producing audio) for a while and the underlying pulse/PipeWire monitor stream is corked/torn down.
Observed 3 times in local testing, always with the same panic signature.
Environment
- roc-toolkit / roc-send version: 0.4.0
- OS: Arch Linux, kernel 7.1.6-zen1-1-zen
- Audio server: PipeWire 1.6.8 (pipewire-pulse compatibility layer)
- Build: local Arch rebuild of the official
roc-toolkit PKGBUILD with --disable-sox added to the scons invocation (to work around an unrelated packaging issue: upstream binaries link libsox_ng.so.3 without declaring the dependency, and sox-ng 14.6.x is ABI-incompatible causing array: can't allocate memory panics in the CLI tools). The crash below is in generic roc_sndio/roc_core code, not sox-related.
Command
roc-send -v --target-latency 200ms \
-i pulse://roc_bridge.monitor \
-s rtp+rs8m://192.168.100.41:10001 \
-r rs8m://192.168.100.41:10002 \
-c rtcp://192.168.100.41:10003
roc_bridge is a PipeWire module-null-sink used as a virtual "audio to network" sink (apps play into it, roc-send reads its monitor and streams it out).
Steps to reproduce
- Create a PipeWire null-sink (
pactl load-module module-null-sink sink_name=roc_bridge).
- Start
roc-send reading pulse://roc_bridge.monitor as above.
- Let the sink sit idle (no application playing audio into it) for several seconds up to a couple of minutes.
- At some point the pulse source logs
stream is broken / pa_stream_peek(): Bad state, attempts restarting stream, the restart itself fails (can't restart stream), and the process then crashes instead of exiting cleanly or retrying.
Observed log output
00:24:54.284 [err] roc_sndio: pulseaudio source: stream is broken
00:24:54.284 [err] roc_sndio: pulseaudio source: pa_stream_peek(): Bad state
00:24:54.284 [inf] roc_sndio: pulseaudio source: restarting stream
00:24:57.770 [err] roc_sndio: pulseaudio source: can't restart stream
00:24:57.770 [inf] roc_pipeline: sender sink: removing slot
src/internal_modules/roc_core/heap_arena.cpp:93: error: roc_panic()
ERROR: roc_core: heap arena: attempt to deallocate chunk not belonging to this arena: this_arena=0x7ffe483543b0 chunk_arena=(nil)
Backtrace (from coredumpctl)
Stack trace of thread (main):
#0 n/a (libc.so.6 + 0x9a01c)
#1 raise (libc.so.6 + 0x3e5d0)
#2 abort (libc.so.6 + 0x25685)
#3 roc::core::die_gracefully(char const*, bool)+0xB9
#4 roc::core::panic(char const*, char const*, int, char const*, ...)+0x188
#5 roc::core::HeapArena::deallocate(void*)+0xEF
#6 roc::core::ScopedPtr<roc::sndio::ISource, roc::core::ArenaAllocation>::~ScopedPtr()+0x30
#7 main+0x14E0
#8 n/a (libc.so.6 + 0x27781)
#9 __libc_start_main (libc.so.6 + 0x278b9)
#10 _start (roc-send + 0x232c5)
chunk_arena=(nil) suggests the pointer being freed in ScopedPtr<ISource>'s destructor was never allocated from the arena in the first place (or was already freed) — looks like the pulse source's failed-restart error path leaves ISource in a state where ownership/allocation bookkeeping is inconsistent, and the top-level cleanup in main() then double-frees or frees a bad pointer.
Additional context
- Separately (not part of this crash), the same idle-sink scenario can also produce transient
stream timeout expired: latency=... timeout=... and can't open input file or device: uri=pulse://... errors around startup/PipeWire-pulse restarts — those recover fine on their own via process restart, only the "stream is broken → restart fails → panic" sequence above is fatal.
- Workaround in use: keep the monitored sink continuously fed with a silent audio stream so it never goes idle long enough to hit the corked/broken state. This avoids the crash but doesn't fix the underlying bug —
roc-send shouldn't abort the whole process on a recoverable source-restart failure.
Happy to provide the full coredump / run under gdb if useful.
Summary
roc-sendaborts with an internalroc_panic()(heap arena corruption) when the PulseAudio input stream (pulse://<sink>.monitor) is reported as broken and the automatic stream-restart attempt itself fails. This happens reproducibly when the monitored sink goes idle (no client producing audio) for a while and the underlying pulse/PipeWire monitor stream is corked/torn down.Observed 3 times in local testing, always with the same panic signature.
Environment
roc-toolkitPKGBUILD with--disable-soxadded to the scons invocation (to work around an unrelated packaging issue: upstream binaries linklibsox_ng.so.3without declaring the dependency, and sox-ng 14.6.x is ABI-incompatible causingarray: can't allocate memorypanics in the CLI tools). The crash below is in genericroc_sndio/roc_corecode, not sox-related.Command
roc_bridgeis a PipeWiremodule-null-sinkused as a virtual "audio to network" sink (apps play into it,roc-sendreads its monitor and streams it out).Steps to reproduce
pactl load-module module-null-sink sink_name=roc_bridge).roc-sendreadingpulse://roc_bridge.monitoras above.stream is broken/pa_stream_peek(): Bad state, attemptsrestarting stream, the restart itself fails (can't restart stream), and the process then crashes instead of exiting cleanly or retrying.Observed log output
Backtrace (from coredumpctl)
chunk_arena=(nil)suggests the pointer being freed inScopedPtr<ISource>'s destructor was never allocated from the arena in the first place (or was already freed) — looks like the pulse source's failed-restart error path leavesISourcein a state where ownership/allocation bookkeeping is inconsistent, and the top-level cleanup inmain()then double-frees or frees a bad pointer.Additional context
stream timeout expired: latency=... timeout=...andcan't open input file or device: uri=pulse://...errors around startup/PipeWire-pulse restarts — those recover fine on their own via process restart, only the "stream is broken → restart fails → panic" sequence above is fatal.roc-sendshouldn't abort the whole process on a recoverable source-restart failure.Happy to provide the full coredump / run under gdb if useful.