fix intermittent issue with game interface during connection loss with servers

This commit is contained in:
RaidMax 2022-06-01 11:25:11 -05:00
parent dd8c4f438f
commit 1f13f9122c
7 changed files with 154 additions and 67 deletions

View File

@ -24,7 +24,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-2037" /> <PackageReference Include="Jint" Version="3.0.0-beta-2038" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" /> <PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -792,8 +792,16 @@ namespace IW4MAdmin
/// <returns></returns> /// <returns></returns>
async Task<List<EFClient>[]> PollPlayersAsync() async Task<List<EFClient>[]> PollPlayersAsync()
{ {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
var currentClients = GetClientsAsList(); var currentClients = GetClientsAsList();
var statusResponse = await this.GetStatusAsync(Manager.CancellationToken); var statusResponse = await this.GetStatusAsync(tokenSource.Token);
if (statusResponse is null)
{
return null;
}
var polledClients = statusResponse.Clients.AsEnumerable(); var polledClients = statusResponse.Clients.AsEnumerable();
if (Manager.GetApplicationSettings().Configuration().IgnoreBots) if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
@ -930,6 +938,11 @@ namespace IW4MAdmin
var polledClients = await PollPlayersAsync(); var polledClients = await PollPlayersAsync();
if (polledClients is null)
{
return true;
}
foreach (var disconnectingClient in polledClients[1] foreach (var disconnectingClient in polledClients[1]
.Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */)) .Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */))
{ {

View File

@ -0,0 +1,12 @@
using System;
using System.Threading;
namespace IW4MAdmin.Application.Misc;
public class AsyncResult : IAsyncResult
{
public object AsyncState { get; set; }
public WaitHandle AsyncWaitHandle { get; set; }
public bool CompletedSynchronously { get; set; }
public bool IsCompleted { get; set; }
}

View File

@ -276,8 +276,8 @@ namespace IW4MAdmin.Application.Misc
{ {
_logger.LogDebug("OnLoad executing for {Name}", Name); _logger.LogDebug("OnLoad executing for {Name}", Name);
_scriptEngine.SetValue("_manager", manager); _scriptEngine.SetValue("_manager", manager);
_scriptEngine.SetValue("getDvar", GetDvarAsync); _scriptEngine.SetValue("getDvar", BeginGetDvar);
_scriptEngine.SetValue("setDvar", SetDvarAsync); _scriptEngine.SetValue("setDvar", BeginSetDvar);
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)"); _scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
return Task.CompletedTask; return Task.CompletedTask;
@ -343,7 +343,8 @@ namespace IW4MAdmin.Application.Misc
/// <param name="commands">commands value from jint parser</param> /// <param name="commands">commands value from jint parser</param>
/// <param name="scriptCommandFactory">factory to create the command from</param> /// <param name="scriptCommandFactory">factory to create the command from</param>
/// <returns></returns> /// <returns></returns>
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory) private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands,
IScriptCommandFactory scriptCommandFactory)
{ {
var commandList = new List<IManagerCommand>(); var commandList = new List<IManagerCommand>();
@ -454,65 +455,54 @@ namespace IW4MAdmin.Application.Misc
return commandList; return commandList;
} }
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted) private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted)
{
Task.Run(() =>
{ {
var tokenSource = new CancellationTokenSource(); var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5)); tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
string result = null;
var success = true;
try
{
result = server.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
}
catch
{
success = false;
}
_onProcessing.Wait(); server.BeginGetDvar(dvarName, result =>
{
var shouldRelease = false;
try try
{ {
_onProcessing.Wait(tokenSource.Token);
shouldRelease = true;
var (success, value) = (ValueTuple<bool, string>)result.AsyncState;
onCompleted.DynamicInvoke(JsValue.Undefined, onCompleted.DynamicInvoke(JsValue.Undefined,
new[] new[]
{ {
JsValue.FromObject(_scriptEngine, server), JsValue.FromObject(_scriptEngine, server),
JsValue.FromObject(_scriptEngine, dvarName), JsValue.FromObject(_scriptEngine, dvarName),
JsValue.FromObject(_scriptEngine, result), JsValue.FromObject(_scriptEngine, value),
JsValue.FromObject(_scriptEngine, success), JsValue.FromObject(_scriptEngine, success)
}); });
} }
finally finally
{ {
if (_onProcessing.CurrentCount == 0) if (_onProcessing.CurrentCount == 0 && shouldRelease)
{ {
_onProcessing.Release(); _onProcessing.Release();
} }
} }
}); }, tokenSource.Token);
} }
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
{ private void BeginSetDvar(Server server, string dvarName, string dvarValue, Delegate onCompleted)
Task.Run(() =>
{ {
var tokenSource = new CancellationTokenSource(); var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5)); tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
var success = true;
server.BeginSetDvar(dvarName, dvarValue, result =>
{
var shouldRelease = false;
try try
{ {
server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult(); _onProcessing.Wait(tokenSource.Token);
} shouldRelease = true;
catch var success = (bool)result.AsyncState;
{
success = false;
}
_onProcessing.Wait();
try
{
onCompleted.DynamicInvoke(JsValue.Undefined, onCompleted.DynamicInvoke(JsValue.Undefined,
new[] new[]
{ {
@ -522,15 +512,14 @@ namespace IW4MAdmin.Application.Misc
JsValue.FromObject(_scriptEngine, success) JsValue.FromObject(_scriptEngine, success)
}); });
} }
finally finally
{ {
if (_onProcessing.CurrentCount == 0) if (_onProcessing.CurrentCount == 0 && shouldRelease)
{ {
_onProcessing.Release(); _onProcessing.Release();
} }
} }
}); }, tokenSource.Token);
} }
} }

View File

@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static SharedLibraryCore.Server; using static SharedLibraryCore.Server;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -141,6 +142,30 @@ namespace IW4MAdmin.Application.RConParsers
}; };
} }
public void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default)
{
GetDvarAsync<string>(connection, dvarName, token: token).ContinueWith(action =>
{
if (action.Exception is null)
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = (true, action.Result.Value)
});
}
else
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = (false, (string)null)
});
}
}, token);
}
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default) public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
{ {
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token); var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
@ -196,6 +221,31 @@ namespace IW4MAdmin.Application.RConParsers
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0; return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
} }
public void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback,
CancellationToken token = default)
{
SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action =>
{
if (action.Exception is null)
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = true
});
}
else
{
callback?.Invoke(new AsyncResult
{
IsCompleted = true,
AsyncState = false
});
}
}, token);
}
private List<EFClient> ClientsFromStatus(string[] Status) private List<EFClient> ClientsFromStatus(string[] Status)
{ {
List<EFClient> StatusPlayers = new List<EFClient>(); List<EFClient> StatusPlayers = new List<EFClient>();

View File

@ -56,6 +56,8 @@ namespace SharedLibraryCore.Interfaces
/// <returns></returns> /// <returns></returns>
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default); Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default);
/// <summary> /// <summary>
/// set value of DVAR by name /// set value of DVAR by name
/// </summary> /// </summary>
@ -66,6 +68,8 @@ namespace SharedLibraryCore.Interfaces
/// <returns></returns> /// <returns></returns>
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default); Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback, CancellationToken token = default);
/// <summary> /// <summary>
/// executes a console command on the server /// executes a console command on the server
/// </summary> /// </summary>

View File

@ -774,6 +774,11 @@ namespace SharedLibraryCore
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token); return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
} }
public static void BeginGetDvar(this Server server, string dvarName, AsyncCallback callback, CancellationToken token = default)
{
server.RconParser.BeginGetDvar(server.RemoteConnection, dvarName, callback, token);
}
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName, public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
T fallbackValue = default) T fallbackValue = default)
{ {
@ -809,6 +814,12 @@ namespace SharedLibraryCore
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token); await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
} }
public static void BeginSetDvar(this Server server, string dvarName, object dvarValue,
AsyncCallback callback, CancellationToken token = default)
{
server.RconParser.BeginSetDvar(server.RemoteConnection, dvarName, dvarValue, callback, token);
}
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue) public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
{ {
await SetDvarAsync(server, dvarName, dvarValue, default); await SetDvarAsync(server, dvarName, dvarValue, default);
@ -824,9 +835,17 @@ namespace SharedLibraryCore
return await ExecuteCommandAsync(server, commandName, default); return await ExecuteCommandAsync(server, commandName, default);
} }
public static Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token) public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
{ {
return server.RconParser.GetStatusAsync(server.RemoteConnection, token); try
{
return await server.RconParser.GetStatusAsync(server.RemoteConnection, token);
}
catch (TaskCanceledException)
{
return null;
}
} }
/// <summary> /// <summary>