Skip to content

refactor: further error callback improvements#1199

Open
roderickvd wants to merge 19 commits into
masterfrom
refactor/remaining-rt-fixes
Open

refactor: further error callback improvements#1199
roderickvd wants to merge 19 commits into
masterfrom
refactor/remaining-rt-fixes

Conversation

@roderickvd
Copy link
Copy Markdown
Member

@roderickvd roderickvd commented May 11, 2026

Continuation from #1187.

  • Updates ASIO to not fire error callbacks before the stream handle is returned.
  • Tightens races on CoreAudio, JACK, ALSA, WASAPI, PulseAudio for the same.
  • Refactors all backends to use a latching mechanism.

This comment was marked as resolved.

This comment was marked as duplicate.

This comment was marked as low quality.

@roderickvd roderickvd requested a review from Copilot May 11, 2026 21:37

This comment was marked as low quality.

This comment was marked as resolved.

@roderickvd roderickvd force-pushed the refactor/remaining-rt-fixes branch from d01734c to 63bf488 Compare May 14, 2026 09:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (8)

src/host/pipewire/device.rs:548

  • This makes failure to acquire the registry fatal for default-device streams, whereas the previous code continued without device-change notifications. That turns an optional monitor failure into stream creation failure, which can prevent otherwise usable streams from being built.
                        Err(e) => {
                            let _ = init_tx.send(Err(Error::with_message(
                                ErrorKind::BackendError,
                                format!("PipeWire: could not acquire registry: {e}"),
                            )));
                            return;

src/host/alsa/mod.rs:263

  • Calling signal_ready() before returning still allows the ALSA worker to run (including realtime error reporting and data callbacks) while this function is preempted before Ok(stream) reaches the caller. This does not fully enforce the “no callbacks before the caller has the handle” guarantee.
        stream.signal_ready();

src/host/wasapi/device.rs:167

  • This unblocks the WASAPI worker before build_output_stream_raw returns to the caller, so a scheduler preemption here can still let the worker invoke the error callback (for example, realtime-priority failure or device-change handling) before the caller receives the Stream handle.
        let stream = Stream::new_output(stream_inner, data_callback, error_callback, monitor)?;
        stream.signal_ready();
        Ok(stream)

src/host/pulseaudio/mod.rs:440

  • signal_ready() runs before the stream is returned, so the driver/latency threads can wake and invoke the user callback before the caller receives the handle. This narrows the old race but does not eliminate it.
        }?;
        stream.signal_ready();
        Ok(stream)

src/host/pipewire/device.rs:465

  • signal_ready() unblocks the PipeWire worker before Ok(stream) reaches the caller; if the worker is scheduled first, realtime promotion failure or other mainloop callbacks can still invoke the error callback before the caller has the stream handle.
        let stream = Stream::new(handle, pw_play_tx, last_quantum, start, stream_ready);
        stream.signal_ready();
        Ok(stream)

src/host/pipewire/device.rs:624

  • signal_ready() unblocks the PipeWire worker before Ok(stream) reaches the caller; if the worker is scheduled first, realtime promotion failure or other mainloop callbacks can still invoke the error callback before the caller has the stream handle.
        let stream = Stream::new(handle, pw_play_tx, last_quantum, start, stream_ready);
        stream.signal_ready();
        Ok(stream)

src/host/aaudio/mod.rs:410

  • This comment is inaccurate when try_emit_error cannot acquire the error-callback mutex: rt_checked remains false, so the RT check is retried on later audio callbacks rather than being performed once per stream.
    // RT check: performed on the audio thread once per stream.

src/host/asio/stream.rs:513

  • add_event_callback can now fail when the debounce timer thread cannot be spawned, but this happens after get_or_create_output_stream has already prepared and stored an ASIO output stream in self.asio_streams. Returning here without rolling that state back can leave a partially-created stream behind after a failed build.
        let driver_event_callback_id = self.add_event_callback(
            &driver,
            error_callback,
            Arc::clone(&hardware_output_latency),
            false,
            Arc::clone(&state),
        )?;

Comment thread src/host/pipewire/device.rs Outdated
Comment thread src/host/pipewire/device.rs Outdated
Comment thread src/host/pipewire/device.rs
Comment thread src/host/alsa/mod.rs
Comment thread src/host/wasapi/device.rs
Comment thread src/host/pulseaudio/mod.rs
Comment thread src/host/jack/stream.rs Outdated
Comment thread src/host/aaudio/mod.rs Outdated
Comment thread src/host/asio/stream.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 12 comments.

Comments suppressed due to low confidence (8)

src/host/wasapi/device.rs:166

  • This wakes the WASAPI worker before build_output_stream_raw has returned the Stream to the caller. The worker can run and invoke the error callback (for example during realtime promotion) in the window before Ok(stream) is returned, so the startup race is not fully closed.
        stream.signal_ready();

src/host/alsa/mod.rs:263

  • This wakes the ALSA worker before the output stream has been returned to the caller. Because the worker may invoke error_callback during its initial realtime-priority setup, the callback-before-handle race remains possible in the window before Ok(stream).
        stream.signal_ready();

src/host/pulseaudio/mod.rs:439

  • This signals the PulseAudio worker threads before the output stream is returned to the caller. The driver/latency threads can run and report errors in the window before Ok(stream), so callbacks are still not strictly gated until after the handle is received.
        stream.signal_ready();

src/host/pipewire/device.rs:655

  • This releases the PipeWire worker before the output stream is returned. The worker can emit the realtime-promotion error immediately after the gate opens, leaving a window where callbacks fire before the caller receives the Stream.
        stream.signal_ready();

src/host/jack/stream.rs:153

  • Changing the state to Paused here happens before new_output returns the Stream. Since JACK notification callbacks are allowed once the state is no longer Initializing, they can still call error_callback before the caller receives the handle.
        StreamState::Paused.store(&state, Ordering::Release);

src/host/asio/stream.rs:817

  • On driver.start() failure this removes callbacks but does not clear the output stream stored by get_or_create_output_stream. Because no Stream is returned to clean it up, subsequent builds on this Device can see stale ASIO stream state and skip buffer preparation for the new driver instance.
        if let Err(e) = driver.start() {
            driver.remove_event_callback(driver_event_callback_id);
            driver.remove_callback(callback_id);
            return Err(build_stream_err(e));

src/host/coreaudio/macos/device.rs:945

  • The output/default-device monitor is active before this returns the Stream, so a device-change, disconnect, or sample-rate notification can still invoke error_callback before the caller receives the handle. Removing audio_unit.start() avoids data callbacks but does not gate these monitor callbacks.
        Ok(Stream::new(inner_arc, monitor))

src/host/coreaudio/ios/mod.rs:239

  • The iOS session observers are active before this output stream is returned, and they invoke error_callback directly on route/media-services notifications. This preserves a callback-before-handle race even though the audio unit no longer starts during construction.
        Ok(Stream::new(
            StreamInner {
                playing: false,
                audio_unit,
            },
            session_manager,

Comment thread src/host/wasapi/device.rs
Comment thread src/host/alsa/mod.rs
Comment thread src/host/pulseaudio/mod.rs
Comment thread src/host/pipewire/device.rs Outdated
Comment thread src/host/jack/stream.rs
Comment thread src/host/pipewire/device.rs
Comment thread src/host/asio/stream.rs
Comment thread src/host/jack/stream.rs
Comment thread src/host/coreaudio/macos/device.rs Outdated
Comment thread src/host/coreaudio/ios/mod.rs Outdated
Add a host/latch.rs utility and replace per-backend Arc<AtomicBool> "stream_ready"
gates with a Latch/Waiter pair.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants