Skip to content

roc-send crashes with heap arena panic ("attempt to deallocate chunk not belonging to this arena") when the pulseaudio source stream fails to restart #849

Description

@oriaj-nocrala

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

  1. Create a PipeWire null-sink (pactl load-module module-null-sink sink_name=roc_bridge).
  2. Start roc-send reading pulse://roc_bridge.monitor as above.
  3. Let the sink sit idle (no application playing audio into it) for several seconds up to a couple of minutes.
  4. 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.

Metadata

Metadata

Assignees

Labels

user reportA bug-report or a feature-request not by a maintainer

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions