Skip to content

OBR head-tracking and limiter setters share the render mutex with Process() #46

Description

@trsonic

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.

Proposed fix (by @olilarkin, #27)

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions