mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-07 17:33:16 +02:00
fix(router): keep offering local candidates when a companion bridge raises
_consume_via_local_candidates awaited the targeted companion bridge with no exception guard, so a raising bridge aborted the whole candidate loop: the hash-colliding room-server or repeater identity registered at the same one-byte dest hash was never offered the packet, and an unclaimed packet never fell through to the forwarding engine. Wrap the bridge call with the same log-and-continue handling the fan-out path already uses.
This commit is contained in:
@@ -316,9 +316,15 @@ class PacketRouter:
|
||||
|
||||
consumed = False
|
||||
if has_companion:
|
||||
bridge_result = await companion_bridges[dest_hash].process_received_packet(packet)
|
||||
if bridge_result.authenticated:
|
||||
consumed = True
|
||||
# A raising bridge must not abort the candidate loop: the colliding
|
||||
# room-server / repeater identity below still gets offered the packet.
|
||||
try:
|
||||
bridge_result = await companion_bridges[dest_hash].process_received_packet(packet)
|
||||
except Exception as e:
|
||||
logger.debug("Companion bridge candidate error: %s", e)
|
||||
else:
|
||||
if bridge_result.authenticated:
|
||||
consumed = True
|
||||
# Offer to the room-server / repeater identity when it shares the hash
|
||||
# (collision) or when no local companion claims it at all (normal
|
||||
# server-owned + remote-forward handling).
|
||||
|
||||
@@ -1321,3 +1321,49 @@ class TestInjectedTxRawEcho(unittest.IsolatedAsyncioTestCase):
|
||||
ok = await router.inject_packet(_make_packet())
|
||||
|
||||
self.assertTrue(ok)
|
||||
|
||||
|
||||
class TestCompanionDeliveryFailureHandling(unittest.IsolatedAsyncioTestCase):
|
||||
"""Dedupe marking and candidate-loop behaviour when companion bridges raise."""
|
||||
|
||||
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."""
|
||||
daemon = _make_daemon()
|
||||
bridge = _make_bridge()
|
||||
bridge.process_received_packet = AsyncMock(side_effect=RuntimeError("bridge down"))
|
||||
daemon.companion_bridges = {0xF5: bridge}
|
||||
daemon.login_helper = MagicMock()
|
||||
daemon.login_helper.handlers = {0xF5: MagicMock()}
|
||||
daemon.login_helper.process_login_packet = AsyncMock(return_value=True)
|
||||
daemon.repeater_handler = AsyncMock()
|
||||
daemon.repeater_handler.storage = MagicMock()
|
||||
daemon.repeater_handler.record_packet_only = MagicMock()
|
||||
router = PacketRouter(daemon)
|
||||
pkt = _make_packet(LoginServerHandler.payload_type())
|
||||
pkt.payload = bytes([0xF5, 0x01])
|
||||
|
||||
await router._route_packet(pkt)
|
||||
|
||||
bridge.process_received_packet.assert_awaited_once()
|
||||
daemon.login_helper.process_login_packet.assert_awaited_once()
|
||||
# The colliding identity consumed it, so the engine must not re-forward.
|
||||
daemon.repeater_handler.assert_not_awaited()
|
||||
|
||||
async def test_text_candidate_bridge_error_leaves_packet_for_engine(self):
|
||||
"""Bridge raises and no local identity claims the text: the packet must
|
||||
still reach the forwarding engine instead of dying with the exception."""
|
||||
daemon = _make_daemon()
|
||||
bridge = _make_bridge()
|
||||
bridge.process_received_packet = AsyncMock(side_effect=RuntimeError("bridge down"))
|
||||
daemon.companion_bridges = {0xF5: bridge}
|
||||
daemon.text_helper = MagicMock()
|
||||
daemon.text_helper.handlers = {}
|
||||
daemon.text_helper.process_text_packet = AsyncMock(return_value=False)
|
||||
router = PacketRouter(daemon)
|
||||
pkt = _make_packet(TextMessageHandler.payload_type())
|
||||
pkt.payload = bytes([0xF5, 0x01])
|
||||
|
||||
await router._route_packet(pkt)
|
||||
|
||||
daemon.repeater_handler.assert_awaited_once()
|
||||
|
||||
Reference in New Issue
Block a user