diff --git a/openhands-agent-server/openhands/agent_server/conversation_service.py b/openhands-agent-server/openhands/agent_server/conversation_service.py index 4f9abac659..f181366680 100644 --- a/openhands-agent-server/openhands/agent_server/conversation_service.py +++ b/openhands-agent-server/openhands/agent_server/conversation_service.py @@ -1100,6 +1100,11 @@ async def _get_or_load_event_service( event_services = self._event_services if event_services is None: raise ValueError("inactive_service") + event_service = event_services.get(conversation_id) + if event_service is not None and event_service.is_open(): + # Cached runtimes do not need lifecycle serialization or disk I/O. + event_service.touch() + return event_service if ( conversation_id not in event_services and conversation_id not in self._conversation_records diff --git a/tests/agent_server/test_conversation_service.py b/tests/agent_server/test_conversation_service.py index e867187cf8..ad3df26631 100644 --- a/tests/agent_server/test_conversation_service.py +++ b/tests/agent_server/test_conversation_service.py @@ -85,6 +85,26 @@ def sample_stored_conversation(): ) +@pytest.mark.asyncio +async def test_cached_event_service_bypasses_lifecycle_lock(tmp_path): + service = ConversationService(conversations_dir=tmp_path / "conversations") + conversation_id = uuid4() + event_service = MagicMock(spec=EventService) + event_service.is_open.return_value = True + service._event_services = {conversation_id: event_service} + + await service._lifecycle_lock.acquire() + try: + result = await asyncio.wait_for( + service.get_event_service(conversation_id), timeout=0.1 + ) + finally: + service._lifecycle_lock.release() + + assert result is event_service + event_service.touch.assert_called_once_with() + + @pytest.mark.asyncio async def test_meta_json_has_no_agent_and_reload_uses_base_state(tmp_path): """End-to-end single-source-of-truth guarantee.