Summary
Program.StartupAsync builds the container (Program.cs, services.BuildServiceProvider()) and nothing ever disposes it — there is no desktop.ShutdownRequested/Exit hook and App.Run does not dispose the provider after StartWithClassicDesktopLifetime returns.
Impact
The carefully written teardown paths never run in production (only tests exercise them):
S7Service.DisposeAsync — graceful PLC disconnect;
PlcLifecycleManager.Dispose — sync-ownership lease release;
PlcSyncCoordinator.Dispose — waits out a pending sync.
Concretely: a recipe edit debounced in the last second before exit is silently dropped; the ownership lock-file handle is released by the OS, not by the code that owns it; the PLC sees a TCP reset instead of a clean disconnect.
Suggested fix
Dispose the provider after StartWithClassicDesktopLifetime returns (async disposables via DisposeAsync), or hook IClassicDesktopStyleApplicationLifetime.ShutdownRequested. Verify the shutdown order tolerates the PLC being unreachable (teardown must not block exit — bound it with a timeout).
Related: singleton view models (MainWindowViewModel, RecipeCoordinator) also implement IDisposable and are equally unreachable; container disposal covers them for free.
Summary
Program.StartupAsyncbuilds the container (Program.cs,services.BuildServiceProvider()) and nothing ever disposes it — there is nodesktop.ShutdownRequested/Exithook andApp.Rundoes not dispose the provider afterStartWithClassicDesktopLifetimereturns.Impact
The carefully written teardown paths never run in production (only tests exercise them):
S7Service.DisposeAsync— graceful PLC disconnect;PlcLifecycleManager.Dispose— sync-ownership lease release;PlcSyncCoordinator.Dispose— waits out a pending sync.Concretely: a recipe edit debounced in the last second before exit is silently dropped; the ownership lock-file handle is released by the OS, not by the code that owns it; the PLC sees a TCP reset instead of a clean disconnect.
Suggested fix
Dispose the provider after
StartWithClassicDesktopLifetimereturns (async disposables viaDisposeAsync), or hookIClassicDesktopStyleApplicationLifetime.ShutdownRequested. Verify the shutdown order tolerates the PLC being unreachable (teardown must not block exit — bound it with a timeout).Related: singleton view models (
MainWindowViewModel,RecipeCoordinator) also implementIDisposableand are equally unreachable; container disposal covers them for free.