Skip to content

fix: dispatch BN API calls to main thread via QTimer queue to prevent deadlocks#73

Open
kings0527 wants to merge 1 commit into
fosdickio:mainfrom
kings0527:fix/main-thread-dispatch
Open

fix: dispatch BN API calls to main thread via QTimer queue to prevent deadlocks#73
kings0527 wants to merge 1 commit into
fosdickio:mainfrom
kings0527:fix/main-thread-dispatch

Conversation

@kings0527
Copy link
Copy Markdown

Problem

BN API calls from the HTTP handler's daemon thread deadlock against the main (UI) thread. This makes all endpoints except /status hang indefinitely when the plugin is running inside the Binary Ninja GUI.

Additionally, _prune_views() uses Python is identity checks on BinaryView wrapper objects, but getCurrentBinaryView() returns a new Python wrapper each time. This causes _current_view to be repeatedly cleared on every timer tick.

Solution

Introduce a lightweight dispatch queue (_main_thread_queue) that the existing QTimer tick drains every 200ms, executing queued callables on the main thread and signalling completion back to the waiting HTTP thread.

Key changes:

binary_operations.py

  • Add _main_thread_queue and _run_on_main_thread() helper that posts a callable + threading.Event, waits for the result, and detects re-entrant calls to avoid nested deadlock
  • Wrap get_function_by_name_or_address() and get_function_names() with _run_on_main_thread
  • Remove identity-based _current_view clearing in _prune_views(); the strong reference should only be cleared by explicit stop/close
  • Remove update_analysis_and_wait() calls that block the event loop when executed inside a QTimer callback

init.py

  • QTimer _tick() now drains _main_thread_queue before doing BV discovery, and records _main_thread_id on first tick
  • Timer interval reduced from 1000ms to 200ms for lower API latency

http_server.py

  • do_GET / do_POST dispatch their entire handler body to the main thread via _dispatch_on_main_thread(), except /status which is handled directly
  • New _do_GET_inner / _do_POST_inner methods contain the original routing logic
  • TimeoutError → 504 response

endpoints.py

  • search_functions() wrapped with _run_on_main_thread

Testing

Tested with Binary Ninja 5.2 on macOS ARM64:

  • /status ✅ (responds instantly, no main-thread dispatch needed)
  • /functions?limit=5 ✅ (returns function list, 69k+ functions)
  • /exports?limit=20 ✅ (returns exports)
  • /decompile?name=JNI_OnLoad ✅ (returns decompiled output)
  • /searchFunctions?query=main ✅ (returns search results)

BN API calls from the HTTP handler's daemon thread deadlock against the
main (UI) thread.  This commit introduces a lightweight dispatch queue
(_main_thread_queue) that the existing QTimer tick drains every 200ms,
executing queued callables on the main thread and signalling completion
back to the waiting HTTP thread.

Key changes:

binary_operations.py
- Add _main_thread_queue (queue.Queue) and _run_on_main_thread() helper
  that posts a callable + threading.Event, waits for the result, and
  detects re-entrant calls to avoid nested deadlock.
- Wrap get_function_by_name_or_address() and get_function_names() with
  _run_on_main_thread so they are safe from any calling thread.
- Remove identity-based _current_view clearing in _prune_views(); the
  strong reference should only be cleared by explicit stop/close.
- Remove update_analysis_and_wait() calls that block the event loop
  when executed inside a QTimer callback.

__init__.py
- QTimer _tick() now drains _main_thread_queue before doing BV
  discovery, and records _main_thread_id on first tick.
- Timer interval reduced from 1000ms to 200ms for lower API latency.

http_server.py
- do_GET / do_POST dispatch their entire handler body to the main
  thread via _dispatch_on_main_thread(), except /status which is
  handled directly (no BN API needed).
- New _do_GET_inner / _do_POST_inner methods contain the original
  routing logic, now running safely on the main thread.
- TimeoutError → 504 response.

endpoints.py
- search_functions() wrapped with _run_on_main_thread.
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.

1 participant