diff --git a/crates/chanora_protocol/src/adapter.rs b/crates/chanora_protocol/src/adapter.rs index 6bedd34..e8319f6 100644 --- a/crates/chanora_protocol/src/adapter.rs +++ b/crates/chanora_protocol/src/adapter.rs @@ -500,24 +500,24 @@ async fn connection_task( // 2b. Sweep stale pending_moves whose deadline has passed. // The server should always reply within ~1 s; 3 s is a - // generous ceiling. Expired entries get an Ok() so the - // caller's snapshot-confirmation polling still has a chance - // to detect success (fall back to the previous optimistic - // behaviour rather than blocking the user with a fake - // ServerRejected). + // generous ceiling. Expired entries fall back to Ok() so + // the caller's snapshot-confirmation polling still has a + // chance to detect success — better than a fake + // ServerRejected for legacy servers that never reply to + // move requests. if !pending_moves.is_empty() { let now = std::time::Instant::now(); - pending_moves.retain(|_, (reply, deadline)| { - if now >= *deadline { - // Cannot move `reply` out of `&mut` cleanly here - // without an intermediate take(); use a sentinel - // sender so retain's signature works. - let _ = std::mem::replace(reply, oneshot::channel().0).send(Ok(())); - false - } else { - true + let expired: Vec = pending_moves + .iter() + .filter_map(|(handle, (_, deadline))| { + if now >= *deadline { Some(*handle) } else { None } + }) + .collect(); + for handle in expired { + if let Some((reply, _)) = pending_moves.remove(&handle) { + let _ = reply.send(Ok(())); } - }); + } } // 3. Service at most one control request (non-blocking).