Fix client startup hangs on macOS - #1042
Conversation
The UniversalGL client never reaches its main menu on macOS - it deadlocks
during startup. There are two distinct hangs, both rooted in .NET's
FileSystemWatcher (FSEvents-backed) stalling during setup on macOS:
1. GameClass.BuildServiceProvider built a full .NET Generic Host via
Host.CreateDefaultBuilder().Build(), whose configuration provider sets up a
reloadOnChange FileSystemWatcher. That setup hangs, so Build() never returns
and the main thread blocks on it forever. The client only ever used the host
as a DI container, so replace it with a plain ServiceCollection (identical
service registrations), dropping the Generic Host machinery entirely.
2. MapLoader.Initialize subscribed StartMapFileWatcher to MapLoadingComplete,
which is invoked synchronously at the end of the map-loading task. That
watcher is another FileSystemWatcher; its setup hangs and blocks the map
loading task from ever completing, leaving the client stuck on the loading
screen ("Waiting for loading maps..."). Skip the live map-file watcher on
macOS - maps still load, they just are not hot-reloaded at runtime.
With both changes the UniversalGL client boots to the main menu on macOS.
The MapLoader change is guarded to macOS only. The ServiceCollection change
applies to all platforms; the client used no Generic Host feature beyond DI,
so behavior should be unchanged, but a Windows/Linux build+run check is
worthwhile.
|
Nightly build for this pull request:
|
|
Thanks for reporting bugs and submitting the fix. Your changes seem to be fine but it is unclear what happened when the client started in macOS. Please first report this bug as an issue following our bug issue template. Besides,
Please note that these words are off-topic. You need to revise what AI said, not just copying the generated words. |
|
I am not sure why a separate issue report and not just describe stuff here already? |
Because it's easier to say please fill the bug template instead of saying please describe the bug in detail including steps to reproduce it, attaching the log etc. I need these texts to reproduce it on my mac machine and verify that: it does not work before this PR and it works after applying this PR. |
| // up a FileSystemWatcher (reloadOnChange), whose FSEvents-backed setup stalls there. | ||
| // A plain ServiceCollection provides the same DI with none of that machinery. | ||
| var services = new ServiceCollection(); | ||
| { |
There was a problem hiding this comment.
{} Brackets could be removed?
|
You said "On macOS, initialization of that watcher stalls, causing Build() to never return and permanently blocking the main thread." and have fixed it by removing the watcher entirely (for Mac). That seems overkill - do you know the reason why it hangs? dotnet/runtime#121256 Also worth testing downloading maps in the lobby with /downloadmap and also with other players (when the host has a map you don't). I've tested your PR on Windows and can't see any issues - but I would like to understand why it hangs on Mac before we remove it completely. |

Summary
Fixes two startup deadlocks that prevented the UniversalGL client from reaching the main menu on macOS.
Both hangs occurred while .NET attempted to initialize
FileSystemWatcherinstances backed by macOS FSEvents. Without these changes, the client either blocked during dependency-injection setup or remained indefinitely on the map-loading screen without rendering a usable main window.Root cause
Generic Host initialization
GameClass.BuildServiceProviderpreviously created a complete .NET Generic Host:Host.CreateDefaultBuilderconfigures application settings with change monitoring enabled. DuringBuild(), the configuration system creates aFileSystemWatcherforreloadOnChange.On macOS, initialization of that watcher stalls, causing
Build()to never return and permanently blocking the main thread.The client did not use hosting, configuration reloads, application lifetime management, or any other Generic Host functionality. It only used the resulting service provider as a dependency-injection container.
This change replaces the Generic Host with a plain
ServiceCollectionwhile preserving the existing service registrations:This provides the same dependency-injection functionality without initializing the additional Generic Host infrastructure.
Map file watcher initialization
MapLoader.Initializepreviously subscribedStartMapFileWatchertoMapLoadingComplete.MapLoadingCompleteis invoked synchronously at the end of the map-loading task.StartMapFileWatchercreates anotherFileSystemWatcher, whose initialization also stalls on macOS.Because the event handler runs synchronously, the watcher prevents the map-loading task from completing. The client consequently remains stuck on:
The live map-file watcher is now disabled on macOS. Maps still load normally, but changes to map files are not detected and hot-reloaded while the client is running.
Result
The UniversalGL client now:
Verified end to end on:
The tested flow was:
Platform notes
The
MapLoaderworkaround is restricted to macOS throughOperatingSystem.IsMacOS(). Windows and Linux retain the existing map hot-reload behavior.The replacement of the Generic Host with
ServiceCollectionapplies to all platforms. The client only used the host as a dependency-injection container, so no intentional behavior changes are expected on Windows or Linux.This change has been tested on macOS. A native Windows and Linux build-and-run check is still recommended to confirm that the dependency-injection change introduces no platform-specific regressions.
Related
This PR fixes startup and lobby rendering for the UniversalGL client on macOS.
Launching the actual game process from the lobby under Wine requires a separate Syringe fix. Stock Syringe silently fails to perform DLL injection and hook installation under Wine, causing the game to exit before
spawn.iniis read.That independent SyringeEx change is available here:
Phobos-developers/SyringeEx#25