fix(android): clean up stale disconnected session in session_add

On mobile, sessionId is a constant per-app-lifetime UUID
(_constSessionId at flutter/lib/models/model.dart:57). When a prior
connection wedges silently after Android Doze pauses the io_loop
long enough that no error path fires and session_close is never
called, the entry stays in the SESSIONS map. The next user tap
calls sessionAddSync which bails with "same session id is found",
but the Flutter side ignores that error (the return is bound to a
`// ignore: unused_local_variable` at model.dart:3723) and calls
sessionStart anyway. session_start_ then finds the stale handler
with its old event_stream still attached, the is_connected
short-circuit prevents a new io_loop from spawning, and the user
sees "Connecting..." forever with no error surfaced.

Detect the stale case in session_add via the existing
ConnectionRoundState::Disconnected state (set by io_loop on exit
in src/client/io_loop.rs:341) and clean up the prior session
before bailing. This is safe — when state is Disconnected, the
prior io_loop has provably exited.

Fixes #15060

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Muad'Dib 2026-05-16 17:20:43 +02:00
parent 472c4fc03a
commit 1b728c4ee6
2 changed files with 18 additions and 3 deletions

View file

@ -1324,11 +1324,22 @@ pub fn session_add(
// to-do: check the same id session.
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
if session.lc.read().unwrap().conn_type != conn_type {
// Mobile reuses one constant session_id for the entire app lifetime
// (_constSessionId in flutter/lib/models/model.dart). When a prior
// connection wedges silently — typically after Android Doze pauses the
// io_loop long enough that no error path fires and session_close is
// never called — the entry stays in SESSIONS. Detect that here and
// clean it up so the new session_add can succeed instead of bailing,
// which would leave the user stuck on a "Connecting..." spinner with
// no error surfaced (the Flutter side ignores sessionAddSync's return).
if session.connection_round_state.lock().unwrap().is_disconnected() {
sessions::remove_session_by_session_id(&session_id);
} else if session.lc.read().unwrap().conn_type != conn_type {
bail!("same session id is found with different conn type?");
} else {
// The same session is added before?
bail!("same session id is found");
}
// The same session is added before?
bail!("same session id is found");
}
LocalConfig::set_remote_id(&id);

View file

@ -128,6 +128,10 @@ impl ConnectionRoundState {
true
}
}
pub fn is_disconnected(&self) -> bool {
matches!(self.state, ConnectionState::Disconnected)
}
}
impl Default for ConnectionRoundState {