pacenotes_installer/single_instance.rs
1const MUTEX_NAME: &str = "Local\\PacenotesInstaller.SingleInstance";
2/// How long a restarted instance waits for the one that launched it to close.
3const RESTART_WAIT_MS: u32 = 10_000;
4const RESTART_POLL_MS: u32 = 100;
5
6/// Whether another copy is running for this user, since two would share the
7/// download temp files, `cache.json` and a game folder's ledger unguarded.
8///
9/// A named mutex needs no cleanup after a crash. `Local\` scopes it to the
10/// logon session, because `Global\` needs a privilege a standard user may lack.
11/// The handle that wins is never closed, so the name lives as long as the
12/// process; one that finds the name taken is, or it would keep the mutex alive
13/// after its owner exits.
14pub fn another_instance_running() -> bool {
15 let name = crate::i18n::to_wide(MUTEX_NAME);
16 let take_ownership = 1;
17 unsafe {
18 let handle = windows_sys::Win32::System::Threading::CreateMutexW(
19 core::ptr::null(),
20 take_ownership,
21 name.as_ptr(),
22 );
23 // A failed check must not block the run on a machine with an unusual policy.
24 if handle.is_null() {
25 return false;
26 }
27 let taken = windows_sys::Win32::Foundation::GetLastError()
28 == windows_sys::Win32::Foundation::ERROR_ALREADY_EXISTS;
29 if taken {
30 windows_sys::Win32::Foundation::CloseHandle(handle);
31 }
32 taken
33 }
34}
35
36/// `another_instance_running` for an instance restarted as administrator, which
37/// starts while the one that launched it is still closing.
38pub fn another_instance_outlasts_restart() -> bool {
39 let mut waited = 0;
40 while another_instance_running() {
41 if waited >= RESTART_WAIT_MS {
42 return true;
43 }
44 std::thread::sleep(std::time::Duration::from_millis(u64::from(RESTART_POLL_MS)));
45 waited += RESTART_POLL_MS;
46 }
47 false
48}