From 6b49c92ef98c0020aaa8b5dc873e40deeba6d9d1 Mon Sep 17 00:00:00 2001 From: defnotken Date: Mon, 29 Dec 2025 08:41:32 -0600 Subject: [PATCH 1/3] Add a timeout to prevent deadlock of application data --- .../PlayerData/Pairs/PairHandlerAdapter.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index 82c4a94..bec8322 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -1420,10 +1420,9 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa } private Task? _pairDownloadTask; - private Task _visibilityGraceTask; private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, - bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken) + bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken) { var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false); try @@ -1577,24 +1576,37 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa RecordFailure("Handler not available for application", "HandlerUnavailable"); return; } + _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); - var appToken = _applicationCancellationTokenSource?.Token; - while ((!_applicationTask?.IsCompleted ?? false) - && !downloadToken.IsCancellationRequested - && (!appToken?.IsCancellationRequested ?? false)) + if (_applicationTask != null && !_applicationTask.IsCompleted) { - Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName); - await Task.Delay(250).ConfigureAwait(false); + Logger.LogDebug("[BASE-{appBase}] Cancelling current data application (Id: {id}) for player ({handler})", applicationBase, _applicationId, PlayerName); + + var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(downloadToken, timeoutCts.Token); + + try + { + await _applicationTask.WaitAsync(combinedCts.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + Logger.LogWarning("[BASE-{appBase}] Timeout waiting for application task {id} to complete, proceeding anyway", applicationBase, _applicationId); + } + finally + { + timeoutCts.Dispose(); + combinedCts.Dispose(); + } } - if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) + if (downloadToken.IsCancellationRequested) { _forceFullReapply = true; RecordFailure("Application cancelled", "Cancellation"); return; } - _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); var token = _applicationCancellationTokenSource.Token; _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token); From 27d4da4615c57df034d7321739944c2d0707eebc Mon Sep 17 00:00:00 2001 From: defnotken Date: Mon, 29 Dec 2025 08:47:51 -0600 Subject: [PATCH 2/3] thought a variable was unused. --- LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index bec8322..71cdda7 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -1420,6 +1420,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa } private Task? _pairDownloadTask; + private Task _visibilityGraceTask; private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken) From 9ea0571e825254e71bb7e4e4df1b24989bc0c943 Mon Sep 17 00:00:00 2001 From: defnotken Date: Tue, 30 Dec 2025 14:29:38 +0000 Subject: [PATCH 3/3] Lower Time out --- LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index 71cdda7..b0f2710 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -1583,7 +1583,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { Logger.LogDebug("[BASE-{appBase}] Cancelling current data application (Id: {id}) for player ({handler})", applicationBase, _applicationId, PlayerName); - var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(downloadToken, timeoutCts.Token); try