mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-10 02:43:02 +02:00
fix(router): mark companion delivery only after a bridge receives the packet
The PATH and protocol-response paths recorded the companion dedupe unconditionally after the bridge fan-out, so a delivery where every bridge raised was still suppressed for the full dedupe TTL — the client lost the packet even though later mesh copies arrived. Have the fan-out report delivery (at least one bridge completed without raising) next to authentication, and mark the dedupe only on delivery, which is the behaviour the marking helper's contract already documented. One healthy bridge still counts as delivered, so duplicate suppression of repeated mesh copies is unchanged.
This commit is contained in:
@@ -1326,6 +1326,63 @@ class TestInjectedTxRawEcho(unittest.IsolatedAsyncioTestCase):
|
||||
class TestCompanionDeliveryFailureHandling(unittest.IsolatedAsyncioTestCase):
|
||||
"""Dedupe marking and candidate-loop behaviour when companion bridges raise."""
|
||||
|
||||
async def test_path_all_bridges_raising_is_retried_on_next_copy(self):
|
||||
"""A PATH delivery where every bridge raised must not be marked
|
||||
delivered; the next mesh copy gets another delivery attempt."""
|
||||
daemon = _make_daemon()
|
||||
bridge = _make_bridge()
|
||||
bridge.process_received_packet = AsyncMock(side_effect=RuntimeError("bridge down"))
|
||||
daemon.companion_bridges = {0x01: bridge}
|
||||
router = PacketRouter(daemon)
|
||||
pkt = _make_packet(PathHandler.payload_type())
|
||||
pkt.payload = bytes([0x01, 0xAA])
|
||||
|
||||
await router._route_packet(pkt)
|
||||
self.assertEqual(bridge.process_received_packet.await_count, 1)
|
||||
|
||||
# Bridge recovers; the second copy must be delivered, not TTL-suppressed.
|
||||
recovered = AsyncMock(return_value=HandlerResult.not_for_us())
|
||||
bridge.process_received_packet = recovered
|
||||
await router._route_packet(pkt)
|
||||
recovered.assert_awaited_once()
|
||||
|
||||
async def test_path_partial_bridge_failure_still_marks_delivered(self):
|
||||
"""One healthy bridge is a delivery: the duplicate copy stays suppressed."""
|
||||
daemon = _make_daemon()
|
||||
raising = _make_bridge()
|
||||
raising.process_received_packet = AsyncMock(side_effect=RuntimeError("boom"))
|
||||
healthy = _make_bridge()
|
||||
healthy.process_received_packet = AsyncMock(return_value=HandlerResult.not_for_us())
|
||||
daemon.companion_bridges = {0x01: raising, 0x02: healthy}
|
||||
router = PacketRouter(daemon)
|
||||
pkt = _make_packet(PathHandler.payload_type())
|
||||
# Dest not in bridges: anon path-return, delivered to all bridges.
|
||||
pkt.payload = bytes([0xEE, 0xAA])
|
||||
|
||||
await router._route_packet(pkt)
|
||||
await router._route_packet(pkt)
|
||||
|
||||
healthy.process_received_packet.assert_awaited_once()
|
||||
raising.process_received_packet.assert_awaited_once()
|
||||
|
||||
async def test_protocol_response_all_bridges_raising_is_retried_on_next_copy(self):
|
||||
daemon = _make_daemon()
|
||||
bridge = _make_bridge()
|
||||
bridge.process_received_packet = AsyncMock(side_effect=RuntimeError("bridge down"))
|
||||
daemon.companion_bridges = {0x01: bridge}
|
||||
router = PacketRouter(daemon)
|
||||
pkt = _make_packet(ProtocolResponseHandler.payload_type())
|
||||
pkt.header = ROUTE_TYPE_FLOOD # not a final hop: dedupe decides delivery
|
||||
|
||||
with patch("repeater.packet_router.PathHandler.payload_type", return_value=0x55):
|
||||
await router._route_packet(pkt)
|
||||
self.assertEqual(bridge.process_received_packet.await_count, 1)
|
||||
|
||||
recovered = AsyncMock(return_value=HandlerResult.not_for_us())
|
||||
bridge.process_received_packet = recovered
|
||||
await router._route_packet(pkt)
|
||||
recovered.assert_awaited_once()
|
||||
|
||||
async def test_login_candidate_bridge_error_still_offers_local_identity(self):
|
||||
"""A raising companion bridge must not abort the candidate loop: the
|
||||
hash-colliding room-server/repeater identity still gets the packet."""
|
||||
|
||||
Reference in New Issue
Block a user