fix: the secure-update crash and the camera ladder (0.6.11) - #72
Merged
Merged
Conversation
`RoomSession.setTracks` called the fail-closed `announce()`, and the engine calls it on every local track change, including its first read of the tracks. Between epochs the gate is shut, so a camera toggle, a share, or a rejoin during a link rotation threw "Room publication is blocked during a secure update" on the engine's coroutine and ended the process; CI's recovery-emulator lane hit it on main after 0.6.10. The set is now kept while the gate is shut, like a heartbeat, and goes out in the successor epoch's first announcement and every heartbeat after. Chat, invites and signals keep failing closed. Two tests pin it: nothing is published while a pending update blocks traffic, and a change during a committed rekey is announced under the successor with the tracks in it. Claude-Session: https://claude.ai/code/session_01TgQJ1LHZjQG6wiKVfZcXPs
Every remote device on a call has its own peer connection and its own encoder, and each encoded the full 1280x720 at 30 with no bitrate ceiling. That is the heat on a group call, and the uplink. `VideoLadder` fits the one camera source to the number of devices it goes to (one: 720p at 30, 1.2 Mbps; two or three: 540p at 24, 800 kbps; four or more: 360p at 15, 500 kbps) with a single `adaptOutputFormat` at the source and a `maxBitrateBps` per camera sender. Screen shares are untouched. The rung is applied from `reconcile` and `setAudience` outside the engine lock; a sender added later is capped as it is added, on the add-a-track path and on the profile-2 slots. No wire change. The engine's jobs also now run under a supervisor with an exception handler, so a media job that throws is a logged failure and not the end of the app. Claude-Session: https://claude.ai/code/session_01TgQJ1LHZjQG6wiKVfZcXPs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two Android call fixes owed after 0.6.10, released together as 0.6.11 (34). No wire change: web and desktop peers need nothing.
A local track change during a secure update killed the process
CI on
mainafter the 0.6.10 docs commit (run 35757338881,recovery-emulator) died inPersistentGroupUiTest.a_create_and_join_web_group:publicationAllowedis false from construction untiljoin()reaches an active epoch, and again fromblockForRekey()untilapplyEpoch()reopens the gate, so every link rotation, removal and rejoin passes through that window. The 15 September fix made heartbeats and delayed replies drop silently there;setTrackswas left on the fail-closedannounce(), and the engine calls it on every local track change, including its first read ofLocalMedia.tracks. Uncaught on the engine's coroutine, that ended the process.RoomSession.setTracksis now best-effort like a heartbeat: the set is kept while the gate is shut and goes out in the successor epoch's first announcement, then on every heartbeat.sendChat,sendChatConfirmed,sendInviteConfirmedandsendSignalkeep failing closed.CoroutineExceptionHandler: a media job that throws is aKithMootMedialog line, not the end of the app, and does not cancel the other jobs. The caller's dispatcher is kept.RoomEpochTransitionTestpins both: a track change while a pending update blocks traffic publishes nothing and does not throw; a track change during a committed rekey is announced under the successor with the tracks in it.The phone ran hot on video calls
Every remote device on a call has its own peer connection and its own encoder, and each encoded the full 1280x720@30 with no bitrate, resolution or framerate cap (libwebrtc's own ceiling is about 2.5 Mbps per sender). The web client asks its camera for
facingModeonly, so a browser gets its default 640x480.VideoLadderfits the one camera source to the number of devices it goes to: one peer 1280x720@30 and 1.2 Mbps; two or three 960x540@24 and 800 kbps; four or more 640x360@15 and 500 kbps. OneadaptOutputFormatat the source feeds every encoder (and the segmenter sees fewer pixels too);maxBitrateBpsis set per camera sender. Screen shares are untouched.reconcileandsetAudience, outside the engine lock (both native calls wait on libwebrtc's threads). A sender added later is capped as it is added, on the add-a-track path (ManagedLink.addLocalTrack) and on the profile-2 slots (WebRtcPeerConnection.setSlotTrack).ManagedLink.sendersis aConcurrentHashMapbecausecapCamerareads it off the lock.VideoLadderTestpins the steps and that only camera track ids are capped.The steps are a first cut and the owner's to tune. Not measured on a handset yet: the check is a ten-minute three-way video call on the Pixel with
dumpsys thermalservicesampled every two minutes, and the far end's received size inchrome://webrtc-internals.Verified
./gradlew :protocol:test :app:testDebugUnitTest :app:lintDebug :app:compileDebugAndroidTestKotlin :app:assembleDebuglocally. Therecovery-emulatorlane is the one that reproduces the crash; it also fails for a Compose picker timing miss and for the emulator going offline, both unrelated, so rerun before reading anything into a red.Write-up with evidence, mechanism and acceptance: kithmoot-private
docs/plans/2026-09-22-android-heat-and-epoch-crash.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01TgQJ1LHZjQG6wiKVfZcXPs