From 117b92cb2456b3ecb3f9a7a428baf683b5e66f89 Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 22 Jul 2026 22:03:16 -0500 Subject: [PATCH] Relay: snapshot by_host in the PEER_UP/DOWN fanouts (end-of-round crash) When every pod disconnects at once (normal end of a multiplayer round), a failed send inside the PEER_DOWN fanout recursively drops that peer and mutates by_host mid-iteration -> RuntimeError -> THE RELAY DIES, taking the round reset and the matchlog uploads with it (live crash, first 3-player internet round). Iterate over snapshots. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/btconsole.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/btconsole.py b/tools/btconsole.py index 711c81a..17ae82f 100644 --- a/tools/btconsole.py +++ b/tools/btconsole.py @@ -923,7 +923,8 @@ class Relay: for other_id in sorted(self.by_host): if other_id != host_id: self._send_control(conn, ROUTE_PEER_UP, other_id) - for other in self.by_host.values(): + for other in list(self.by_host.values()): # snapshot: a failed + # send drops that peer INSIDE the loop (mutates by_host) if other is not conn: self._send_control(other, ROUTE_PEER_UP, host_id) return @@ -990,7 +991,8 @@ class Relay: if conn.host_id is not None and self.by_host.get(conn.host_id) is conn: del self.by_host[conn.host_id] self.udp_endpoint.pop(conn.host_id, None) - for other in self.by_host.values(): + for other in list(self.by_host.values()): # snapshot: a failed + # send drops that peer INSIDE the loop (mutates by_host) self._send_control(other, ROUTE_PEER_DOWN, conn.host_id) self._maybe_reset_round()