PR #745: Fix races and hangs that stall or abort game launches
Pre-releasePR #745
Title: Fix races and hangs that stall or abort game launches
Author: @maxjivi05
Source PR: WinNative-Emu/WinNative#745
Source branch: fix/freezing
Source commit: 583d5acc32ca55b772d39c4b4f854482c480c2b7
Build workflow run: https://github.com/WinNative-Emu/WinNative/actions/runs/34716372084
What This PR Is About
Addresses launches that hang on the preloader, abort silently, or leave the app unresponsive. The reported workaround of toggling Steam Offline Mode on and then off maps to the cloud-sync and preloader paths below: enabling offline skips the cloud-sync leg entirely, and the launch that follows re-stamps the state the aborted launch had left behind.
Launch aborts and permanent stalls:
-
extractDXWrapperFiles indexed dxwrapper.split(";")[2] unguarded. A legacy dxwrapperConfig with no ddrawrapper key threw ArrayIndexOutOfBounds after wipeDxwrapperDllsForReextract had already deleted the d3d DLLs and before the stamp was written, so every subsequent launch reproduced it. Both the split and the config read now fall back to DEFAULT_DDRAWRAPPER.
-
wnLauncherDrivesDismiss was armed from getWnPlanW (default true) while the Plan-W launcher only runs under the narrower bionic-Steam conditions. When they diverged, the opaque non-cancelable preloader covered a running game for good. It is now armed from planWWrapperArgsResolved, set only where the Plan-W wrapper args are actually built, and the tailer gained a launcher-silence fallback that hands the preloader back.
-
Two launch prep threads edited the same system.reg/user.reg through WineRegistryEditor, which clones on open and renames on close with no locking, so one thread's entire edit set was silently lost while both latched their applied stamps. Registry files now carry a per-path lock, held by the editor and by the raw readers/writers that bypass it (forceHideSteamRegistry, hideRegistrySubtrees, restoreRegistrySubtrees, seedVcRedistBatched). Three WineUtils sites closed the editor inside try instead of finally.
-
Container.saveData serialized the live shared extraData while other threads mutated it, and wrote with a truncating FileWriter, so a concurrent reader saw an empty file and dropped the container. Extras are now guarded, the save serializes a snapshot, saves are serialized against each other, and the write is a staged rename that no longer falls back to truncating the live file.
-
The Steam CM WebSocket connect had no timeout and is held under the session bring-up mutex, so a network that accepts the socket but never completes the handshake wedged every caller behind it.
-
Stale SessionKeepAliveService statics let a later launch adopt a dead environment and never start Wine. Publishing now matches the background session preference and the statics are cleared when the environment stops.
Unresponsiveness:
-
The default uncaught-exception handler logged and swallowed, so a main-thread exception killed the looper and surfaced as an unattributable ANR instead of a crash. Main-thread failures now reach the platform handler; background threads keep the existing behaviour.
-
A Compose composition called SteamService.getAppDirPath, which is runBlocking on Dispatchers.IO plus Room, from a remember initialiser on the main thread. It is now a produceState.
-
LogManager.prepareForNewSession ran logcat -c with an unbounded waitFor on the main thread.
-
The launch-time Lossless shader import ran on the UI thread on every launch because its gate was inverted. Discovery now runs off-thread when frame generation is off and publishes the resolved cache path back so the drawer option stays available.
-
The cloud-conflict dialog was shown from a worker with no lifecycle guard and no try/catch before the worker parked on a ten-minute latch, so a dialog that could not be shown held the launch. All three stores now bail early, re-check inside the posted runnable, and release the latch on failure. An unanswered Steam conflict no longer pushes the local save over the cloud.
-
VulkanRenderer guarded the native handle with the instance monitor held across nativeDestroy and vkDeviceWaitIdle. It is now a ReentrantLock and the handle is volatile, closing a torn 64-bit read on the render thread.
Attached Artifacts
- WinNative-Debug-Standard-PR.745-583d5.apk
- WinNative-Debug-Ludashi-PR.745-583d5.apk
- WinNative-Debug-Pubg-PR.745-583d5.apk
- WinNative-Debug-Antutu-PR.745-583d5.apk
Built from source commit 583d5.