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
This commit is contained in:
RaidMax 2018-08-31 22:35:51 -05:00
parent 46bdc2ac33
commit cc7628d058
11 changed files with 126 additions and 67 deletions

View File

@ -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
};
}

View File

@ -15,14 +15,11 @@ namespace IW4MAdmin.Application
private readonly IManager Manager;
static long NextEventId = 1;
private readonly SortedList<long, GameEvent> OutOfOrderEvents;
private readonly SemaphoreSlim ProcessingEvent;
public GameEventHandler(IManager mgr)
{
Manager = mgr;
OutOfOrderEvents = new SortedList<long, GameEvent>();
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)

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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

View File

@ -45,7 +45,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var iqClientRatings = (from rating in context.Set<EFRating>()
#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)
{
}
}
}

View File

@ -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]);
}

View File

@ -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<TEntity>(this IQueryable<TEntity> 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<TEntity>(queryModel);
var sql = modelVisitor.Queries.First().ToString();
return sql;
}
}
}

View File

@ -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;

View File

@ -116,7 +116,7 @@ namespace SharedLibraryCore
/// <param name="Message">Message to be sent to all players</param>
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);

View File

@ -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();
});
});
/*