Finalize DI: drop transitional VM ctor; harden disposal
Remove the transitional MainWindowViewModel(AppSettings, IStartupTracer) constructor and the _ownsServices flag now that every caller resolves the view model from the container; the container owns and disposes the hardware monitor (driver unload), so the view model no longer disposes it. Make HardwareMonitorService.Dispose idempotent with a _disposed guard, and run provider disposal in a finally block in MainWindow_Closed so the ring0 driver is always unloaded on real exit even if an earlier teardown step throws. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -456,16 +456,22 @@ public sealed partial class MainWindow : Window
|
||||
}
|
||||
|
||||
_isShuttingDown = true;
|
||||
_timer.Stop();
|
||||
_columnMeasurer.StopSettleTimer();
|
||||
_trayIconService.Dispose();
|
||||
_secondaryWindows.CloseAll();
|
||||
_placementService.Save();
|
||||
|
||||
// The container owns the view model and domain services. Disposing the provider runs
|
||||
// MainWindowViewModel.Dispose first (reverse construction order) to persist settings while the
|
||||
// hardware monitor and web server are still alive, then disposes those services and the tracer.
|
||||
(_serviceProvider as IDisposable)?.Dispose();
|
||||
try
|
||||
{
|
||||
_timer.Stop();
|
||||
_columnMeasurer.StopSettleTimer();
|
||||
_trayIconService.Dispose();
|
||||
_secondaryWindows.CloseAll();
|
||||
_placementService.Save();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// The container owns the view model and domain services. Disposing the provider runs
|
||||
// MainWindowViewModel.Dispose first (reverse construction order) to persist settings while the
|
||||
// hardware monitor and web server are still alive, then disposes those services and the tracer.
|
||||
// This MUST run even if a teardown step above throws, so the ring0 driver is always unloaded.
|
||||
(_serviceProvider as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed class HardwareMonitorService : IHardwareMonitorService
|
||||
private readonly UpdateVisitor _updateVisitor = new();
|
||||
private readonly TreeRebuildCoalescer _treeRebuildCoalescer;
|
||||
private bool _isOpen;
|
||||
private bool _disposed;
|
||||
|
||||
public HardwareMonitorService(AppSettings settings)
|
||||
{
|
||||
@@ -199,6 +200,11 @@ public sealed class HardwareMonitorService : IHardwareMonitorService
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
// Clear before closing so any tree-rebuild task still pending its delay bails instead of rebuilding from a
|
||||
// half-closed Computer.
|
||||
_isOpen = false;
|
||||
|
||||
@@ -90,7 +90,6 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
||||
private readonly IRemoteWebServer _remoteWebServer;
|
||||
private readonly StartupService _startupService;
|
||||
private readonly IStartupTracer _startupTrace;
|
||||
private readonly bool _ownsServices;
|
||||
private AppThemeMode _themeMode;
|
||||
private int _loggingIntervalIndex;
|
||||
private PlotLocation _plotLocation;
|
||||
@@ -141,32 +140,6 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
||||
InitializeFromSettings(settings);
|
||||
}
|
||||
|
||||
// Transitional constructor that builds the domain graph the legacy way for callers that do not yet
|
||||
// resolve the view model from the container. Removed once MainWindow is DI-resolved (di-app-invert).
|
||||
internal MainWindowViewModel(AppSettings settings, IStartupTracer startupTrace)
|
||||
{
|
||||
Settings = settings;
|
||||
_startupTrace = startupTrace;
|
||||
_startupTrace.Mark("MainWindowViewModel.Constructor.Begin");
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
HardwareMonitorService hardwareMonitor = new(settings);
|
||||
_hardwareMonitor = hardwareMonitor;
|
||||
_logger = new Logger(hardwareMonitor.Computer);
|
||||
_sensorSelection = new SensorSelectionService(settings);
|
||||
_remoteWebServer = new RemoteWebServer(
|
||||
hardwareMonitor.Computer,
|
||||
hardwareMonitor.SensorReadLock,
|
||||
settings.GetValue("listenerIp", "?"),
|
||||
settings.GetValue("listenerPort", 8085),
|
||||
settings.GetValue("authenticationEnabled", false),
|
||||
settings.GetValue("authenticationUserName", ""),
|
||||
settings.GetValue("authenticationPassword", ""));
|
||||
_plotTracking = new PlotTrackingService();
|
||||
_startupService = new StartupService();
|
||||
_ownsServices = true;
|
||||
InitializeFromSettings(settings);
|
||||
}
|
||||
|
||||
private void InitializeFromSettings(AppSettings settings)
|
||||
{
|
||||
_remoteWebServer.SetRootProvider(() => RootItems.FirstOrDefault());
|
||||
@@ -789,8 +762,6 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable
|
||||
Settings.SetValue("authenticationUserName", AuthWebServerUserName);
|
||||
Settings.SetValue("authenticationPassword", _remoteWebServer.PasswordHash);
|
||||
_remoteWebServer.Quit();
|
||||
if (_ownsServices)
|
||||
_hardwareMonitor.Dispose();
|
||||
Save();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user