diff --git a/services/xr-media-hub/xr_media_hub/ipc/_hub.py b/services/xr-media-hub/xr_media_hub/ipc/_hub.py index 4c927218..470288d5 100644 --- a/services/xr-media-hub/xr_media_hub/ipc/_hub.py +++ b/services/xr-media-hub/xr_media_hub/ipc/_hub.py @@ -461,7 +461,7 @@ async def _replay_roster(self) -> None: def _handle_registration(self, reg: ConnectorRegistration) -> None: if reg.connector_id in self._ring_registry: logger.warning("Connector {} re-registered — replacing ring buffer", reg.connector_id) - old_ring = self._ring_registry[reg.connector_id] + old_ring = self._ring_registry.pop(reg.connector_id) # Drop any frames still held in the old ring BEFORE closing it. # A live SlotView keeps a sliced memoryview exported into the ring's # mmap; closing with that outstanding makes ShmRingBuffer.close()'s diff --git a/tests/test_participant_events.py b/tests/test_participant_events.py index fd298f83..aa461e14 100644 --- a/tests/test_participant_events.py +++ b/tests/test_participant_events.py @@ -11,8 +11,9 @@ import asyncio import pytest +import xr_media_hub.ipc._hub as hub_module -from xr_ai_hub import ParticipantEvent, PixelFormat +from xr_ai_hub import ConnectorRegistration, ParticipantEvent, PixelFormat pytestmark = pytest.mark.asyncio @@ -160,3 +161,30 @@ async def test_connector_reregistration_releases_held_slots(hub, make_connector, ) await settle() assert ("alice", "cam") in hub._latest_slots + + +async def test_connector_reregistration_open_failure_drops_stale_ring(monkeypatch): + """Regression for #209: a failed SHM reopen during connector + re-registration must not leave the just-closed old ring in the registry.""" + + class CloseTrackingRing: + def __init__(self) -> None: + self.closed = False + + def close(self) -> None: + self.closed = True + + def fail_open(*, name: str, create: bool): + raise RuntimeError(f"cannot open {name} create={create}") + + hub = hub_module.HubEndpoint.__new__(hub_module.HubEndpoint) + old_ring = CloseTrackingRing() + hub._latest_slots = {} + hub._ring_registry = {} + hub._ring_registry["conn"] = old_ring + monkeypatch.setattr(hub_module, "ShmRingBuffer", fail_open) + + hub._handle_registration(ConnectorRegistration(connector_id="conn", shm_name="missing")) + + assert old_ring.closed is True + assert "conn" not in hub._ring_registry