fix: prevent some deadlocks in profile management code

We can never load/unload plugins synchronously, since they might need to get back on framework thread to unload.
Also fixes an issue wherein applying might have gotten stuck if an unload threw an exception.
This commit is contained in:
goat 2023-06-20 21:55:31 +02:00
parent da8bbf5a28
commit c3fe41640e
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
7 changed files with 107 additions and 88 deletions

View file

@ -2367,12 +2367,12 @@ internal class PluginInstallerWindow : Window, IDisposable
{
if (inProfile)
{
Task.Run(() => profile.AddOrUpdate(plugin.Manifest.InternalName, true))
Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.InternalName, true))
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotAdd);
}
else
{
Task.Run(() => profile.Remove(plugin.Manifest.InternalName))
Task.Run(() => profile.RemoveAsync(plugin.Manifest.InternalName))
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotRemove);
}
}
@ -2391,14 +2391,17 @@ internal class PluginInstallerWindow : Window, IDisposable
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
{
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, plugin.IsLoaded, false);
// TODO: Work this out
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, plugin.IsLoaded, false))
.GetAwaiter().GetResult();
foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile && x.Plugins.Any(y => y.InternalName == plugin.Manifest.InternalName)))
{
profile.Remove(plugin.Manifest.InternalName, false);
Task.Run(() => profile.RemoveAsync(plugin.Manifest.InternalName, false))
.GetAwaiter().GetResult();
}
// TODO error handling
Task.Run(() => profileManager.ApplyAllWantStates());
Task.Run(profileManager.ApplyAllWantStatesAsync)
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_ProfileApplyFail);
}
ImGui.SameLine();
@ -2448,7 +2451,9 @@ internal class PluginInstallerWindow : Window, IDisposable
return;
}
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, false, false);
// TODO: Work this out
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, false, false))
.GetAwaiter().GetResult();
this.enableDisableStatus = OperationStatus.Complete;
notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
@ -2466,7 +2471,9 @@ internal class PluginInstallerWindow : Window, IDisposable
plugin.ReloadManifest();
}
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
// TODO: Work this out
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false))
.GetAwaiter().GetResult();
var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
.ContinueWith(
@ -3282,6 +3289,8 @@ internal class PluginInstallerWindow : Window, IDisposable
public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain.");
public static string ErrorModal_ProfileApplyFail => Loc.Localize("InstallerProfileApplyFail", "Failed to process collections.\nPlease restart your game and try again. If this error occurs again, please complain.");
public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount);
public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount);

View file

@ -119,7 +119,7 @@ internal class ProfileManagerWidget
var isEnabled = profile.IsEnabled;
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
{
Task.Run(() => profile.SetState(isEnabled))
Task.Run(() => profile.SetStateAsync(isEnabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
@ -228,9 +228,7 @@ internal class ProfileManagerWidget
if (ImGui.Selectable($"{plugin.Manifest.Name}###selector{plugin.Manifest.InternalName}"))
{
// TODO this sucks
profile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
Task.Run(() => profman.ApplyAllWantStates())
Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
}
@ -273,8 +271,12 @@ internal class ProfileManagerWidget
this.Reset();
// DeleteProfile() is sync, it doesn't apply and we are modifying the plugins collection. Will throw below when iterating
profman.DeleteProfile(profile);
Task.Run(() => profman.ApplyAllWantStates())
// TODO: DeleteProfileAsync should probably apply as well
Task.Run(async () =>
{
await profman.DeleteProfileAsync(profile);
await profman.ApplyAllWantStatesAsync();
})
.ContinueWith(t =>
{
this.installer.DisplayErrorContinuation(t, Locs.ErrorCouldNotChangeState);
@ -300,7 +302,7 @@ internal class ProfileManagerWidget
var isEnabled = profile.IsEnabled;
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
{
Task.Run(() => profile.SetState(isEnabled))
Task.Run(() => profile.SetStateAsync(isEnabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
@ -391,7 +393,7 @@ internal class ProfileManagerWidget
var enabled = plugin.IsEnabled;
if (ImGui.Checkbox($"###{this.editingProfileGuid}-{plugin.InternalName}", ref enabled))
{
Task.Run(() => profile.AddOrUpdate(plugin.InternalName, enabled))
Task.Run(() => profile.AddOrUpdateAsync(plugin.InternalName, enabled))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
}
@ -411,9 +413,8 @@ internal class ProfileManagerWidget
if (wantRemovePluginInternalName != null)
{
// TODO: handle error
profile.Remove(wantRemovePluginInternalName, false);
Task.Run(() => profman.ApplyAllWantStates())
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotRemove);
Task.Run(() => profile.RemoveAsync(wantRemovePluginInternalName, false))
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotRemove);
}
if (!didAny)