Actual Behavior
AsyncExecutor.close() hangs forever when a task is blocked in synchronous code (a worker thread that ignores cancellation). This is the same class of bug as #4546 but specifically about the close() path.
When close() is called (e.g. during LocalConversation.close() → tool executor cleanup), it calls portal_cm.__exit__(None, None, None) which takes anyio's graceful path (cancel_remaining=False) and waits for in-flight tasks to finish on their own. If a task is blocked in a C-level lock, blocking I/O, or a futex, it never finishes — close() hangs forever, holding the conversation's lifecycle lock.
Reproduce
uv run pytest tests/sdk/utils/test_cancellation_deadlock.py::test_async_executor_close_hangs_on_blocking_task -xvs
The test fails after 5s:
AssertionError: AsyncExecutor.close() hung >5s on a task blocked in a worker thread.
The task ignores cancellation (blocked in sync code), and close() does not bound the wait.
Expected Behavior
close() must never hang forever. It should cancel remaining tasks and bound the thread join with a timeout, abandoning the daemon thread if it doesn't respond.
Acceptance Criteria
Related
Actual Behavior
AsyncExecutor.close()hangs forever when a task is blocked in synchronous code (a worker thread that ignores cancellation). This is the same class of bug as #4546 but specifically about theclose()path.When
close()is called (e.g. duringLocalConversation.close()→ tool executor cleanup), it callsportal_cm.__exit__(None, None, None)which takes anyio's graceful path (cancel_remaining=False) and waits for in-flight tasks to finish on their own. If a task is blocked in a C-level lock, blocking I/O, or a futex, it never finishes —close()hangs forever, holding the conversation's lifecycle lock.Reproduce
The test fails after 5s:
Expected Behavior
close()must never hang forever. It should cancel remaining tasks and bound the thread join with a timeout, abandoning the daemon thread if it doesn't respond.Acceptance Criteria
close()returns within a bounded timeout even when a task is blocked in synchronous codetest_async_executor.pytests still passRelated
close()itself