From 396310fa0a848b2a08f27ca524820901378e2c5d Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 6 May 2026 00:54:06 -0400 Subject: [PATCH] Guard async void TaskViewModel methods against unhandled exceptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InstallMod, VerifyMod, CreateModVersion, and UploadModVersion were async void with no top-level exception handling. Any exception escaping after an await would be posted to the SynchronizationContext as unhandled, silently crashing the process — no global handler is registered anywhere in the codebase. Wrap each method body in a try/catch that logs via Log.Add, matching the pattern already used in Knossos.StartUp and Knossos.PlayMod. No callers need to change; the fire-and-forget pattern is preserved. Co-Authored-By: Claude Sonnet 4.6 --- Knossos.NET/ViewModels/TaskViewModel.cs | 136 ++++++++++++++---------- 1 file changed, 82 insertions(+), 54 deletions(-) diff --git a/Knossos.NET/ViewModels/TaskViewModel.cs b/Knossos.NET/ViewModels/TaskViewModel.cs index 75439cef..9734736e 100644 --- a/Knossos.NET/ViewModels/TaskViewModel.cs +++ b/Knossos.NET/ViewModels/TaskViewModel.cs @@ -292,45 +292,52 @@ await Dispatcher.UIThread.InvokeAsync(async () => /// public async void InstallMod(Mod mod, List? reinstallPkgs = null, bool manualCompress = false, bool cleanupOldVersions = false, bool cleanInstall = false, bool allowHardlinks = true) { - if (Knossos.GetKnossosLibraryPath() == null) + try { - await Dispatcher.UIThread.InvokeAsync(async () => + if (Knossos.GetKnossosLibraryPath() == null) { - await MessageBox.Show(MainWindow.instance!, "KnossosNET Library Folder path is not set! Before installing mods go to settings and select a Library Folder.", "Error", MessageBox.MessageBoxButtons.OK); - }); - return; - } - - if (mod.type == ModType.engine) - { - //If this is an engine build then call the UI element to do the build install process instead - FsoBuildsViewModel.Instance?.RelayInstallBuild(mod); - } - else - { - using (var cancelSource = new CancellationTokenSource()) - { - var newTask = new TaskItemViewModel(); - Dispatcher.UIThread.Invoke(() => + await Dispatcher.UIThread.InvokeAsync(async () => { - TaskList.Add(newTask); - taskQueue.Enqueue(newTask); + await MessageBox.Show(MainWindow.instance!, "KnossosNET Library Folder path is not set! Before installing mods go to settings and select a Library Folder.", "Error", MessageBox.MessageBoxButtons.OK); }); - var res = await newTask.InstallMod(mod, cancelSource, reinstallPkgs, manualCompress, cleanupOldVersions, cleanInstall, allowHardlinks).ConfigureAwait(false); - if(res && Knossos.inSingleTCMode) + return; + } + + if (mod.type == ModType.engine) + { + //If this is an engine build then call the UI element to do the build install process instead + FsoBuildsViewModel.Instance?.RelayInstallBuild(mod); + } + else + { + using (var cancelSource = new CancellationTokenSource()) { - try + var newTask = new TaskItemViewModel(); + Dispatcher.UIThread.Invoke(() => { - TaskList.Remove(newTask); - } - catch (Exception ex) + TaskList.Add(newTask); + taskQueue.Enqueue(newTask); + }); + var res = await newTask.InstallMod(mod, cancelSource, reinstallPkgs, manualCompress, cleanupOldVersions, cleanInstall, allowHardlinks).ConfigureAwait(false); + if(res && Knossos.inSingleTCMode) { - Log.Add(Log.LogSeverity.Error, "TaskViewModel.InstallMod()", ex); + try + { + TaskList.Remove(newTask); + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "TaskViewModel.InstallMod()", ex); + } + Dispatcher.UIThread.Invoke(() => TaskViewModel.Instance?.AddMessageTask("Completed: " + newTask.Name), DispatcherPriority.Background); } - Dispatcher.UIThread.Invoke(() => TaskViewModel.Instance?.AddMessageTask("Completed: " + newTask.Name), DispatcherPriority.Background); } } } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "TaskViewModel.InstallMod()", ex); + } } /// @@ -418,15 +425,22 @@ public async Task DecompressMod(Mod mod) /// public async void VerifyMod(Mod mod) { - using (var cancelSource = new CancellationTokenSource()) + try { - var newTask = new TaskItemViewModel(); - Dispatcher.UIThread.Invoke(() => + using (var cancelSource = new CancellationTokenSource()) { - TaskList.Add(newTask); - taskQueue.Enqueue(newTask); - }); - await newTask.VerifyMod(mod, cancelSource).ConfigureAwait(false); + var newTask = new TaskItemViewModel(); + Dispatcher.UIThread.Invoke(() => + { + TaskList.Add(newTask); + taskQueue.Enqueue(newTask); + }); + await newTask.VerifyMod(mod, cancelSource).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "TaskViewModel.VerifyMod()", ex); } } @@ -439,20 +453,27 @@ public async void VerifyMod(Mod mod) /// public async void CreateModVersion(Mod oldMod, string newVersion, Action hackCallback) { - using (var cancelSource = new CancellationTokenSource()) + try { - var newTask = new TaskItemViewModel(); - Dispatcher.UIThread.Invoke(() => - { - TaskList.Add(newTask); - taskQueue.Enqueue(newTask); - }); - await newTask.CreateModVersion(oldMod, newVersion, cancelSource).ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - Dispatcher.UIThread.Invoke(() => + using (var cancelSource = new CancellationTokenSource()) { - hackCallback?.Invoke(); - }); + var newTask = new TaskItemViewModel(); + Dispatcher.UIThread.Invoke(() => + { + TaskList.Add(newTask); + taskQueue.Enqueue(newTask); + }); + await newTask.CreateModVersion(oldMod, newVersion, cancelSource).ConfigureAwait(false); + await Task.Delay(1000).ConfigureAwait(false); + Dispatcher.UIThread.Invoke(() => + { + hackCallback?.Invoke(); + }); + } + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "TaskViewModel.CreateModVersion()", ex); } } @@ -468,15 +489,22 @@ public async void CreateModVersion(Mod oldMod, string newVersion, Action hackCal /// public async void UploadModVersion(Mod mod, bool isNewMod, bool metadataonly = false, int parallelCompression = 1, int parallelUploads = 1, List? advData = null ) { - using (var cancelSource = new CancellationTokenSource()) + try { - var newTask = new TaskItemViewModel(); - Dispatcher.UIThread.Invoke(() => + using (var cancelSource = new CancellationTokenSource()) { - TaskList.Add(newTask); - taskQueue.Enqueue(newTask); - }); - await newTask.UploadModVersion(mod, isNewMod, metadataonly, cancelSource, parallelCompression, parallelUploads, advData).ConfigureAwait(false); + var newTask = new TaskItemViewModel(); + Dispatcher.UIThread.Invoke(() => + { + TaskList.Add(newTask); + taskQueue.Enqueue(newTask); + }); + await newTask.UploadModVersion(mod, isNewMod, metadataonly, cancelSource, parallelCompression, parallelUploads, advData).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Log.Add(Log.LogSeverity.Error, "TaskViewModel.UploadModVersion()", ex); } }