Skip to content

Drain the callback pipe on stream stop#199

Open
padenot wants to merge 1 commit into
mozilla:masterfrom
padenot:drain-data-callabck-stop
Open

Drain the callback pipe on stream stop#199
padenot wants to merge 1 commit into
mozilla:masterfrom
padenot:drain-data-callabck-stop

Conversation

@padenot
Copy link
Copy Markdown
Contributor

@padenot padenot commented Jun 1, 2026

audioipc2 services each stream over two independent IPC pipes, each on its own thread per side: the control pipe (StreamStop/StreamStopped) and the callback pipe (CallbackReq::Data/CallbackResp::Data). There is no ordering guarantee between them, so a data callback already queued on the callback pipe can be delivered to the client after StreamStopped has come back on the control pipe — i.e. after cubeb_stream_stop() has returned. This violates cubeb's contract that no data callback runs after stop.

The fix is to add CallbackReq::Drain / CallbackResp::Drain. After stopping the native stream, the server makes a synchronous Drain call on the callback pipe before replying StreamStopped. The client services that pipe on a single thread in receive order, so by the time it answers Drain every earlier data callback has already run to completion and returned. The call must stay blocking: a fire-and-forget Drain would not order anything and would reintroduce the race.

This guarantee is specific to data callbacks, which users rely on to synchronize shutdown sequences.Sstate and device change callbacks aren't affected by this fix and aren't a problem.

audioipc2 services each stream over two independent IPC pipes, each on
its own thread per side: the control pipe (StreamStop/StreamStopped) and
the callback pipe (CallbackReq::Data/CallbackResp::Data). There is no
ordering guarantee between them, so a data callback already queued on the
callback pipe can be delivered to the client after StreamStopped has come
back on the control pipe — i.e. after cubeb_stream_stop() has returned.
This violates cubeb's contract that no data callback runs after stop.

The fix is to add CallbackReq::Drain / CallbackResp::Drain. After
stopping the native stream, the server makes a *synchronous* Drain call
on the callback pipe before replying StreamStopped. The client services
that pipe on a single thread in receive order, so by the time it answers
Drain every earlier data callback has already run to completion and
returned. The call must stay blocking: a fire-and-forget Drain would not
order anything and would reintroduce the race.

This guarantee is specific to data callbacks, which users rely on to
synchronize shutdown sequences.Sstate and device change callbacks aren't
affected by this fix and aren't a problem.
@padenot padenot force-pushed the drain-data-callabck-stop branch from 222aee1 to 8b6c39e Compare June 1, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant