Skip to content

Commit 7ae8680

Browse files
authored
Merge branch 'main' into unify-skills-modules
2 parents d43cebb + c78c3c2 commit 7ae8680

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

openhands-agent-server/openhands/agent_server/event_service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@ async def close(self):
656656
await self._pub_sub.close()
657657
if self._conversation:
658658
loop = asyncio.get_running_loop()
659-
loop.run_in_executor(None, self._conversation.close)
659+
await loop.run_in_executor(None, self._conversation.close)
660+
self._conversation = None
660661

661662
async def generate_title(
662663
self, llm: "LLM | None" = None, max_length: int = 50

tests/agent_server/test_event_service.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,3 +1725,49 @@ def hold_lock_like_run_loop():
17251725
f"for {hold_seconds}s. The read path is blocked by the write lock "
17261726
f"(see HANG_REPRO.md)."
17271727
)
1728+
1729+
1730+
class TestEventServiceClose:
1731+
"""Tests for EventService.close() awaiting conversation teardown."""
1732+
1733+
@pytest.mark.asyncio
1734+
async def test_close_awaits_conversation_close(self, event_service):
1735+
"""close() must await conversation.close(), not fire-and-forget."""
1736+
conversation = MagicMock(spec=Conversation)
1737+
event_service._conversation = conversation
1738+
1739+
closed = asyncio.Event()
1740+
1741+
def slow_close():
1742+
# Simulate non-trivial teardown work
1743+
time.sleep(0.05)
1744+
closed.set()
1745+
1746+
conversation.close = slow_close
1747+
1748+
await event_service.close()
1749+
1750+
assert closed.is_set(), (
1751+
"EventService.close() returned before conversation.close() finished"
1752+
)
1753+
1754+
@pytest.mark.asyncio
1755+
async def test_close_clears_conversation_reference(self, event_service):
1756+
"""close() must set _conversation to None after closing."""
1757+
conversation = MagicMock()
1758+
event_service._conversation = conversation
1759+
1760+
await event_service.close()
1761+
1762+
assert event_service._conversation is None
1763+
1764+
@pytest.mark.asyncio
1765+
async def test_close_is_idempotent(self, event_service):
1766+
"""Calling close() twice must not raise."""
1767+
conversation = MagicMock()
1768+
event_service._conversation = conversation
1769+
1770+
await event_service.close()
1771+
await event_service.close() # second call — _conversation is already None
1772+
1773+
conversation.close.assert_called_once()

0 commit comments

Comments
 (0)