Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions openhands-sdk/openhands/sdk/conversation/fifo_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class FIFOLock:
- FIFO ordering: Threads get lock in request order
- Context manager support: Use with 'with' statement
- Thread-safe: Safe for concurrent access

Note: reentrancy is keyed by OS thread id, not by asyncio task, so the
lock does NOT serialize concurrent coroutines on the same event-loop
thread - each re-enters it. Only hold it across an ``await`` when no
other task on that thread can acquire it.
"""

_mutex: threading.Lock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,13 @@ async def arun(self) -> None:
ConversationExecutionStatus.RUNNING
)

# The state lock is held across this await. This is safe
# only because every other state mutator is dispatched via
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Important: This says every other state mutator is dispatched via run_in_executor, but native async Agent.astep() and ACPAgent.astep() both run state/on-event mutations on the event-loop thread after async awaits. That makes the documented invariant too broad and contradictory. Please narrow this to the actual rule: no unrelated/concurrent same-loop state mutator may run while arun() holds the lock; intentional mutations inside the awaited astep() are part of this critical section.

# run_in_executor onto a worker thread: FIFOLock is thread-
# (not task-) reentrant, so a state-mutating coroutine
# awaited on this event-loop thread would silently re-enter
# the lock and corrupt history. Do not await state mutations
# on the event-loop thread.
await self.agent.astep(
self,
on_event=self._on_event,
Expand Down
Loading