Skip to content

AsyncExecutor.close() hangs forever on tasks blocked in synchronous code #4597

Description

@neubig

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

  • close() returns within a bounded timeout even when a task is blocked in synchronous code
  • The failing test passes
  • All existing test_async_executor.py tests still pass

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:highFor bugs, affecting nearly all users and degrading performance or UX.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions