fix(call-to-play): compact expired snapshots

Expired call histories were removed on the next store insertion, so an otherwise
idle peer could continue carrying stale payload through handshakes after the
five-minute UI retention ended.

Run the same inactive-call compaction before local and handshake snapshots. This
makes the expiry boundary exact without trimming any event from an active call.

Test Plan:
- `just fmt` -- passed
- `just clippy` -- passed
- `just test` -- passed, 147 peer tests
- `git diff --cached --check` -- passed
This commit is contained in:
2026-07-21 22:59:48 +02:00
parent 383e8f4855
commit d58307c328
3 changed files with 11 additions and 8 deletions
+7 -4
View File
@@ -30,7 +30,12 @@ pub(crate) struct CallToPlayStore {
} }
impl CallToPlayStore { impl CallToPlayStore {
pub(crate) fn snapshot(&self) -> Vec<CallToPlayEvent> { pub(crate) fn snapshot(&mut self) -> Vec<CallToPlayEvent> {
self.snapshot_at(now_ms())
}
fn snapshot_at(&mut self, now: i64) -> Vec<CallToPlayEvent> {
self.compact_inactive_calls(now);
self.events.clone() self.events.clone()
} }
@@ -434,8 +439,6 @@ mod tests {
.insert(create_event("create")) .insert(create_event("create"))
.expect("active call should fit"); .expect("active call should fit");
store.compact_inactive_calls(TEST_NOW + 5 * 60_000 + 60_001); assert!(store.snapshot_at(TEST_NOW + 5 * 60_000 + 60_001).is_empty());
assert!(store.snapshot().is_empty());
} }
} }
+1 -1
View File
@@ -510,7 +510,7 @@ async fn handle_peer_commands(
let _ = reply.send(result); let _ = reply.send(result);
} }
PeerCommand::GetCallToPlayEvents { reply } => { PeerCommand::GetCallToPlayEvents { reply } => {
let events = ctx.call_to_play.read().await.snapshot(); let events = ctx.call_to_play.write().await.snapshot();
events::send(tx_notify_ui, PeerEvent::CallToPlayEvents(events.clone())); events::send(tx_notify_ui, PeerEvent::CallToPlayEvents(events.clone()));
if let Some(reply) = reply { if let Some(reply) = reply {
let _ = reply.send(events); let _ = reply.send(events);
@@ -67,7 +67,7 @@ pub(super) async fn build_hello_ack(ctx: &PeerCtx) -> eyre::Result<HelloAck> {
let library_guard = ctx.local_library.read().await; let library_guard = ctx.local_library.read().await;
build_library_snapshot(&library_guard) build_library_snapshot(&library_guard)
}; };
let call_to_play_events = ctx.call_to_play.read().await.snapshot(); let call_to_play_events = ctx.call_to_play.write().await.snapshot();
Ok(HelloAck { Ok(HelloAck {
peer_id: ctx.peer_id.as_ref().clone(), peer_id: ctx.peer_id.as_ref().clone(),
proto_ver: PROTOCOL_VERSION, proto_ver: PROTOCOL_VERSION,
@@ -84,7 +84,7 @@ async fn build_hello_from_state(ctx: &HandshakeCtx) -> eyre::Result<Hello> {
let library_guard = ctx.local_library.read().await; let library_guard = ctx.local_library.read().await;
build_library_snapshot(&library_guard) build_library_snapshot(&library_guard)
}; };
let call_to_play_events = ctx.call_to_play.read().await.snapshot(); let call_to_play_events = ctx.call_to_play.write().await.snapshot();
Ok(Hello { Ok(Hello {
peer_id: ctx.peer_id.as_ref().clone(), peer_id: ctx.peer_id.as_ref().clone(),
proto_ver: PROTOCOL_VERSION, proto_ver: PROTOCOL_VERSION,
@@ -538,7 +538,7 @@ mod tests {
.expect("current protocol hello should be accepted"); .expect("current protocol hello should be accepted");
assert_eq!( assert_eq!(
ctx.call_to_play.read().await.snapshot(), ctx.call_to_play.write().await.snapshot(),
[call_to_play_event()] [call_to_play_event()]
); );
assert!(matches!( assert!(matches!(