Make the Windows handler reentrancy-safe (fix "already borrowed" panic on resize)#263
Make the Windows handler reentrancy-safe (fix "already borrowed" panic on resize)#263aidan729 wants to merge 1 commit into
Conversation
handle_on_frame and handle_event hold self.handler.borrow_mut() across the entire user callback. If that callback synchronously triggers another window message — e.g. a SetWindowPos or host-driven resize that sends WM_SIZE straight back into the window procedure — the wndproc re-enters handle_event, borrows the handler a second time, and panics with a BorrowMutError. In an audio-plugin context that unwinds across the FFI boundary and crashes the host. Use ry_borrow_mut() and skip the re-entrant call instead of panicking. The handler isn't reentrant, so dropping the nested invocation is correct: window state stays consistent and the next genuine frame/event picks up the latest size. This makes it safe to call host resize APIs (which synchronously re-enter the window proc on Windows) from inside a handler callback.
|
For additional context, someone in the Rust Audio Discord raised a concern about this approach:
My response was that I don't believe anything meaningful is actually being dropped in this case. The re-entrant call isn't carrying new independent input; it's a synchronous side effect of the outer callback. Specifically, the resize API call immediately generates a As a result, the nested invocation observes handler state while it is mid-mutation, which is exactly why the From that perspective, skipping the nested invocation is simply deferring reconciliation until the borrow is valid rather than panicking and potentially unwinding across the FFI boundary into the host. The main alternative would be to queue re-entrant events and replay them once the borrow is released. I considered that approach, but for That said, if there's a case where a skipped re-entrant invocation carries state that cannot otherwise be recovered, I'd be interested in seeing an example. I wasn't able to construct such a case for the resize path. |
Problem
On Windows,
WindowState::handle_on_frameandhandle_eventholdself.handler.borrow_mut()for the full duration of the user'son_frame/on_eventcallback. If the callback causes Windows to synchronously dispatchanother message into the window procedure, the wndproc re-enters
handle_event,calls
borrow_mut()again, and panics withalready borrowed: BorrowMutError.The most common trigger is resizing: calling a host's resize request (or a
SetWindowPos) from inside a handler callback synchronously sendsWM_SIZEbackinto the window proc. In an audio-plugin host this panic unwinds across the FFI
boundary and crashes the DAW.
This is reproducible whenever a baseview-hosted GUI tries to resize its own
window in response to user interaction (e.g. a draggable resize affordance that
asks the host to grow the editor).
Fix
Use
try_borrow_mut()in bothhandle_on_frameandhandle_eventand skip theevent if the handler is already borrowed, instead of panicking. The handler is
not reentrant, so dropping the nested call is the correct behavior — the window
state remains consistent and the next genuine frame/event observes the latest
size.
This brings the Windows backend in line with how non-reentrant event loops are
expected to behave and makes it safe to invoke host resize APIs from within a
handler callback.
Scope
src/win/window.rs), two call sites.re-entrant callback is now skipped instead of panicking.