[review] matrix-rtc: stale ghost m.call.member from abort-without-await teardown #68
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
dark/nether-voicebridge#68
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The presence-phase / pre-publish retire paths
abort()thepost_membership_until_joinedtask and immediately PUT the leave, instead of cancelling-then-awaiting the way the active-phase teardown deliberately does — an in-flight join PUT can be committed server-side after the leave, leaving a phantom ghost tile in Element Call until the startup sweep or ~4h expiry.F7-5 — P2 / LIVE / UNVERIFIED (needs live)
Location:
crates/matrix-rtc/src/rtc/ghost.rs:853@968da57Defect: The presence/cancel teardown paths (ghost.rs:852-857, 885-890, 898-903)
abort()the post_membership_until_joined task and immediately PUT the m.call.member leave, unlike the active-phase path (ghost.rs:934) which awaits the task first — abort() does not stop an already-dispatched HTTP PUT, so an in-flight join PUT can be serialized by the server AFTER the leave PUT.Failure scenario: A Discord user joins a bridged channel and leaves within the first seconds (or while the room's call-member PL is still being retried): the ghost is cancelled while its join PUT is in flight → server processes leave then the racing join → a stale ghost m.call.member remains, showing a phantom Discord participant in Element Call until the reaper (only while a call is active) or the next bridge restart's stale-ghost sweep clears it.
Verifier: verifier unavailable
F8-5 — P2 / LIVE / UNVERIFIED (needs live)
Location:
crates/matrix-rtc/src/rtc/ghost.rs:852@968da57Defect: The presence-phase and pre-publish retire paths
abort()the post_membership_until_joined task instead of awaiting its prompt cancel-exit (the pattern the normal teardown at line 932-934 deliberately uses), so an in-flight join PUT can land server-side after the subsequent leave PUT.Failure scenario: Discord user joins then leaves a bridged channel within the retry window while a join PUT is in flight on its own connection → the leave (sent immediately after abort on a different connection) is processed first → a stale ghost m.call.member remains; the reaper skips bridge-owned identities (matrix.rs:1138) and sweep_stale_ghosts only runs at startup, so the phantom ghost tile persists in Element Call up to the ~4 h membership expiry.
Verifier: verifier unavailable
Filed from an automated multi-agent code review of
master@968da57(8 scoped finders + adversarial verification). Every finding below was re-derived from the code by an independent verifier. Fixes for the confirmed local-verifiable defects (allocator anchor panic F1-1/F1-2/F1-3/F2-1/F2-2, capture-sink panic F2-7, config env-fallback F5-4, opt-out RMW race F5-2) already landed on branchrefactor/review-and-split.Fixed and merged to master (
15dad3c).The presence-phase and pre-publish ghost retire paths in
run_ghostnow cancel-then-await thepost_membership_until_joinedtask (mirroring the active-phase teardown) instead ofabort()-then-leave, so any in-flight join PUT settles before the leave is sent — the leave is always the last writer at them.call.memberstate key, closing the phantom-tile race. The presence→active transition'sabort()is deliberately left (join-after-join is idempotent, no stale-leave risk).Not live-fault-injected (documented gap): the race is timing-dependent and near-impossible to trigger deterministically; the ordering is correct by construction, and the no-phantom-membership check passed across the audio scenarios.
Validation: built into
integration/parked-fixes(master + #67 + #68 + #71, merged in that order — the two overlap regions inrun_ghostandon_bridge_completeverified by hand), runner-green (workspace tests + clippy-D warnings), and live-tested on staging with production up:smoke_open_close+audio_d2m_enc(NCC 1.000) +audio_d2m_unenc(0.900) +audio_m2d_enc_freshjoin(0.985) all pass, zero bridge errors, clean ghost teardown (ghost retired → pool drained → all bridges drained cleanly), and each scenario's no-phantom-membership check passed. Shipped in v0.3.0-alpha.15 and deployed to prod.