Tracking issue for the design proposed by @olilarkin in #27, which is closed without merging (see below).
Problem
Since #25, SetHeadRotation(), EnableHeadTracking() and EnableLimiter() in src/renderer/obr/obr_capi/obr/obr/renderer/obr_impl.cc take mutex_, the same absl::Mutex that Process() holds for the whole render call. #25 fixed a real race: a four-float quaternion written concurrently with Process() could be read torn. But the fix puts the control thread and the audio thread on the same lock.
absl::Mutex has no priority inheritance. A control thread preempted while holding mutex_ stalls rendering until the scheduler runs it again. Head rotation is written continuously from a sensor thread, so the lock is contended on every buffer, not occasionally. The audio thread should not wait on a lower-priority thread.
Publish the three values instead of locking them:
EnableHeadTracking() and EnableLimiter() flags become std::atomic<bool>.
- The quaternion goes through a seqlock over four
std::atomic<float> (AtomicWorldRotation in obr/common/atomic_rotation.h). std::atomic<WorldRotation> is 16 bytes and not lock-free on the platforms obr targets, so it would reintroduce a hidden lock on the audio thread.
- The seqlock read is bounded, not spin-to-convergence. After a few failed attempts
Load() returns the caller's previous value, so the read is wait-free. Reusing one buffer's rotation is inaudible; missing a deadline is not.
Process() latches all three values once at the top of the block. ProcessingGroup interpolates the rotation across the buffer, so a value that changes partway through is already wrong.
mutex_ still guards audio_elements_ and processing_groups_, which are rebuilt rather than handed over. Nothing that runs continuously during playback takes it.
Tests in the proposal: atomic_rotation_test (round trip, no tearing under concurrent writes, bounded fallback under a write storm) and ObrImplTest.TestConcurrentHeadRotationDuringProcessing (render while a second thread sweeps rotation and toggles both flags). Both clean under ThreadSanitizer; the existing renderer and processing-group tests pass unchanged. No public API change.
The implementation is on SpatialAudioKit/oar:lock-free-control-state (33c4b09, 8 files, +379/-24).
Status
#27 is closed without merging because the Contributor License Agreement was not signed. @olilarkin agreed in the PR thread that the contribution can be adapted. The fix will be resubmitted as a fresh PR based on this design, rebased onto current main, with credit to @olilarkin for the analysis and the design.
Related: #28, the per-render malloc/free in Process(), noticed while measuring this change and filed separately.
Tracking issue for the design proposed by @olilarkin in #27, which is closed without merging (see below).
Problem
Since #25,
SetHeadRotation(),EnableHeadTracking()andEnableLimiter()insrc/renderer/obr/obr_capi/obr/obr/renderer/obr_impl.cctakemutex_, the sameabsl::MutexthatProcess()holds for the whole render call. #25 fixed a real race: a four-float quaternion written concurrently withProcess()could be read torn. But the fix puts the control thread and the audio thread on the same lock.absl::Mutexhas no priority inheritance. A control thread preempted while holdingmutex_stalls rendering until the scheduler runs it again. Head rotation is written continuously from a sensor thread, so the lock is contended on every buffer, not occasionally. The audio thread should not wait on a lower-priority thread.Proposed fix (by @olilarkin, #27)
Publish the three values instead of locking them:
EnableHeadTracking()andEnableLimiter()flags becomestd::atomic<bool>.std::atomic<float>(AtomicWorldRotationinobr/common/atomic_rotation.h).std::atomic<WorldRotation>is 16 bytes and not lock-free on the platforms obr targets, so it would reintroduce a hidden lock on the audio thread.Load()returns the caller's previous value, so the read is wait-free. Reusing one buffer's rotation is inaudible; missing a deadline is not.Process()latches all three values once at the top of the block.ProcessingGroupinterpolates the rotation across the buffer, so a value that changes partway through is already wrong.mutex_still guardsaudio_elements_andprocessing_groups_, which are rebuilt rather than handed over. Nothing that runs continuously during playback takes it.Tests in the proposal:
atomic_rotation_test(round trip, no tearing under concurrent writes, bounded fallback under a write storm) andObrImplTest.TestConcurrentHeadRotationDuringProcessing(render while a second thread sweeps rotation and toggles both flags). Both clean under ThreadSanitizer; the existing renderer and processing-group tests pass unchanged. No public API change.The implementation is on
SpatialAudioKit/oar:lock-free-control-state(33c4b09, 8 files, +379/-24).Status
#27 is closed without merging because the Contributor License Agreement was not signed. @olilarkin agreed in the PR thread that the contribution can be adapted. The fix will be resubmitted as a fresh PR based on this design, rebased onto current
main, with credit to @olilarkin for the analysis and the design.Related: #28, the per-render
malloc/freeinProcess(), noticed while measuring this change and filed separately.