fix: dispatch BN API calls to main thread via QTimer queue to prevent deadlocks#73
Open
kings0527 wants to merge 1 commit into
Open
fix: dispatch BN API calls to main thread via QTimer queue to prevent deadlocks#73kings0527 wants to merge 1 commit into
kings0527 wants to merge 1 commit into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
BN API calls from the HTTP handler's daemon thread deadlock against the main (UI) thread. This makes all endpoints except
/statushang indefinitely when the plugin is running inside the Binary Ninja GUI.Additionally,
_prune_views()uses Pythonisidentity checks on BinaryView wrapper objects, butgetCurrentBinaryView()returns a new Python wrapper each time. This causes_current_viewto 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
_main_thread_queueand_run_on_main_thread()helper that posts a callable +threading.Event, waits for the result, and detects re-entrant calls to avoid nested deadlockget_function_by_name_or_address()andget_function_names()with_run_on_main_thread_current_viewclearing in_prune_views(); the strong reference should only be cleared by explicit stop/closeupdate_analysis_and_wait()calls that block the event loop when executed inside a QTimer callbackinit.py
_tick()now drains_main_thread_queuebefore doing BV discovery, and records_main_thread_idon first tickhttp_server.py
do_GET/do_POSTdispatch their entire handler body to the main thread via_dispatch_on_main_thread(), except/statuswhich is handled directly_do_GET_inner/_do_POST_innermethods contain the original routing logicTimeoutError→ 504 responseendpoints.py
search_functions()wrapped with_run_on_main_threadTesting
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)