Body
Component: src/mod/formats/mod_shell_stream/mod_shell_stream.c
Problem
mod_shell_stream is documented/designed to stream audio from an external command's stdout into a call, using a background thread (buffer_thread_run) that reads from the child process's stdout pipe into a switch_buffer_t incrementally, and a file_read implementation that only blocks until one frame's worth of data is available:
static switch_status_t shell_stream_file_read(switch_file_handle_t *handle, void *data, size_t *len)
{
...
switch_size_t rlen = *len * 2;
while (context->running && switch_buffer_inuse(context->audio_buffer) < rlen) {
switch_cond_next();
}
...
}
This design looks like it should support incremental/streaming playback — audio starts playing as soon as enough has been buffered for one frame, not the whole file.
However, shell_stream_file_open() calls wait(&(context->pid)) in the parent process before returning:
switch_thread_create(&context->thread, thd_attr, buffer_thread_run, context, handle->memory_pool);
context->running = 2;
while (context->running == 2) {
switch_cond_next();
}
wait(&(context->pid)); // <-- blocks here
goto end;
Since FreeSWITCH's playback engine can't call file_read() until file_open() returns, this means no audio plays until the entire external command has exited — regardless of how much data the background thread has already buffered. This defeats the purpose of the buffering thread entirely, and makes mod_shell_stream unusable for any source that streams audio incrementally and takes non-trivial time to finish (e.g. real-time TTS engines), since callers hear nothing until generation is 100% complete.
Impact
Any use case involving a shell command that streams audio progressively (rather than finishing near-instantly, like a short tone generator) experiences a playback delay equal to the entire generation time of the external command, with no way to start audio earlier — even though the underlying buffer/read design clearly intends to support this.
Reproduction
- Write a script that writes raw PCM to stdout in chunks over several seconds (e.g. streaming from a TTS API).
playback shell_stream://your_script.py some args in the dialplan.
- Observe: nothing is heard until the script fully exits, even though it was writing/flushing audio to stdout the whole time.
Proposed fix
See linked PR: move child-process reaping from file_open() (blocking) to file_close() (non-blocking start of playback, with a bounded grace period + SIGKILL fallback to avoid zombies/hangs if the file is closed while the child is still running, e.g. on early hangup).
Environment
FreeSWITCH 1.10.x (reproduced on 1.10.10), Debian, but the bug is not platform-specific — it's a straightforward logic issue in the module's process-management code.
Body
Component:
src/mod/formats/mod_shell_stream/mod_shell_stream.cProblem
mod_shell_streamis documented/designed to stream audio from an external command's stdout into a call, using a background thread (buffer_thread_run) that reads from the child process's stdout pipe into aswitch_buffer_tincrementally, and afile_readimplementation that only blocks until one frame's worth of data is available:This design looks like it should support incremental/streaming playback — audio starts playing as soon as enough has been buffered for one frame, not the whole file.
However,
shell_stream_file_open()callswait(&(context->pid))in the parent process before returning:Since FreeSWITCH's playback engine can't call
file_read()untilfile_open()returns, this means no audio plays until the entire external command has exited — regardless of how much data the background thread has already buffered. This defeats the purpose of the buffering thread entirely, and makesmod_shell_streamunusable for any source that streams audio incrementally and takes non-trivial time to finish (e.g. real-time TTS engines), since callers hear nothing until generation is 100% complete.Impact
Any use case involving a shell command that streams audio progressively (rather than finishing near-instantly, like a short tone generator) experiences a playback delay equal to the entire generation time of the external command, with no way to start audio earlier — even though the underlying buffer/read design clearly intends to support this.
Reproduction
playback shell_stream://your_script.py some argsin the dialplan.Proposed fix
See linked PR: move child-process reaping from
file_open()(blocking) tofile_close()(non-blocking start of playback, with a bounded grace period +SIGKILLfallback to avoid zombies/hangs if the file is closed while the child is still running, e.g. on early hangup).Environment
FreeSWITCH 1.10.x (reproduced on 1.10.10), Debian, but the bug is not platform-specific — it's a straightforward logic issue in the module's process-management code.