Skip to content

[Bug]: Desktop app hangs on quit (⌘Q) — deadlock between tauri_plugin_http shutdown and Webview::url() IPC #7548

Open

Description

@boden0002

Environment

  • Stirling-PDF Desktop version: 2.14.2 (2.14.2)
  • OS: macOS 15.7.7 (24G720)
  • Hardware: MacBookAir10,1 (Apple M1, arm64)
  • Install method: macOS .app bundle (Applications folder)

Description

Quitting the desktop app via ⌘Q (or Menu → Quit) frequently hangs the app completely — the window becomes unresponsive and the process must be force-killed. This has been observed multiple times, with .hang diagnostic reports confirming the process is unresponsive shortly after launch/quit.

I captured a live sample (spindump) of the hung process. It shows a clear deadlock:

Main thread — blocked handling the Cmd+Q key equivalent, inside the applicationWillTerminate → tao/tauri shutdown path:

-[NSMenu performKeyEquivalent:]
  -[NSApplication terminate:]
    -[NSNotificationCenter postNotificationName:object:userInfo:]
      tao::platform_impl::platform::app_delegate::application_will_terminate
        tao::platform_impl::platform::app_state::AppState::exit
          tauri_plugin_http::init::{{closure}}
            std::sync::mpmc::Receiver<T>::recv   <-- blocks here, forever

Six tokio-rt-worker threads — simultaneously blocked waiting for the main thread to service a webview IPC call:

tauri::ipc::InvokeResolver<R>::respond_async_serialized_inner::{{closure}}
  tauri::webview::Webview<R>::url
    <WryWebviewDispatcher<T> as WebviewDispatch<T>>::url
      std::sync::mpmc::Receiver<T>::recv   <-- waiting on main thread response

This is a classic mutual-wait deadlock: the main thread is stuck waiting on a tauri_plugin_http shutdown channel inside the applicationWillTerminate handler, while multiple worker threads are stuck waiting for the main thread to respond to Webview::url() calls. Since the main thread never returns to the event loop to service those IPC requests, and tauri_plugin_http's shutdown handler never gets whatever signal it's waiting for, both sides wait forever.

There is also a thread blocked in shared_child::SharedChild::wait() (waitid), presumably waiting for the bundled backend child process to exit — which also can't complete while the above deadlock holds.

Steps to reproduce

  1. Launch Stirling-PDF.app
  2. Quit via ⌘Q (observed most reliably when quitting shortly — within ~30–60s — after launch, though not confirmed as a strict requirement)
  3. App becomes unresponsive; window does not close; process must be force-quit

Expected behavior

App quits cleanly without hanging.

Additional context

Happy to provide the full spindump/sample output or additional .hang diagnostic reports if useful.

Activity

  1. balazs-szucs commented on Aug 23, 2026

    @balazs-szucs
    Collaborator

    Hi,

    thanks for reporting, i would wait until somebody else can reproduce this. I am afraid i cannot, tried 5 times straight no dice.

    Thanks!

  2. added and removed
    needs investigationIssues that require further investigation
    on Aug 23, 2026
  3. github-actions commented on Sep 22, 2026

    @github-actions
  4. added
    StaleIssues or pull requests that have become inactive
    on Sep 22, 2026
  5. github-actions commented on Sep 29, 2026

    @github-actions
  6. removed
    StaleIssues or pull requests that have become inactive
    on Sep 30, 2026
  7. amquck commented on Oct 3, 2026

    @amquck

    Experiencing the exact same issue and replicated via the same process as the OP.

  8. mtichikawa commented on Oct 18, 2026

    @mtichikawa

    I can reproduce this reliably on the current release, and I think I can explain why it doesn't show up every time.

    Environment: Stirling-PDF Desktop 3.1.0 and the official 3.2.0 build, macOS 27.0.1, MacBookPro18,3 (M1 Pro). Not signed in. It also happens signed in, so the account doesn't matter.

    Repro: launch the app, then close it (red close button) within about 0–2 s of the window appearing. Closing at a calm moment usually works, which may be why it didn't reproduce for you.

    Build Tries Hung (>30 s, force-killed) Quit in ~1 s
    3.1.0 7 4 3
    3.2.0 4 2 2

    What the hung process looks like (sample, 3.1.0): the same deadlock as the original report. The main thread is in tauri_plugin_http's exit handler, waiting in Receiver::recv for the cookie-store save. All 8 tokio-rt-worker threads are in Webview::url() waiting for the main thread.

    Every hang was preceded by Stirling's own 10 s backend-shutdown timeout (the "did not stop in time" line in tauri-backend.log), and none of the clean quits were. I think that's the trigger:

    1. RunEvent::ExitRequested calls cleanup_backend() on the main thread, which sleep-polls BACKEND_EXITED for up to 10 s.
    2. BACKEND_EXITED is set by the monitor_backend_output task on a tokio worker. If the frontend has fetches in flight, the workers are blocked in Webview::url() waiting for the main thread, which is asleep, so the backend's exit goes unnoticed and the 10 s timeout runs out. That's the freeze people see even when it eventually quits.
    3. The app then reaches RunEvent::Exit, where tauri-plugin-http (cookies feature) hands the cookie save to a tokio task and blocks the main thread on recv(). With every worker still stuck in url(), that never completes.

    Library side: step 3 is a tauri-plugin-http bug. tauri-apps/plugins-workspace#3703 (open) replaces that exit-time request_save() + recv() with a direct synchronous save, which would remove the permanent hang once it ships.

    Stirling side: steps 1–2 are fixable here regardless: run cleanup_backend() off the main thread (prevent_exit(), do the cleanup on a spawned thread, then app_handle.exit(0)), so the main thread keeps answering the webview while the backend stops. I think this also avoids the permanent hang in practice, because the workers drain before the exit handler runs, but I haven't proven that yet: a build from current main didn't hang in 10 tries for me, so I can't show a before/after until I find what differs from the release builds.

    Happy to open a small PR for the Stirling side if you'd like to assign this to me. I can attach the full sample output too.

    Investigated with help from Claude Code.

  9. mtichikawa commented on Oct 18, 2026

    @mtichikawa

    Correction and follow-up to my comment above: my earlier "a build from main didn't hang" was a flaw in my test harness, not a real difference. With that fixed, current main reproduces it too, so I was able to test both halves. Same machine; each try launches the app and quits within 0–2 s of the window appearing.

    Close button

    Build Tries Hung ~10 s freeze, then quit Quit in ~1 s
    main 17 6 6 5
    main + Stirling change (cleanup off the main thread) 12 0 0 12

    ⌘Q, within the first second

    Build Tries Hung
    main 2 2
    main + Stirling change 3 3
    main + Stirling change + patched tauri-plugin-http 8 0

    (⌘Q one or two seconds after launch quit cleanly on every build, 8 of 8.)

    So I need to walk back one guess from above: the Stirling change fixes the close-button path completely in my tests, but not ⌘Q. ⌘Q goes NSApplication terminate: → applicationWillTerminate → RunEvent::Exit without an ExitRequested, so Stirling's handler never runs and the app goes straight into the plugin's cookie-save recv() (confirmed with sample on the Stirling-change build). That half needs the plugin fix: the patch I tested replaces the exit-time request_save() + recv() with a direct synchronous write, the same thing tauri-apps/plugins-workspace#3703 does.

    Proposed PR for this repo: just the ExitRequested change in src-tauri/src/lib.rs (around 20 lines). It removes the 10 s freeze and the close-button hang now; ⌘Q will follow once the plugin fix ships and the dependency is bumped. Happy to open it if you assign this to me.

    Investigated with help from Claude Code.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions