Skip to content

appkit: fix the panic when a nested run loop runs inside an event handler - #4796

Open
alesproch wants to merge 1 commit into
rust-windowing:masterfrom
alesproch:appkit-nested-run-loop-reentrancy
Open

alesproch wants to merge 1 commit into
rust-windowing:masterfrom
alesproch:appkit-nested-run-loop-reentrancy

Conversation

@alesproch

Copy link
Copy Markdown

Fixes #3992.

The problem

A nested run loop started from inside an event handler (a modal NSAlert or
NSOpenPanel, e.g. rfd's synchronous dialogs, or a drag-tracking session)
runs the main run loop's sources and queued blocks re-entrantly. On master,
two of them call the application handler there, and EventHandler::handle
panics with "tried to handle event while another event is currently being
handled":

  1. The event loop proxy's run loop source is registered in the common modes,
    so it also fires in a modal panel's run loop
    (NSModalPanelRunLoopMode). Any thread that calls
    EventLoopProxy::wake_up while a modal panel opened from a handler is up
    crashes the application.
  2. maybe_queue_with_handler queues an event AppKit reports while the handler
    is in use as a block in the default mode. A nested run loop in the default
    mode, such as the one a drag-tracking session runs
    (-[NSRunLoop runUntilDate:] under NSCoreDragTrackingProc, as reported in
    MacOS panic: "tried to handle event while another event is currently being handled" #3992), runs that block and calls the handler.

(The default-mode-only queueing already keeps modal panels out of the second
path since #1779; the first path, and nested loops in the default mode, were
still open.)

The fix

AppState keeps a call that arrives while the handler is in use, instead of
making it, and makes the kept calls in order once the outer handler has
returned (with_handler drains them). Several proxy wake-ups are delivered as
one. Because the kept calls may be delivered after about_to_wait, the run
loop is woken afterwards, so another pass follows and the application still
hears about_to_wait last before the loop waits.

This is direction (a) of the analysis posted in #3992 on 2026-10-21.

Tests

Two regression tests in winit-appkit/tests (harness = false, as they need
the main thread), run by the existing cargo test -p winit-appkit CI step:

  • proxy_wake_in_nested_run_loop: another thread calls wake_up while a
    handler runs the run loop in NSModalPanelRunLoopMode;
  • queued_event_in_nested_run_loop: a handler resizes its window (AppKit
    reports the resize, winit queues SurfaceResized) and then runs the run
    loop in the default mode.

Both panic on master and pass with the fix; each also checks that the
application hears about_to_wait after the deferred call.

Tested on macOS 26.6.2 (Apple silicon): cargo test -p winit-appkit,
cargo clippy -p winit-appkit --all-targets -- -Dwarnings (one finding
already on master, clippy::let_and_return in event_loop.rs:331 with
clippy 1.93, unrelated),
cargo +nightly fmt --all -- --check.

  • Tested on all platforms changed
  • Added an entry to the changelog module if knowledge of this change
    could be valuable to users
  • Updated documentation to reflect any user-facing changes, including
    notes of platform-specific behavior (no API change)
  • Created or updated an example program if it would help users understand
    this functionality (the tests are the reproductions)

@alesproch
alesproch requested a review from madsmtm as a code owner October 24, 2026 17:03
A nested run loop started from inside an event handler (a modal NSAlert or
NSOpenPanel, a drag-tracking session) runs the main run loop's sources and
queued blocks re-entrantly. Two of them called the application handler there
and panicked with "tried to handle event while another event is currently
being handled":

- the event loop proxy's source, which is in the common modes and so fires in
  a modal panel's run loop when another thread calls wake_up();
- the blocks maybe_queue_with_handler queues while the handler is in use,
  which a nested run loop in the default mode runs.

Keep such calls and make them, in order, once the outer handler has returned;
several proxy wake-ups are delivered as one. As they may come after
about_to_wait, the run loop is woken so another pass follows and the
application still hears about_to_wait last before it waits.

Fixes rust-windowing#3992.
@alesproch
alesproch force-pushed the appkit-nested-run-loop-reentrancy branch from f4c2d52 to f820012 Compare October 24, 2026 17:06

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

MacOS panic: "tried to handle event while another event is currently being handled"

1 participant