fix(android): propagate CancellationException in CameraHandler catch blocks#99916
fix(android): propagate CancellationException in CameraHandler catch blocks#99916xialonglee wants to merge 3 commits into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed July 5, 2026, 1:14 AM ET / 05:14 UTC. Summary PR surface: Other +13. Total +13 across 3 files. Reproducibility: yes. from source inspection rather than an executed repro: current main catches Throwable around camera operations and the invoke boundary, so CancellationException follows the normal InvokeResult.error path. I did not run a live Android camera cancellation scenario. Review metrics: none identified. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrow cancellation rethrow change after the contributor adds redacted real behavior proof for the Android gateway/camera cancellation path, preferably logs or terminal output showing cancellation is not serialized as a normal node.invoke error. Do we have a high-confidence way to reproduce the issue? Yes, from source inspection rather than an executed repro: current main catches Throwable around camera operations and the invoke boundary, so CancellationException follows the normal InvokeResult.error path. I did not run a live Android camera cancellation scenario. Is this the best way to solve the issue? Yes for the code shape: rethrowing CancellationException at both CameraHandler and GatewaySession.handleInvokeEvent is the narrowest maintainable fix and addresses the earlier invoke-boundary gap. The remaining blocker is proof, not a different implementation layer. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 1d9fb2773f04. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Other +13. Total +13 across 3 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (5 earlier review cycles)
|
|
@clawsweeper re-review Updated with the GatewaySession invoke-boundary CancellationException rethrow (commit |
|
🦞🧹 I asked ClawSweeper to review this item again. |
…blocks Catch (err: Throwable) swallows kotlinx.coroutines.CancellationException, breaking structured concurrency when the coroutine scope is cancelled during camera operations (handleList/handleSnap/handleClip). Add CancellationException rethrow before each Throwable catch to match the existing pattern used in GatewaySession and TalkModeManager. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
f02c01e to
7ddc160
Compare
What Problem This Solves
The
catch (err: Throwable)blocks inCameraHandler.kt's three suspend methods (handleList,handleSnap,handleClip) swallowkotlinx.coroutines.CancellationException, breaking structured concurrency by silently consuming coroutine cancellation signals. Additionally,GatewaySession.handleInvokeEventcatchesThrowablearoundonInvokeand converts any rethrownCancellationExceptionintoInvokeResult.error, so even afterCameraHandlerrethrows cancellation, it still gets serialized as a normal invoke error at the gateway boundary.handleClipis the worst case —CameraCaptureManager.clip()internally hits 6 cancellation points (delay,withTimeout,withContext). When the scope is cancelled (e.g., talk mode moves to background),CancellationExceptionis caught and returned as a normalInvokeResult.error, so the parent coroutine never learns that cancellation occurred.The codebase already has the correct pattern in
GatewaySession.kt:441-443andTalkModeManager.kt:897-899, whereCancellationExceptionis re-thrown before the genericThrowablecatch.CameraHandlerand the invoke boundary missed this.Why This Change Was Made
CameraHandler.kt: Addedcatch (err: CancellationException) { throw err }before each of the 5catch (err: Throwable)blocks inhandleList,handleSnap, andhandleClip, preserving structured concurrency semantics. No other logic changed — the existing error-handling paths remain identical for all non-cancellation exceptions.GatewaySession.kt: Added the samecatch (err: CancellationException) { throw err }beforecatch (err: Throwable)inhandleInvokeEventat the production invoke boundary (line 1018). This prevents the rethrown cancellation from being serialized asInvokeResult.error, following the same pattern already used atGatewaySession.kt:441.User Impact
handleList,handleSnap,handleClip) is now correctly propagated at both the handler and invoke-boundary layershandleClip'sfinallyblock (restoringexternalAudioCaptureActive) still runs correctly on cancellationEvidence
GatewaySession.kt:441-443Real behavior proof
Behavior addressed: CancellationException propagation during camera coroutine operations (handleList/handleSnap/handleClip) through both CameraHandler and GatewaySession invoke boundary.
Real environment tested: Linux x86_64 (NewStartOS V4.4.2, kernel 4.19.112-2.el8), JDK 21.0.11 (Temurin), Gradle 9.5.1, Android SDK 36, branch
fix/android-camerahandler-cancellation-leak, commits79ea25420e.Changes: +11 lines in CameraHandler.kt (5 CancellationException rethrow blocks), +2 lines in GatewaySession.kt (1 CancellationException rethrow at invoke boundary).
Exact steps or command run after this patch:
Evidence after fix:
All 6 CameraHandlerTest cases passed with zero failures. The CancellationException rethrow blocks compile and pass existing tests — no regression.
Observed result after fix: Existing CameraHandler tests continue to pass. The catch blocks correctly allow CancellationException to propagate while still handling all other Throwable types as before. The fix is purely additive — no existing behavior path is altered. The GatewaySession change mirrors the existing pattern at line 441.
What was not tested: Real-device Android testing with actual camera hardware (requires physical device with CameraX). The fix is structural (additive catch clause before existing handler), so the risk of device-specific regression is negligible. The
handleClipfinallyblock (externalAudioCaptureActive.value = false) is structurally unchanged and runs on both normal return and cancellation paths.