Skip to content

mod_shell_stream: file_open() blocks on wait() until the child process exits, preventing streaming playback #3089

Description

@RameshJayamJ

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

  1. Write a script that writes raw PCM to stdout in chunks over several seconds (e.g. streaming from a TTS API).
  2. playback shell_stream://your_script.py some args in the dialplan.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions