tauri-apps / tauri-apps/plugins-workspace

single-instance (Windows): second instance survives when FindWindowW misses the startup window

Open
#3,587 0 comments 0 reactions 0 assignees Claimed by @SSS135 View on GitHub
platform: windows plugin: single-instance type: bug
Dominant language
Rust
Stars
1.8k
Forks
602
Avg merge
4d 14h
Merged PRs (30d)
9

Description

### Summary

On Windows, a second instance survives `tauri-plugin-single-instance` when it starts during the window between the first instance's `CreateMutexW` and its `create_event_target_window`. The exit path is nested inside `if !hwnd.is_null()` and there is no `else`, so a `FindWindowW` miss falls through and the second process boots completely.

Version: `2.4.2`. The same code is present on `v2` HEAD (`2.4.4`) — `2.4.3` was a macOS thread fix and `2.4.4` was documentation, so upgrading does not change this.

### The code

`single-instance/src/platform_impl/windows.rs` (2.4.2, lines ~70-105):

```rust
let hmutex = unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };

if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe {
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());

if !hwnd.is_null() {
// ... SendMessageW(WM_COPYDATA) ...
app.cleanup_before_exit();
std::process::exit(0);
}
// <-- no `else`: hwnd == null falls through and the instance keeps booting
}
} else {
app.manage(MutexHandle(hmutex as _));
// ...
let hwnd = create_event_target_window::(&class_name, &window_name, userdata);
app.manage(TargetWindowHandle(hwnd as _));
}
```

The first instance publishes the mutex *before* it creates the event target window. Any launch that lands in that interval observes `ERROR_ALREADY_EXISTS`, finds no window to message, and then simply continues into `Builder::run` as a full second instance.

### Reproduction

Two launches with no pause between them — e.g. a fast double-click on the shortcut, or:

```powershell
Start-Process .\my-app.exe; Start-Process .\my-app.exe
```

Expected: one process. Observed (intermittently, the window is a few milliseconds wide): two `my-app.exe` in Task Manager, both fully initialized.

Autostart plus an impatient user double-clicking the shortcut makes this land more often than the raw timing suggests.

### Impact

For a plain UI app the visible symptom is two windows. For an app whose single-instance guarantee protects an exclusive resource it is worse: in our case each instance opens its own SQLite connection pool over the same file, and the second writer produced a sustained `database is locked` on a point-of-sale machine until the operator restarted. The plugin is the documented way to prevent exactly that, so the failure is silent — nothing logs, nothing errors.

### Suggested fix

Exit in the `hwnd.is_null()` case as well. The first instance is known to exist (the mutex proves it); it simply is not ready to be messaged yet, which is not a reason to keep running:

```rust
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
let hwnd = unsafe { FindWindowW(class_name.as_ptr(), window_name.as_ptr()) };
if !hwnd.is_null() {
// ... SendMessageW ...
}
app.cleanup_before_exit();
std::process::exit(0);
}
```

A short bounded retry on `FindWindowW` before exiting would additionally preserve deep-link forwarding for launches that land in the gap, instead of dropping the arguments. Either way the process should not survive.

Creating the event target window *before* the mutex would also close the gap, at the cost of a window with no mutex behind it if setup fails afterwards.

### Workaround

We create our own named mutex as the first statement of `run()` (before `tauri::Builder`, therefore before any plugin `setup`), and exit only when the plugin's event window is absent — so the plugin keeps handling focus and deep links in every other case.

Contributor guide

Open the contributing guide

Research direction

Read single-instance/src/platform_impl/windows.rs around CreateMutexW, FindWindowW, and the startup branch. Reproduce the rapid two-launch case on Windows, then verify that the second process no longer completes startup when the mutex exists but the event window is not yet found; the linked pull request may already cover the work.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, tauri
Domain
desktop, operating-systems
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.