Skip to content

Commit

Permalink
Fix kestrel deadlock in update procedure
Browse files Browse the repository at this point in the history
This time for good
  • Loading branch information
JustArchi committed Oct 27, 2024
1 parent 8879ed7 commit 6dddaa5
Showing 1 changed file with 25 additions and 16 deletions.
41 changes: 25 additions & 16 deletions ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.IPC.Requests;
Expand Down Expand Up @@ -200,23 +201,31 @@ public async Task<ActionResult<GenericResponse<string>>> UpdatePost([FromBody] U
// In this case, we have very little opportunity to do anything, especially we will not have access to the return value of the action
// That's because update action will synchronously stop the kestrel, and wait for it before proceeding with an update, and that'll wait for us finishing the request, never happening
// Therefore, we'll allow this action to proceed while listening for application shutdown request, if it happens, we'll do our best by getting alternative signal that update is proceeding
bool success;
string? message = null;
Version? version;

try {
(success, message, version) = await Task.Run(() => Actions.Update(request.Channel, request.Forced), ApplicationLifetime.ApplicationStopping).ConfigureAwait(false);
} catch (TaskCanceledException) {
// It's almost guaranteed that this is the result of update process requesting kestrel shutdown
// However, we're still going to check PendingVersionUpdate, which should be set by the update process as alternative way to inform us about pending update
version = PendingVersionUpdate;
success = version != null;
}
TaskCompletionSource<bool> applicationStopping = new();

if (string.IsNullOrEmpty(message)) {
message = success ? Strings.Success : Strings.WarningFailed;
}
CancellationTokenRegistration applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(() => applicationStopping.SetResult(true));

await using (applicationStoppingRegistration.ConfigureAwait(false)) {
Task<(bool Success, string? Message, Version? Version)> updateTask = Actions.Update(request.Channel, request.Forced);

return Ok(new GenericResponse<string>(success, message, version?.ToString()));
bool success;
string? message = null;
Version? version;

if (await Task.WhenAny(updateTask, applicationStopping.Task).ConfigureAwait(false) == updateTask) {
(success, message, version) = await updateTask.ConfigureAwait(false);
} else {
// It's almost guaranteed that this is the result of update process requesting kestrel shutdown
// However, we're still going to check PendingVersionUpdate, which should be set by the update process as alternative way to inform us about pending update
version = PendingVersionUpdate;
success = version != null;
}

if (string.IsNullOrEmpty(message)) {
message = success ? Strings.Success : Strings.WarningFailed;
}

return Ok(new GenericResponse<string>(success, message, version?.ToString()));
}
}
}

0 comments on commit 6dddaa5

Please sign in to comment.