Skip to content

fix(windows): use CoWaitForMultipleHandles for ARM64 WebView2 deadlock#1666

Open
npiesco wants to merge 1 commit into
tauri-apps:devfrom
npiesco:fix/deferred-webview2-arm64
Open

fix(windows): use CoWaitForMultipleHandles for ARM64 WebView2 deadlock#1666
npiesco wants to merge 1 commit into
tauri-apps:devfrom
npiesco:fix/deferred-webview2-arm64

Conversation

@npiesco
Copy link
Copy Markdown

@npiesco npiesco commented Feb 8, 2026

Summary

Replaces mpsc::channel + wait_with_pump pattern with CoWaitForMultipleHandles in three WebView2 call sites, fixing deterministic deadlock on Windows ARM64 and latent reentrancy issue on all architectures.

Closes #1665. Related: #583.

Problem

On Windows ARM64 (Snapdragon X Elite, aarch64-pc-windows-msvc), creating second WebView2 controller from main STA thread deadlocks inside MsgWaitForMultipleObjectsEx. Root cause is that webview2_com::wait_with_pump runs nested Win32 message pump (GetMessage/PeekMessage), which does not allow COM to re-enter apartment to deliver async completion callbacks. On x86/x64 this usually works by accident because WebView2's internal RPC piggybacks on window messages, but on ARM64 timing exposes bug deterministically.

Root cause behind #583 (deadlock creating new window from an IPC handler), which was worked around but never fixed at source.

Solution

Replace all three mpsc::channel + wait_with_pump call sites with:

ust CoWaitForMultipleHandles( COWAIT_DISPATCH_CALLS | COWAIT_DISPATCH_WINDOW_MESSAGES, INFINITE, &[event], )

COM-sanctioned mechanism for yielding STA thread while preserving reentrancy:

  • COWAIT_DISPATCH_CALLS dispatches incoming COM calls (required for WebView2 async completion callbacks)
  • COWAIT_DISPATCH_WINDOW_MESSAGES dispatches window messages (keeps the event loop responsive)

Approach is consistent with Microsoft's WebView2 threading model documentation on reentrancy.

Design notes

  • Intentional departure from mpsc::channel: previous pattern used mpsc::channel + wait_with_pump (and comments explained why wait_for_async was avoided). PR removes approach entirely because underlying Win32 message pump prevents COM STA reentrancy, exact mechanism WebView2 needs to deliver completion callbacks.
  • Rc<RefCell<_>> instead of Arc<Mutex<_>>: completion callbacks run on same STA thread (dispatched by CoWaitForMultipleHandles), so Rc/RefCell is correct COM types inside aren't Send/Sync.
  • Win32_Security feature gate: Required by windows crate's CreateEventW signature (SECURITY_ATTRIBUTES). Verified won't compile without it.
  • Fixes all architectures: ARM64 just exposed bug deterministically; reentrancy problem exists on x86/x64 too (see Deadlock creating a new window on the IPC handler on Windows #583).

Affected call sites

  1. create_environment() CreateCoreWebView2EnvironmentWithOptions
  2. create_controller() CreateCoreWebView2Controller / CreateCoreWebView2ControllerWithOptions
  3. cookies_inner() CookieManager().GetCookies

Testing

  • 6 unit tests added covering co_wait_for_handle behavior, the Rc<RefCell<Option<T>>> + event signaling pattern, STA reentrancy dispatch, and full create_environment/create_controller integration
  • cargo test passes (8 passed, 0 failed)
  • cargo clippy clean (0 warnings)
  • Tested on Windows ARM64 (Snapdragon X Elite) deadlock resolved
  • Tested on WSL2/Linux zero regressions (webview2 module is #[cfg(target_os = windows)])

Minimal Reproduction

https://github.com/npiesco/wry-arm64-deadlock

  • main branch: deadlocks on ARM64 (uses upstream wry)
  • fix/vendored-wry-tauri branch: works (uses patched wry fork)

On Windows ARM64 (e.g. Snapdragon X Elite), creating a second WebView2 controller from the main STA thread deadlocked in MsgWaitForMultipleObjectsEx because the nested GetMessage/PeekMessage loop prevented COM from re-entering the apartment to deliver the async completion callback.

Replace the mpsc::channel + wait_with_pump pattern in create_environment(), create_controller(), and cookies_inner() with CoWaitForMultipleHandles using COWAIT_DISPATCH_CALLS | COWAIT_DISPATCH_WINDOW_MESSAGES -- the COM-sanctioned mechanism for yielding an STA thread while preserving re-entrancy.

Includes unit and integration tests for the new co_wait_for_handle primitive, the Arc<Mutex<Option<T>>> delivery pattern, COWAIT_DISPATCH_CALLS flag acceptance, and the full create_environment/create_controller paths.

Ref: https://github.com/npiesco/wry-arm64-deadlock (minimal reproduction)
Copy link
Copy Markdown
Contributor

@Legend-Master Legend-Master left a comment

Choose a reason for hiding this comment

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

This doesn't seem to fix 'dead lock when creating a new webview inside an event/completion handler' (if that's what you're trying to fix) for me at all on an x64 machine from testing

@FabianLars FabianLars added the status: waiting Waiting for a response or another PR label Mar 22, 2026
@Legend-Master Legend-Master added the ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy label May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-slop Low effort content, see https://github.com/tauri-apps/tauri?tab=contributing-ov-file#ai-tool-policy status: waiting Waiting for a response or another PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] WebView2 deadlock on ARM64 (and potential reentrancy issue on all architectures)

3 participants