From cc7628d058192e750e366761ebd17a71c7c13315 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 31 Aug 2018 22:35:51 -0500 Subject: [PATCH] fixed broken broadcast events events don't get out of order when a invalid event line throws exception handle the stats history update with no change throwing DBConcurrencyException --- Application/EventParsers/IW4EventParser.cs | 64 +++++++++++-------- Application/GameEventHandler.cs | 26 +++++--- Application/IO/GameLogReaderHttp.cs | 4 -- Application/Server.cs | 4 +- Master/master/resources/authenticate.py | 15 +++-- Plugins/Stats/Helpers/StatManager.cs | 15 +++-- Plugins/Stats/Plugin.cs | 6 -- .../Stats/Web/Controllers/StatsController.cs | 33 ++++++++++ SharedLibraryCore/RCon/Connection.cs | 2 +- SharedLibraryCore/Server.cs | 8 ++- WebfrontCore/wwwroot/js/profile.js | 16 ++--- 11 files changed, 126 insertions(+), 67 deletions(-) diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index 0a72afc70..ebd5d93bd 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -18,29 +18,15 @@ namespace IW4MAdmin.Application.EventParsers string[] lineSplit = logLine.Split(';'); string eventType = lineSplit[0]; - // kill - if (eventType == "K") - { - if (!server.CustomCallback) - { - return new GameEvent() - { - Type = GameEvent.EventType.Kill, - Data = logLine, - Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)), - Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), - Owner = server - }; - } - } - if (eventType == "JoinTeam") { + var origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong()); + return new GameEvent() { Type = GameEvent.EventType.JoinTeam, Data = eventType, - Origin = server.GetPlayersAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong()), + Origin = origin, Owner = server }; } @@ -55,13 +41,15 @@ namespace IW4MAdmin.Application.EventParsers .Replace("\x15", "") .Trim(); + var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)); + if (message[0] == '!' || message[0] == '@') { return new GameEvent() { Type = GameEvent.EventType.Command, Data = message, - Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Origin = origin, Owner = server, Message = message }; @@ -71,33 +59,56 @@ namespace IW4MAdmin.Application.EventParsers { Type = GameEvent.EventType.Say, Data = message, - Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)), + Origin = origin, Owner = server, Message = message }; } } + if (eventType == "K") + { + if (!server.CustomCallback) + { + var origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)); + var target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)); + + return new GameEvent() + { + Type = GameEvent.EventType.Kill, + Data = logLine, + Origin = origin, + Owner = server + }; + } + } + + if (eventType == "ScriptKill") { + var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); + var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); return new GameEvent() { Type = GameEvent.EventType.ScriptKill, Data = logLine, - Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), - Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()), + Origin = origin, + Target = target, Owner = server }; } if (eventType == "ScriptDamage") { + var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); + var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()); + return new GameEvent() { Type = GameEvent.EventType.ScriptDamage, Data = logLine, - Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), - Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()), + Origin = origin, + Target = target, Owner = server }; } @@ -107,12 +118,15 @@ namespace IW4MAdmin.Application.EventParsers { if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success) { + var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()); + var target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); + return new GameEvent() { Type = GameEvent.EventType.Damage, Data = eventType, - Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()), - Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()), + Origin = origin, + Target = target, Owner = server }; } diff --git a/Application/GameEventHandler.cs b/Application/GameEventHandler.cs index 565afd459..3d60d5fb0 100644 --- a/Application/GameEventHandler.cs +++ b/Application/GameEventHandler.cs @@ -15,14 +15,11 @@ namespace IW4MAdmin.Application private readonly IManager Manager; static long NextEventId = 1; private readonly SortedList OutOfOrderEvents; - private readonly SemaphoreSlim ProcessingEvent; public GameEventHandler(IManager mgr) { Manager = mgr; OutOfOrderEvents = new SortedList(); - ProcessingEvent = new SemaphoreSlim(0); - ProcessingEvent.Release(); } public bool AddEvent(GameEvent gameEvent) @@ -30,13 +27,23 @@ namespace IW4MAdmin.Application #if DEBUG Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}"); #endif - while (OutOfOrderEvents.Values.FirstOrDefault()?.Id == Interlocked.Read(ref NextEventId)) + GameEvent sortedEvent = null; + lock (OutOfOrderEvents) + { + sortedEvent = OutOfOrderEvents.Values.FirstOrDefault(); + } + + while (sortedEvent?.Id == Interlocked.Read(ref NextEventId)) { lock (OutOfOrderEvents) { - var fixedEvent = OutOfOrderEvents.Values[0]; OutOfOrderEvents.RemoveAt(0); - AddEvent(fixedEvent); + } + AddEvent(sortedEvent); + + lock (OutOfOrderEvents) + { + sortedEvent = OutOfOrderEvents.Values.FirstOrDefault(); } } @@ -44,9 +51,10 @@ namespace IW4MAdmin.Application // event occurs if (gameEvent.Id == Interlocked.Read(ref NextEventId)) { - Manager.GetLogger().WriteDebug($"Starting to wait for event with id {gameEvent.Id}"); +#if DEBUG == true + Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed"); +#endif ((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent)); - Manager.GetLogger().WriteDebug($"Finished waiting for event with id {gameEvent.Id}"); Interlocked.Increment(ref NextEventId); } @@ -54,8 +62,10 @@ namespace IW4MAdmin.Application // so me must wait until the next expected one arrives else { +#if DEBUG == true Manager.GetLogger().WriteWarning("Incoming event is out of order"); Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead"); +#endif // this prevents multiple threads from adding simultaneously lock (OutOfOrderEvents) diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index f79e4428d..a019445e4 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -2,10 +2,7 @@ using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; -using System.IO; -using System.Net; using System.Net.Http; -using System.Text; namespace IW4MAdmin.Application.IO { @@ -76,7 +73,6 @@ namespace IW4MAdmin.Application.IO { try { - // todo: catch elsewhere var e = Parser.GetEvent(server, eventLine); #if DEBUG == true server.Logger.WriteDebug($"Parsed event with id {e.Id} from http"); diff --git a/Application/Server.cs b/Application/Server.cs index 757abe17d..a76c99141 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -529,9 +529,9 @@ namespace IW4MAdmin if (E.Type == GameEvent.EventType.Broadcast) { // this is a little ugly but I don't want to change the abstract class - if (E.Message != null) + if (E.Data != null) { - await E.Owner.ExecuteCommandAsync(E.Message); + await E.Owner.ExecuteCommandAsync(E.Data); } } diff --git a/Master/master/resources/authenticate.py b/Master/master/resources/authenticate.py index a62957448..f22886482 100644 --- a/Master/master/resources/authenticate.py +++ b/Master/master/resources/authenticate.py @@ -8,10 +8,11 @@ import datetime class Authenticate(Resource): def post(self): instance_id = request.json['id'] - if ctx.get_token(instance_id) is not False: - return { 'message' : 'that id already has a token'}, 401 - else: - expires = datetime.timedelta(days=30) - token = create_access_token(instance_id, expires_delta=expires) - ctx.add_token(instance_id, token) - return { 'access_token' : token }, 200 + #todo: see why this is failing + #if ctx.get_token(instance_id) is not False: + # return { 'message' : 'that id already has a token'}, 401 + #else: + expires = datetime.timedelta(days=30) + token = create_access_token(instance_id, expires_delta=expires) + ctx.add_token(instance_id, token) + return { 'access_token' : token }, 200 diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 3e43035f6..6eeec0bca 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -45,7 +45,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1); var iqClientRatings = (from rating in context.Set() -#if !DEBUG +#if DEBUG == false where rating.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime #endif where rating.RatingHistory.Client.Level != Player.Permission.Banned @@ -561,9 +561,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // update their performance -#if !DEBUG +//#if !DEBUG if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5) -#endif +//#endif { await UpdateStatHistory(attacker, attackerStats); attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; @@ -718,7 +718,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers ActivityAmount = clientStatsList.Sum(s => s.TimePlayed) }); - await ctx.SaveChangesAsync(); + try + { + await ctx.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException e) + { + + } } } diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 1c9d40b0f..328a19e62 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -13,8 +13,6 @@ using SharedLibraryCore.Services; using IW4MAdmin.Plugins.Stats.Config; using IW4MAdmin.Plugins.Stats.Helpers; using IW4MAdmin.Plugins.Stats.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Routing; namespace IW4MAdmin.Plugins.Stats { @@ -77,8 +75,6 @@ namespace IW4MAdmin.Plugins.Stats string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 13) { - // todo: remove me - E.Owner.Logger.WriteDebug($"Starting Add script hit (kill) for event with id {E.Id}"); await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); } @@ -95,8 +91,6 @@ namespace IW4MAdmin.Plugins.Stats killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0]; if (killInfo.Length >= 13) { - // todo: remove me - E.Owner.Logger.WriteDebug($"Starting Add script hit (damage) for event with id {E.Id}"); await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13]); } diff --git a/Plugins/Stats/Web/Controllers/StatsController.cs b/Plugins/Stats/Web/Controllers/StatsController.cs index 3cdbb4b3f..aea491eca 100644 --- a/Plugins/Stats/Web/Controllers/StatsController.cs +++ b/Plugins/Stats/Web/Controllers/StatsController.cs @@ -1,10 +1,14 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Storage; using SharedLibraryCore; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using WebfrontCore.Controllers; @@ -48,6 +52,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers }; var messages = await iqMessages.ToListAsync(); + string sql = iqMessages.ToSql(); return View("_MessageContext", messages); } @@ -78,4 +83,32 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers } } } + + public static class IQueryableExtensions + { + private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo(); + + private static readonly FieldInfo QueryCompilerField = typeof(EntityQueryProvider).GetTypeInfo().DeclaredFields.First(x => x.Name == "_queryCompiler"); + + private static readonly FieldInfo QueryModelGeneratorField = QueryCompilerTypeInfo.DeclaredFields.First(x => x.Name == "_queryModelGenerator"); + + private static readonly FieldInfo DataBaseField = QueryCompilerTypeInfo.DeclaredFields.Single(x => x.Name == "_database"); + + private static readonly PropertyInfo DatabaseDependenciesField = typeof(Database).GetTypeInfo().DeclaredProperties.Single(x => x.Name == "Dependencies"); + + public static string ToSql(this IQueryable query) where TEntity : class + { + var queryCompiler = (QueryCompiler)QueryCompilerField.GetValue(query.Provider); + var modelGenerator = (QueryModelGenerator)QueryModelGeneratorField.GetValue(queryCompiler); + var queryModel = modelGenerator.ParseQuery(query.Expression); + var database = (IDatabase)DataBaseField.GetValue(queryCompiler); + var databaseDependencies = (DatabaseDependencies)DatabaseDependenciesField.GetValue(database); + var queryCompilationContext = databaseDependencies.QueryCompilationContextFactory.Create(false); + var modelVisitor = (RelationalQueryModelVisitor)queryCompilationContext.CreateQueryModelVisitor(); + modelVisitor.CreateQueryExecutor(queryModel); + var sql = modelVisitor.Queries.First().ToString(); + + return sql; + } + } } diff --git a/SharedLibraryCore/RCon/Connection.cs b/SharedLibraryCore/RCon/Connection.cs index 2bb6e9980..21231dd8b 100644 --- a/SharedLibraryCore/RCon/Connection.cs +++ b/SharedLibraryCore/RCon/Connection.cs @@ -150,8 +150,8 @@ namespace SharedLibraryCore.RCon // will this really prevent flooding? if ((DateTime.Now - LastQuery).TotalMilliseconds < 350) { - //await Task.Delay(350); Thread.Sleep(350); + //await Task.Delay(350); } LastQuery = DateTime.Now; diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 47818d125..1ae5a2f6d 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -116,7 +116,7 @@ namespace SharedLibraryCore /// Message to be sent to all players public async Task Broadcast(String Message) { -#if !DEBUG +#if DEBUG == false string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message); #else Logger.WriteVerbose(Message.StripColors()); @@ -124,8 +124,12 @@ namespace SharedLibraryCore var e = new GameEvent() { Type = GameEvent.EventType.Broadcast, +#if DEBUG == true Data = Message, - Owner = this +#else + Data = formattedMessage, +#endif + Owner = this, }; Manager.GetEventHandler().AddEvent(e); diff --git a/WebfrontCore/wwwroot/js/profile.js b/WebfrontCore/wwwroot/js/profile.js index dc8ba4648..eff99c607 100644 --- a/WebfrontCore/wwwroot/js/profile.js +++ b/WebfrontCore/wwwroot/js/profile.js @@ -65,14 +65,14 @@ $(document).ready(function () { 'serverId': $(this).data('serverid'), 'when': $(this).data('when') }) - .done(function (response) { - $('.client-message-context').remove(); - location.after(response); - hideLoader(); - }) - .fail(function (jqxhr, textStatus, error) { - errorLoader(); - }); + .done(function (response) { + $('.client-message-context').remove(); + location.after(response); + hideLoader(); + }) + .fail(function (jqxhr, textStatus, error) { + errorLoader(); + }); }); /*