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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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