Skip to content

Fix data race in AudioProcessor: thread-safe audioSamples and audioEnergy#443

Open
shawnzhu wants to merge 1 commit intoargmaxinc:mainfrom
shawnzhu:fix/issue-442
Open

Fix data race in AudioProcessor: thread-safe audioSamples and audioEnergy#443
shawnzhu wants to merge 1 commit intoargmaxinc:mainfrom
shawnzhu:fix/issue-442

Conversation

@shawnzhu
Copy link
Copy Markdown

@shawnzhu shawnzhu commented Mar 15, 2026

Summary

  • Add NSLock to protect audioSamples and audioEnergy from concurrent access
  • These properties are written from the audio tap callback thread (in processBuffer) and read from arbitrary threads (e.g., main thread polling relativeEnergy for VAD)
  • Lock is held only for the minimum critical section — energy calculation and callback invocation happen outside the lock

Problem

audioSamples and audioEnergy are plain var properties with no synchronization, causing data races when:

  1. The audio tap callback writes via processBuffer()
  2. Other threads read via relativeEnergy, audioSamples, or purgeAudioSamples()

This manifests as stale/inconsistent values when polling relativeEnergy from the main thread for voice activity detection, and can potentially crash in optimized builds.

Test plan

  • All 194 existing unit tests pass
  • swift build succeeds
  • Manual test: poll relativeEnergy from main thread during recording — values should update consistently

Fixes #442

🤖 Generated with Claude Code

AudioProcessor.audioSamples and audioEnergy are written from the audio
tap callback thread (in processBuffer) and read from arbitrary threads
(e.g. main thread polling relativeEnergy for VAD). This is a data race
that can cause stale reads, inconsistent values, or crashes.

Add an NSLock to serialize access to these properties. The lock is held
only for the minimum critical section — energy calculation happens
outside the lock, and the audioBufferCallback is invoked after releasing
the lock to avoid potential deadlocks.

Fixes argmaxinc#442

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@shawnzhu
Copy link
Copy Markdown
Author

shawnzhu commented Mar 15, 2026

human here: manually tested and it works for me.

The use case is using Whisper's audio energy to detect speech pause.

@shawnzhu
Copy link
Copy Markdown
Author

@ZachNagengast would you be able to review this? You have the most context on the AudioProcessor code. Happy to address any feedback.

@ZachNagengast
Copy link
Copy Markdown
Contributor

@shawnzhu Thanks for the contribution, my main thought currently is that this is handled in #412, in somewhat of a different way. Would you be able to test out that PR with your implementation and see if it covers what you were aiming for with this? If not, we'll have to reconcile them.

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.

Data race in AudioProcessor: audioEnergy/audioSamples accessed from multiple threads without synchronization

2 participants