-
Notifications
You must be signed in to change notification settings - Fork 309
Fix audio recording race conditions (Compose) #6077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix audio recording race conditions (Compose) #6077
Conversation
…tart recording completes.
…rt recording completes.
…rt recording completes.
SDK Size Comparison 📏
|
WalkthroughThe changes fix audio recording race conditions by introducing state guards and thread-safe flags. The UI component now checks for locked states during drag operations, while the audio controller uses pending flags to handle concurrent calls to Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
CHANGELOG.md (1)
71-73: Changelog entry looks correct and properly scoped to Compose.
The item is in the right section and links to the PR. Optionally, consider making the description slightly more specific (e.g., “...when lock/cancel is invoked immediately after start”) to aid release notes readers.stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.kt (1)
176-206: Consider adding unit tests for race condition scenarios.The race condition handling logic is critical. Based on learnings, consider adding deterministic tests using
runTestwith virtual time to verify:
cancelRecordingcalled duringstartRecording's IO results in proper cleanuplockRecordingcalled duringstartRecording's IO transitions toLockedstate- Multiple concurrent calls don't cause unexpected state transitions
Do you have unit tests covering these race condition scenarios? If not, would you like me to help draft test cases?
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
CHANGELOG.mdstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.ktstream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.kt
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{kt,kts}
📄 CodeRabbit inference engine (AGENTS.md)
Format and apply Kotlin style with Spotless (4 spaces, no wildcard imports, licence headers)
Files:
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt
**/*.kt
📄 CodeRabbit inference engine (AGENTS.md)
**/*.kt: Use@OptInannotations explicitly; avoid suppressions unless documented
Document public APIs with KDoc, including thread expectations and state notes
Files:
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.ktstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt
**/stream-chat-android-compose/**/*.kt
📄 CodeRabbit inference engine (AGENTS.md)
**/stream-chat-android-compose/**/*.kt: Compose components should follow noun-based naming (e.g.,MessageList,ChannelListHeader)
Compose previews should use@StreamPreviewhelpers
Files:
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-compose/**/*Test.kt : Add Paparazzi snapshots for Compose UI regressions and run `verifyPaparazziDebug`
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-ui-components/**/*Test.kt : Record Shot baselines when behaviour changes in XML kit UI tests
Applied to files:
CHANGELOG.mdstream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/stream-chat-android-compose/**/*Test.kt : Add Paparazzi snapshots for Compose UI regressions and run `verifyPaparazziDebug`
Applied to files:
CHANGELOG.md
📚 Learning: 2025-12-17T15:00:07.506Z
Learnt from: CR
Repo: GetStream/stream-chat-android PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T15:00:07.506Z
Learning: Applies to **/src/test/**/*.kt : Use deterministic tests with `runTest` + virtual time for concurrency-sensitive logic (uploads, sync, message state)
Applied to files:
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.kt
🧬 Code graph analysis (1)
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.kt (1)
stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt (1)
clearData(670-678)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test compose (1)
- GitHub Check: Test compose (2)
- GitHub Check: Test compose (0)
🔇 Additional comments (8)
stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/DefaultMessageComposerRecordingContent.kt (3)
51-51: Good use ofrememberUpdatedStatefor gesture handling.Using
rememberUpdatedState(state)is the correct pattern here to capture the latest state value within thepointerInputblock, which doesn't recompose whenstatechanges. This ensurescurrentStatealways reflects the most recent recording state during the drag gesture.Also applies to: 156-156
199-202: Correct early exit when recording is locked.Breaking out of the drag loop when the state transitions to
Lockedprevents the UI from continuing to process drag events after locking, which aligns with the race condition fix.
206-217: Documented suppression for state guard is appropriate.The
@Suppress("KotlinConstantConditions")is correctly applied with an explanatory comment. The compiler can't know thatcurrentStatemay change during the suspend call toawaitDragOrCancellation. The guard prevents sending/canceling when the recording was already locked via the pending mechanism inAudioRecordingController.stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/AudioRecordingController.kt (5)
86-96: Well-documented thread-safe flags.Good use of
AtomicBooleanfor thread-safe flag operations between the main thread (wherecancelRecording/lockRecordingare called) and the IO dispatcher (wherestartAudioRecordingexecutes). The KDoc clearly explains the purpose.
184-205: Sound race condition handling instartRecording.The implementation correctly:
- Resets flags before async work to only detect concurrent calls
- Uses
getAndSet(false)for atomic read-and-clear- Checks
pendingCancelbeforependingLock, giving cancellation precedence over lockingThis ensures that quick cancel or lock requests during the IO work are honored.
222-230: Correct deferred locking mechanism.When
lockRecordingis called while state isIdle(meaningstartRecording's IO is in progress), settingpendingLockensures the lock will be applied after the IO completes. This directly addresses the race condition described in the PR objectives.
239-246: Correct deferred cancellation mechanism.Setting
pendingCancelwhen state isIdlehandles the quick-tap scenario wherecancelRecordingraces withstartRecording's IO work. This prevents the UI from getting stuck in the "hold" state as described in the PR objectives.
485-496: Correct cleanup of pending flags.Resetting both
pendingCancelandpendingLockinclearDataensures a clean slate for subsequent recording sessions, preventing stale flags from causing unexpected behavior.
|


🎯 Goal
Deferring the IO operations done in the
AudioRecordingControllerto the IO dispatcher (to resolve theStrictModeviolations) introduced some interesting race conditions, mainly:cancelRecordingin quick succession after callingstartRecordingis ignoredlockRecordingin quick succession after callingstartRecordingis ignoredThe cause for these issues is because the creation of the recording file is now deferred to the
IOdispatcher, meaning that it no longer blocks the UI thread. However, this also means that the UI could submit requests tocancelRecording/lockRecordingbefore the recording file is created, and the recorder state is not ready to handle those events. In this PR we introduce a mechanism to 'schedule' such requests, done afterstartRecordingwas called, but also before it completes.Note: This PR covers only Compose. I will create a separate one for XML.
🛠 Implementation details
cancelRecording/lockRecordingto await execution after the IO work is done.🎨 UI Changes
Case 1: Quick tap on record button (cancelRecording). Currently, the UI gets stuck in 'hold' state
short_tap_before.mp4
short_tap_after.mp4
Case 2: Impossible to call
lockRecordingimmediately afterstartRecording(currently it is ignored)To test this, you can update the
AudioRecordingActionsto callstartRecordingandlockRecordingsubsequently (use attached patch):auto_lock_after.mp4
🧪 Testing
Case 1: Tap on the recording button - it shouldn't be stuck in 'held' state
Case 2:
Provide the patch summary here
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.