Skip to content

fix: abandon uncancellable handler instead of holding lock - #32

Closed
neubig wants to merge 3 commits into
browser-use:mainfrom
neubig:fix/abandon-uncancellable-handler
Closed

neubig wants to merge 3 commits into
browser-use:mainfrom
neubig:fix/abandon-uncancellable-handler

Conversation

@neubig

@neubig neubig commented Aug 23, 2026 •

Copy link
Copy Markdown

Why

When a handler is blocked in synchronous code (via run_in_executor or a C-level call), CancelledError can't be delivered — it only fires at the next await point, which the blocking thread never reaches. The finally block in execute_handler waited only 0.1s then silently abandoned the task, but the global lock was held during that wait.

What this changes

  • Increased the cleanup grace period from 0.1s to 1.0s (gives cancellable handlers time to respond)
  • If the task still isn't done after 1s, logs a warning: "handler could not be cancelled (still running after 1s). It is likely blocked in synchronous code and will be abandoned as a zombie thread."
  • Returns immediately without further waiting — the global lock is released so other events can proceed

Limitation

The zombie thread is still alive (Python can't kill threads blocked in C-level code). This PR stops the bus from hanging; the companion PR (#32) addresses the root cause by recommending anyio.to_thread.run_sync which supports proper cancellation.

Fixes #31.


Summary by cubic

Abandons uncancellable handler tasks without holding the global lock, preventing bus hangs when a handler blocks in synchronous code. Old behavior: waited 0.1s under the lock and silently abandoned; new behavior: wait up to 1s, emit a warning if still running, then release the lock immediately.

  • Cleanup grace period is now 1.0s (was 0.1s) to let cancellable handlers exit.
  • If the task is still running after 1s, log a warning and abandon it; the global lock is released so other events proceed.
  • Adds tests/test_handler_timeout_zombie_thread.py to verify the bus does not hang on a blocking handler.
  • Known limitation: the blocking thread may persist; prefer anyio.to_thread.run_sync over run_in_executor for cancellable sync work.

Written for commit 21bdec0. Summary will update on new commits.

Review in cubic

neubig added 3 commits August 23, 2026 18:13
Failing test demonstrating that execute_handler's timeout fires but doesn't free threads blocked in synchronous code (run_in_executor). CancelledError can't be delivered to a blocking sync call, and the finally block waits only 0.1s before abandoning the task.
When a handler is blocked in synchronous code (run_in_executor, C-level
call), CancelledError can't be delivered. The finally block waited only
0.1s then abandoned the task silently. Now waits 1s, logs a warning, and
releases the global lock so other events can proceed.

Fixes the hang; zombie threads are an acknowledged limitation addressed
by the companion PR (anyio.to_thread.run_sync).

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread bubus/service.py
Comment thread tests/test_handler_timeout_zombie_thread.py
Comment thread tests/test_handler_timeout_zombie_thread.py
Comment thread tests/test_handler_timeout_zombie_thread.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handler timeout leaves zombie threads (can't cancel blocking sync code)

1 participant