Skip to content

fix: the secure-update crash and the camera ladder (0.6.11) - #72

Merged
TheCryptoDonkey merged 3 commits into
mainfrom
fix/call-heat-and-epoch-crash
Sep 22, 2026
Merged

TheCryptoDonkey merged 3 commits into
mainfrom
fix/call-heat-and-epoch-crash

Conversation

@TheCryptoDonkey

Copy link
Copy Markdown
Member

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 main after the 0.6.10 docs commit (run 35757338881, recovery-emulator) died in PersistentGroupUiTest.a_create_and_join_web_group:

java.lang.IllegalStateException: Room publication is blocked during a secure update
    at RoomSession.announce(RoomSession.kt:403)
    at RoomSession.setTracks(RoomSession.kt:488)
    at WebRtcEngine.onLocalTracksChanged(WebRtcEngine.kt:429)

publicationAllowed is false from construction until join() reaches an active epoch, and again from blockForRekey() until applyEpoch() 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; setTracks was left on the fail-closed announce(), and the engine calls it on every local track change, including its first read of LocalMedia.tracks. Uncaught on the engine's coroutine, that ended the process.

  • RoomSession.setTracks is 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, sendInviteConfirmed and sendSignal keep failing closed.
  • The engine's jobs now run under a supervisor with a CoroutineExceptionHandler: a media job that throws is a KithMootMedia log line, not the end of the app, and does not cancel the other jobs. The caller's dispatcher is kept.
  • RoomEpochTransitionTest pins 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 facingMode only, so a browser gets its default 640x480.

  • VideoLadder fits 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. One adaptOutputFormat at the source feeds every encoder (and the segmenter sees fewer pixels too); maxBitrateBps is set per camera sender. Screen shares are untouched.
  • The rung is applied from reconcile and setAudience, 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.senders is a ConcurrentHashMap because capCamera reads it off the lock.
  • VideoLadderTest pins 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 thermalservice sampled every two minutes, and the far end's received size in chrome://webrtc-internals.

Verified

./gradlew :protocol:test :app:testDebugUnitTest :app:lintDebug :app:compileDebugAndroidTestKotlin :app:assembleDebug locally. The recovery-emulator lane 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

`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
@TheCryptoDonkey
TheCryptoDonkey merged commit b397fa9 into main Sep 22, 2026
4 checks passed
@TheCryptoDonkey
TheCryptoDonkey deleted the fix/call-heat-and-epoch-crash branch September 22, 2026 20:58
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.

1 participant