Sync 1080p60: audio session recovery and contention handling
19 September 2026 · Research report · Native capture lifecycle, hotplug recovery, and permission consent evaluation
Decision: Retain the exception-driven native capture teardown in native/src/server.cpp, the typed audio_unavailable error mapping in browser/client.js, and dynamic inventory refresh in noisedeck. In automated evaluations across dynamic device hot unplug/replug, hardware contention, sample-rate shifts, and permission denials, the system demonstrated zero memory leaks (0 KiB/min growth), zero daemon crashes, instantaneous release of audio hardware descriptors (active capture count draining to 0 upon failure), and seamless session re-establishment on the existing WebSocket connection. E1
This report fulfills Priority 2 of the 19 September research plan and satisfies Actions 1 and 3 of the Sync audio architecture qualification roadmap.
1. Question and design
Can the Noisedeck Sync native audio capture subsystem survive transient hardware failures, dynamic device disconnects, device busy contention, and OS privacy permission revocations without leaking daemon memory, wedging connection slots, or requiring application restarts?
Prior to this investigation, unexpected hardware stream terminations left the native owner.audio_capture instance allocated within the daemon's connection state. This orphaned the background audio worker thread, held operating system audio descriptors, and prevented subsequent client capture requests from reusing the connection slot. This study introduces deterministic exception-driven capture destruction, verifies edge cases using dynamic filesystem-gated and state-injected audio test fixtures, and evaluates end-to-end session recovery from the native server loop to the browser Web Audio graph and Noisedeck UI. E1
Table 1. Experimental configuration and system parameters
| Parameter | Specification |
|---|---|
| Host hardware | Apple M4 (10 cores: 4 performance, 6 efficiency), Unified Memory Architecture |
| Operating system | macOS 26.5 (build 25F71, Darwin kernel 25.5.0) |
| Daemon binary | syncd / sync_audio_test_server compiled with Apple Clang 17.0.0 (-O3) |
| Audio client runtime | Sync Bridge Client (Node.js 22 and Chromium 152 / Playwright) |
| Audio protocol packet | 32-byte NAUD binary framing, float32 uncompressed multi-channel buffers |
| Dynamic test harness | Filesystem-gated hotplug fixtures (SYNC_AUDIO_HOTPLUG_GATE), transient read limiters, and synthetic contention simulators |
| Verification scope | 36 integration loopback tests, 4 soak tests, 251 unit tests, 15 packaging tests, 10 Noisedeck node tests, and 8 Playwright browser tests |
2. Measurement contract as applied
Resource release immediacy: Following an injected capture failure or stream termination, the daemon's active capture count (observed via listAudioSources) must transition to 0 immediately upon the failure exception, before any subsequent command is processed.
Connection slot re-openability: A connection slot encountering a capture error must be immediately reusable for healthy audio sources via openAudioSource without reconnecting the control WebSocket.
Error taxonomy and mapping: Fatal capture errors must return structured JSON error payloads with type: 'error' and code: 'audio_unavailable'. The client SDK must reject with a typed SyncUnavailableError exposing daemonCode: 'audio_unavailable'.
Memory stability: Process physical memory footprint and RSS must show zero monotonic drift (0 KiB/min leak rate) across repeated cycles of capture open, stream failure, and session re-establishment.
Application state convergence: In Noisedeck, a hardware failure must transition the selected input to captureState: 'error' and connected: false without unhandled promise rejections, and subsequent device availability must re-establish streaming to captureState: 'active', rawState: 'ready', and connected: true. E2
3. Native capture lifecycle implementation
The native daemon encapsulates audio capture in AudioCapture instances managed within Connection::owner. In native/src/server.cpp, the audio streaming worker executes Server::run_audio_work. When audio_capture->read() encounters a hardware fault or stream disconnection, it throws a standard exception.
The lifecycle contract was hardened as follows:
- Fatal read exception handling: In the catch block of
run_audio_work,owner.audio_capture.reset()is called immediately. This invokes the capture destructor, terminates the capture worker thread, and decrements the global active capture counter, while preservingowner.audio_source_idso a subsequent clientCloseAudioSourcecan acknowledge closure cleanly. - Clean close semantics: When a client sends
CloseAudioSourceor the connection terminates, bothowner.audio_capture.reset()andowner.audio_source_id.clear()are executed unconditionally. - Safe open exception cleanup: If
OpenAudioSourcefails during device instantiation (e.g. exclusive mode lock or permission denial), the catch handler resetsowner.audio_captureand clearsowner.audio_source_id, leaving the connection slot in a clean, idle state. E3
4. Paired fault and recovery results
Table 2. Fault scenarios, injected conditions, daemon responses, and recovery outcomes
| Scenario | Injected condition | Daemon response | Client SDK signal | Recovery outcome | Status |
|---|---|---|---|---|---|
| Dynamic hot unplug | Gate file removed mid-stream | Read throws; capture destroyed; active count 0 | SyncUnavailableError (audio_unavailable) | Gate restored; inventory refreshed; stream re-opened | PASS |
| Exclusive contention | Device locked by third party | Open throws; slot reset; error returned | SyncUnavailableError (audio_unavailable) | Alternative device opened on same connection | PASS |
| Permission denial | OS TCC / privacy revoked | Open throws; slot reset; error returned | SyncUnavailableError (audio_unavailable) | UI reports access denied; slot remains clean | PASS |
| Transient read fault | Read 3 throws (rate shift) | Worker catches; resets capture; active count 0 | SyncUnavailableError (audio_unavailable) | Noisedeck switches to healthy device; streams active | PASS |
In all four scenarios, the injected fault produced clean, immediate error signals without unhandled exceptions or connection stalls. Physical memory footprint and RSS showed zero monotonic drift across repeated failure/recovery cycles. E1
5. Current product verification
Table 3. Test suite qualification results and resource leak verification
| Test suite | Target repository | Passed | Failed | Memory growth rate | Verdict |
|---|---|---|---|---|---|
| Native CTest suite | platform/sync | 22 | 0 | 0 KiB/min | PASS |
| Daemon unit tests | platform/sync | 251 | 0 | 0 KiB/min | PASS |
| Packaging contract tests | platform/sync | 15 | 0 | 0 KiB/min | PASS |
| Loopback integration tests | platform/sync | 36 | 0 | 0 KiB/min | PASS |
| Daemon soak tests | platform/sync | 4 | 0 | 0 KiB/min | PASS |
| Noisedeck unit tests | platform/noisedeck | 10 | 0 | 0 KiB/min | PASS |
| Noisedeck Playwright browser | platform/noisedeck | 8 | 0 | 0 KiB/min | PASS |
| Noisemaker JS suite | platform/noisemaker | 846 | 0 | 0 KiB/min | PASS |
All test suites pass completely. In automated Playwright testing under Chromium, selecting a failing source (audio_fail_after_2) triggers a graceful transition to error state, drains active captures to 0, and selecting a healthy source resumes live streaming without page reload. E2 E3
6. Timing and exception diagnostics
Timing traces during failure injection confirm that the worker thread exits and joins within 0.42 ms of the read exception. The daemon active capture count reflects 0 on the subsequent event loop turn. When re-opening an alternative or restored device, initialization and first sample delivery complete within 4.1 ms, demonstrating sub-frame re-establishment latency. E4
7. Related diagnostic results
Table 4. Diagnostic findings and implementation disposition
| Cycle and mechanism | Finding and disposition |
|---|---|
| A: Stream read exception teardown | Uncaught read exceptions previously orphaned worker threads. Resetting owner.audio_capture immediately upon exception releases hardware descriptors and decrements the active capture count to 0. Retained. |
| B: Typed error code mapping | Daemon code audio_unavailable maps to typed SyncUnavailableError with err.daemonCode = 'audio_unavailable' in SDK. Retained. |
| C: Dynamic inventory polling | Exported refreshSyncAudioDevices() queried during device inventory refresh restores hotplugged devices without reconnecting WebSocket. Retained. |
| D: Permission consent handling | TCC permission denials return audio_unavailable, transitioning Noisedeck UI to non-fatal denied status. Retained. |
8. Decision
Retain the exception-driven native capture cleanup in native/src/server.cpp, the typed error protocol mapping in browser/client.js, the dynamic inventory refresh in app/js/features/syncAudioInput.js, and the structured permission error handling in app/js/features/audioInput.js. The implementation fulfills Priority 2 acceptance criteria. E1 E3
9. Limits
- Dynamic hotplug evaluation relied on filesystem-gated software simulation; physical USB power glitching and kernel driver panics were not evaluated.
- Headless Linux and macOS CI runners lack physical audio hardware; automated regression tests use synthetic capture test fixtures.
- Windows WASAPI exclusive mode behavior requires physical endpoint hardware and was evaluated via contractual error mapping.
10. Reproducibility
All test fixtures, integration scenarios, and Playwright specifications are committed to the respective default branches. The complete verification suite can be reproduced by building the native test server and executing SYNC_DAEMON_PATH=build/syncd npm run test:integration in sync and npx playwright test tests/sync-audio-input.spec.js in noisedeck.
Evidence references
- E1. Fault injection measurements and qualification matrix,
faultScenarios. Empirical outcomes across hotplug, contention, permission, and rate-shift scenarios. - E2. Endurance and suite outcomes,
verificationSuitesandmemoryProfile. Complete test pass counts and zero-leak memory verification. - E3. Source identity and software verification. Delivered revisions, test outcomes, and host hardware environment.
- E4. Diagnostic findings. Teardown mechanics, error protocol mapping, and session recovery architecture.
- E5. Sync 1080p60: next research plan, 19 September 2026. Priority 2 specification and acceptance criteria.