From 7526f86dabe6721662f1e2475fa71c4e80d6508d Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 26 Aug 2022 12:07:43 -0500 Subject: [PATCH] fix issues with game interface reconnecting after rcon connection lost --- Application/Misc/ScriptPlugin.cs | 85 ++++++++++++++++++--- Application/Misc/ScriptPluginTimerHelper.cs | 5 +- Application/RConParsers/BaseRConParser.cs | 8 +- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/Application/Misc/ScriptPlugin.cs b/Application/Misc/ScriptPlugin.cs index c484a0ae7..a9d18cedc 100644 --- a/Application/Misc/ScriptPlugin.cs +++ b/Application/Misc/ScriptPlugin.cs @@ -42,6 +42,7 @@ namespace IW4MAdmin.Application.Misc private Engine _scriptEngine; private readonly string _fileName; private readonly SemaphoreSlim _onProcessing = new(1, 1); + private readonly SemaphoreSlim _onDvarActionComplete = new(1, 1); private bool _successfullyLoaded; private readonly List _registeredCommandNames; private readonly ILogger _logger; @@ -458,18 +459,16 @@ namespace IW4MAdmin.Application.Misc private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted) { - var tokenSource = new CancellationTokenSource(); - tokenSource.CancelAfter(TimeSpan.FromSeconds(15)); - - server.BeginGetDvar(dvarName, result => + void OnComplete(IAsyncResult result) { - var shouldRelease = false; try { - _onProcessing.Wait(tokenSource.Token); - shouldRelease = true; var (success, value) = (ValueTuple)result.AsyncState; + _logger.LogDebug("Waiting for onDvarActionComplete -> get"); + _onDvarActionComplete.Wait(); + _logger.LogDebug("Completed wait for onDvarActionComplete -> get"); + onCompleted.DynamicInvoke(JsValue.Undefined, new[] { @@ -479,9 +478,43 @@ namespace IW4MAdmin.Application.Misc JsValue.FromObject(_scriptEngine, success) }); } + catch (JavaScriptException ex) + { + using (LogContext.PushProperty("Server", server.ToString())) + { + _logger.LogError(ex, "Could complete BeginGetDvar for {Filename} {@Location}", + Path.GetFileName(_fileName), ex.Location); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not complete {BeginGetDvar} for {Class}", nameof(BeginGetDvar), Name); + } + finally + { + if (_onDvarActionComplete.CurrentCount == 0) + { + _onDvarActionComplete.Release(); + } + } + } + + var tokenSource = new CancellationTokenSource(); + tokenSource.CancelAfter(TimeSpan.FromSeconds(15)); + + server.BeginGetDvar(dvarName, result => + { + var shouldRelease = false; + try + { + _onProcessing.Wait(tokenSource.Token); + shouldRelease = true; + } finally { + OnComplete(result); + if (_onProcessing.CurrentCount == 0 && shouldRelease) { _onProcessing.Release(); @@ -495,15 +528,15 @@ namespace IW4MAdmin.Application.Misc var tokenSource = new CancellationTokenSource(); tokenSource.CancelAfter(TimeSpan.FromSeconds(15)); - server.BeginSetDvar(dvarName, dvarValue, result => + void OnComplete(IAsyncResult result) { - var shouldRelease = false; try { - _onProcessing.Wait(tokenSource.Token); - shouldRelease = true; var success = (bool)result.AsyncState; + _logger.LogDebug("Waiting for onDvarActionComplete -> set"); + _onDvarActionComplete.Wait(); + _logger.LogDebug("Completed wait for onDvarActionComplete -> set"); onCompleted.DynamicInvoke(JsValue.Undefined, new[] { @@ -513,8 +546,38 @@ namespace IW4MAdmin.Application.Misc JsValue.FromObject(_scriptEngine, success) }); } + catch (JavaScriptException ex) + { + using (LogContext.PushProperty("Server", server.ToString())) + { + _logger.LogError(ex, "Could complete BeginSetDvar for {Filename} {@Location}", + Path.GetFileName(_fileName), ex.Location); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not complete {BeginSetDvar} for {Class}", nameof(BeginSetDvar), Name); + } finally { + if (_onDvarActionComplete.CurrentCount == 0) + { + _onDvarActionComplete.Release(); + } + } + } + + server.BeginSetDvar(dvarName, dvarValue, result => + { + var shouldRelease = false; + try + { + _onProcessing.Wait(tokenSource.Token); + shouldRelease = true; + } + finally + { + OnComplete(result); if (_onProcessing.CurrentCount == 0 && shouldRelease) { _onProcessing.Release(); diff --git a/Application/Misc/ScriptPluginTimerHelper.cs b/Application/Misc/ScriptPluginTimerHelper.cs index cbe928a99..cc997be1a 100644 --- a/Application/Misc/ScriptPluginTimerHelper.cs +++ b/Application/Misc/ScriptPluginTimerHelper.cs @@ -111,7 +111,7 @@ public class ScriptPluginTimerHelper : IScriptPluginTimerHelper { return; } - + _logger.LogDebug("-Releasing OnTick for timer"); _onDependentAction?.Release(1); } private void OnTick() @@ -128,7 +128,8 @@ public class ScriptPluginTimerHelper : IScriptPluginTimerHelper _onRunningTick.Reset(); // the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously - _onDependentAction?.WaitAsync().Wait(); + _onDependentAction?.Wait(); + _logger.LogDebug("+Running OnTick for timer"); var start = DateTime.Now; _jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined }); _logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds); diff --git a/Application/RConParsers/BaseRConParser.cs b/Application/RConParsers/BaseRConParser.cs index c94a5adb2..5ae819b7f 100644 --- a/Application/RConParsers/BaseRConParser.cs +++ b/Application/RConParsers/BaseRConParser.cs @@ -147,7 +147,7 @@ namespace IW4MAdmin.Application.RConParsers { GetDvarAsync(connection, dvarName, token: token).ContinueWith(action => { - if (action.Exception is null) + if (action.Exception is null && !action.IsCanceled) { callback?.Invoke(new AsyncResult { @@ -164,7 +164,7 @@ namespace IW4MAdmin.Application.RConParsers AsyncState = (false, (string)null) }); } - }, token); + }, CancellationToken.None); } public virtual async Task GetStatusAsync(IRConConnection connection, CancellationToken token = default) @@ -227,7 +227,7 @@ namespace IW4MAdmin.Application.RConParsers { SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action => { - if (action.Exception is null) + if (action.Exception is null && !action.IsCanceled) { callback?.Invoke(new AsyncResult { @@ -244,7 +244,7 @@ namespace IW4MAdmin.Application.RConParsers AsyncState = false }); } - }, token); + }, CancellationToken.None); } private List ClientsFromStatus(string[] Status)