Skip to content

Phase 3: thread-safety & resource fixes (atomic stream state, JACK/ASIO/OSS) - #2

Merged
tap merged 1 commit into
masterfrom
claude/rtaudio-fork-audit-7cnqfu
Jun 23, 2026
Merged

Phase 3: thread-safety & resource fixes (atomic stream state, JACK/ASIO/OSS)#2
tap merged 1 commit into
masterfrom
claude/rtaudio-fork-audit-7cnqfu

Conversation

@tap

@tap tap commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Overview

Phase 3 of the audit-and-hardening work, building on the merged #1. These are thread-safety and resource-management fixes in the realtime/teardown paths of the backends, plus the core stream_.state change. Findings and status are tracked in AUDIT.md.

All 16 CI jobs pass on this branch (run #5). The stream_.state change is portable and is additionally covered by the ASan/UBSan and StrictWarnings (g++ & clang++) gates; the backend changes are compile-verified across the matrix (Linux ALSA/Pulse/JACK/OSS, macOS/CoreAudio, MinGW WASAPI/ASIO/DirectSound) but — lacking the target hardware — were not runtime-tested. Please review the backend changes with that in mind.

Changes

  • core — stream_.state is now std::atomic<StreamState> (H-5). It's read by the realtime callback thread and written by the control thread (e.g. stopStream) without the stream mutex. All ~180 access sites are plain load/store (no address-of, switch, or arithmetic), so the type change is transparent while removing the data race.
  • JACKcloseStream() now always calls jack_deactivate() before unregistering ports / freeing the handle (was only when state == RUNNING), closing the window where the realtime xrun callback ran against freed ports (H-4). The duplex INPUT error path no longer tears down the client/handle shared with the already-open OUTPUT pass — it only frees the handle when this call allocated it (H-2).
  • ASIO — the process-global asioCallbackInfo is cleared in closeStream() and NULL-checked in bufferSwitch(), sampleRateChanged() and asioMessages(), so a late driver callback can't dereference destroyed stream state (H-3).
  • OSSOssHandle::id[] uses -1 (not 0) as the "unused" sentinel and the teardown paths close only descriptors >= 0; previously fd 0 (a valid descriptor) was treated as "no device" and leaked (M-1).

Deferred (see AUDIT.md)

Remaining low-severity items: the ASIO single-instance streamOpen check-then-set race (ASIO is effectively single-driver), and assorted ignored return codes (ALSA snd_pcm_drop, OSS ioctls, WASAPI/DS CreateEvent/CoCreateInstance) and non-predicate-loop drain handshakes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WHbT36gMxx7WH2tzZfiqUt


Generated by Claude Code

Backend changes are compile-verified by CI (not runtime-tested here for
lack of the target hardware). The portable build stays warning-clean and
its tests pass under -fsanitize=address,undefined.

* core: stream_.state is now std::atomic<StreamState>. It is read by the
  realtime callback thread and written by the control thread (e.g.
  stopStream) without the stream mutex; every access is a plain
  load/store (no address-of, switch, or arithmetic), so the type change
  is transparent while removing the data race.

* JACK: closeStream() now always calls jack_deactivate() before
  unregistering ports and freeing the handle, closing the window where
  the realtime xrun callback could run against freed state (it was only
  deactivating when state == RUNNING). The duplex INPUT error path no
  longer tears down the client/handle shared with the already-open
  OUTPUT pass — it only frees the handle when this call allocated it.

* ASIO: the process-global asioCallbackInfo is cleared in closeStream()
  and NULL-checked in bufferSwitch(), sampleRateChanged() and
  asioMessages(), so a late driver callback can no longer dereference
  destroyed stream state.

* OSS: OssHandle::id[] now uses -1 (not 0) as the "unused" sentinel, and
  the teardown paths close only descriptors >= 0. Previously fd 0 (a
  valid descriptor) was treated as "no device" and leaked.

AUDIT.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHbT36gMxx7WH2tzZfiqUt
@tap
tap merged commit e9efd96 into master Jun 23, 2026
32 checks passed
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.

2 participants