1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-11 15:52:25 -05:00

moved event API stuff around

finally fixed threading issue (which actually had to do with IW4x log outputs being out of sync (not an issue with my code). What a lot of headache over something that wasn't my fault.
This commit is contained in:
RaidMax
2018-08-30 20:53:00 -05:00
parent b6f37035a1
commit 18aa6e85fc
25 changed files with 254 additions and 164 deletions

View File

@ -1,83 +0,0 @@
using System;
using System.Collections.Generic;
using SharedLibraryCore;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Application.API
{
class EventApi : IEventApi
{
private const int MaxEvents = 100;
private Queue<EventInfo> RecentEvents = new Queue<EventInfo>();
public IEnumerable<EventInfo> GetEvents(bool shouldConsume)
{
var eventList = RecentEvents.ToArray();
// clear queue if events should be consumed
if (shouldConsume)
{
RecentEvents.Clear();
}
return eventList;
}
public void OnServerEvent(object sender, GameEvent E)
{
// don't want to clog up the api with unknown events
if (E.Type == GameEvent.EventType.Unknown)
return;
var apiEvent = new EventInfo()
{
ExtraInfo = E.Extra?.ToString() ?? E.Data,
GameInfo = new EntityInfo()
{
Name = E.Owner.GameName.ToString(),
Id = (int)E.Owner.GameName
},
OwnerEntity = new EntityInfo()
{
Name = E.Owner.Hostname,
Id = E.Owner.GetHashCode()
},
OriginEntity = E.Origin == null ? null : new EntityInfo()
{
Id = E.Origin.ClientId,
Name = E.Origin.Name
},
TargetEntity = E.Target == null ? null : new EntityInfo()
{
Id = E.Target.ClientId,
Name = E.Target.Name
},
EventType = new EntityInfo()
{
Id = (int)E.Type,
Name = E.Type.ToString()
},
EventTime = E.Time
};
// add the new event to the list
AddNewEvent(apiEvent);
}
/// <summary>
/// Adds event to the list and removes first added if reached max capacity
/// </summary>
/// <param name="info">EventInfo to add</param>
private void AddNewEvent(EventInfo info)
{
// remove the first added event
if (RecentEvents.Count >= MaxEvents)
RecentEvents.Dequeue();
RecentEvents.Enqueue(info);
}
}
}

View File

@ -5,7 +5,7 @@
<TargetFramework>netcoreapp2.1</TargetFramework>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.1.5</Version>
<Version>2.1.6</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>

View File

@ -1,27 +1,72 @@
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
class GameEventHandler : IEventHandler
{
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 void AddEvent(GameEvent gameEvent)
public bool AddEvent(GameEvent gameEvent)
{
#if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}");
#endif
// todo: later
((Manager as ApplicationManager).OnServerEvent)(this, new ApplicationManager.GameEventArgs(null, false, gameEvent));
while (OutOfOrderEvents.Values.FirstOrDefault()?.Id == Interlocked.Read(ref NextEventId))
{
lock (OutOfOrderEvents)
{
var fixedEvent = OutOfOrderEvents.Values[0];
OutOfOrderEvents.RemoveAt(0);
AddEvent(fixedEvent);
}
}
// both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
// event occurs
if (gameEvent.Id == Interlocked.Read(ref NextEventId))
{
Manager.GetLogger().WriteDebug($"Starting to wait for event with id {gameEvent.Id}");
((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);
}
// a "newer" event has been added before and "older" one has been added (due to threads and context switching)
// so me must wait until the next expected one arrives
else
{
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");
// this prevents multiple threads from adding simultaneously
lock (OutOfOrderEvents)
{
if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
{
OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
}
}
}
return true;
}
}
}

View File

@ -1,7 +1,9 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO
@ -31,44 +33,36 @@ namespace IW4MAdmin.Application.IO
{
Reader = new GameLogReader(gameLogPath, server.EventParser);
}
Server = server;
Task.Run(async () =>
{
while (!server.Manager.ShutdownRequested())
{
if ((server.Manager as ApplicationManager).IsInitialized)
{
OnEvent(new EventState()
{
Log = server.Manager.GetLogger(),
ServerId = server.ToString()
});
}
await Task.Delay(100);
}
});
}
private void OnEvent(object state)
public void PollForChanges()
{
long newLength = Reader.Length;
try
while (!Server.Manager.ShutdownRequested())
{
UpdateLogEvents(newLength);
}
if ((Server.Manager as ApplicationManager).IsInitialized)
{
try
{
UpdateLogEvents();
}
catch (Exception e)
{
((EventState)state).Log.WriteWarning($"Failed to update log event for {((EventState)state).ServerId}");
((EventState)state).Log.WriteDebug($"Exception: {e.Message}");
((EventState)state).Log.WriteDebug($"StackTrace: {e.StackTrace}");
catch (Exception e)
{
Server.Logger.WriteWarning($"Failed to update log event for {Server.GetHashCode()}");
Server.Logger.WriteDebug($"Exception: {e.Message}");
Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
}
}
Thread.Sleep(Reader.UpdateInterval);
}
}
private void UpdateLogEvents(long fileSize)
private void UpdateLogEvents()
{
long fileSize = Reader.Length;
if (PreviousFileSize == 0)
PreviousFileSize = fileSize;
@ -79,9 +73,12 @@ namespace IW4MAdmin.Application.IO
PreviousFileSize = fileSize;
var events = Reader.EventsFromLog(Server, fileDiff, 0);
var events = Reader.ReadEventsFromLog(Server, fileDiff, 0);
foreach (var ev in events)
{
Server.Manager.GetEventHandler().AddEvent(ev);
}
PreviousFileSize = fileSize;
}

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.IO
public long Length => new FileInfo(LogFile).Length;
public int UpdateInterval => 100;
public int UpdateInterval => 300;
public GameLogReader(string logFile, IEventParser parser)
{
@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.IO
Parser = parser;
}
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition)
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
// allocate the bytes for the new log lines
List<string> logLines = new List<string>();

View File

@ -29,11 +29,12 @@ namespace IW4MAdmin.Application.IO
{
using (var cl = new HttpClient())
{
using (var re = cl.GetAsync($"{LogFile}?length=1").Result)
using (var re = cl.GetAsync($"{LogFile}&length=1").Result)
{
using (var content = re.Content)
{
return Convert.ToInt64(content.ReadAsStringAsync().Result ?? "0");
string response = content.ReadAsStringAsync().Result ?? "0";
return Convert.ToInt64(response);
}
}
}
@ -42,12 +43,15 @@ namespace IW4MAdmin.Application.IO
public int UpdateInterval => 1000;
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition)
public ICollection<GameEvent> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
#if DEBUG == true
server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log");
#endif
string log;
using (var cl = new HttpClient())
{
using (var re = cl.GetAsync($"{LogFile}?start={fileSizeDiff}").Result)
using (var re = cl.GetAsync($"{LogFile}&start={fileSizeDiff}").Result)
{
using (var content = re.Content)
{
@ -55,18 +59,29 @@ namespace IW4MAdmin.Application.IO
}
}
}
#if DEBUG == true
server.Logger.WriteDebug($"retrieved events from http log");
#endif
List<GameEvent> events = new List<GameEvent>();
string[] lines = log.Split(Environment.NewLine);
#if DEBUG == true
server.Logger.WriteDebug($"Begin parse of {lines.Length} lines from http log");
#endif
// parse each line
foreach (string eventLine in log.Split(Environment.NewLine))
foreach (string eventLine in lines)
{
if (eventLine.Length > 0)
{
try
{
// todo: catch elsewhere
events.Add(Parser.GetEvent(server, eventLine));
var e = Parser.GetEvent(server, eventLine);
#if DEBUG == true
server.Logger.WriteDebug($"Parsed event with id {e.Id} from http");
#endif
events.Add(e);
}
catch (Exception e)

View File

@ -106,7 +106,7 @@ namespace IW4MAdmin.Application
ServerManager.Init().Wait();
var consoleTask = Task.Run(() =>
var consoleTask = Task.Run(async () =>
{
String userInput;
Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer();
@ -136,7 +136,7 @@ namespace IW4MAdmin.Application
};
ServerManager.GetEventHandler().AddEvent(E);
E.OnProcessed.Wait(5000);
await E.OnProcessed.WaitAsync(30 * 1000);
}
Console.Write('>');

View File

@ -22,6 +22,7 @@ using System.Text;
using IW4MAdmin.Application.API.Master;
using System.Reflection;
using SharedLibraryCore.Database;
using SharedLibraryCore.Events;
namespace IW4MAdmin.Application
{
@ -33,7 +34,6 @@ namespace IW4MAdmin.Application
public ILogger Logger { get; private set; }
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
//public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
// define what the delagate function looks like
public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
// expose the event handler so we can execute the events
@ -48,22 +48,10 @@ namespace IW4MAdmin.Application
readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc;
public BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
EventApi Api;
GameEventHandler Handler;
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
public GameEventArgs(Exception error, bool cancelled, GameEvent userState) : base(error, cancelled, userState)
{
Event = userState;
}
public GameEvent Event { get; }
}
private ApplicationManager()
{
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
@ -75,13 +63,12 @@ namespace IW4MAdmin.Application
AliasSvc = new AliasService();
PenaltySvc = new PenaltyService();
PrivilegedClients = new Dictionary<int, Player>();
Api = new EventApi();
//ServerEventOccurred += Api.OnServerEvent;
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnQuit = new ManualResetEventSlim();
PageList = new PageList();
OnServerEvent += OnServerEventAsync;
OnServerEvent += EventApi.OnGameEvent;
}
private async void OnServerEventAsync(object sender, GameEventArgs args)
@ -108,7 +95,7 @@ namespace IW4MAdmin.Application
return;
}
await newEvent.Owner.ExecuteEvent(newEvent);
//// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
@ -119,7 +106,20 @@ namespace IW4MAdmin.Application
// add the delayed event to the queue
while(events.Count > 0)
{
var e = events.Dequeue();
var oldEvent = events.Dequeue();
var e = new GameEvent()
{
Type = oldEvent.Type,
Origin = newEvent.Origin,
Data = oldEvent.Data,
Extra = oldEvent.Extra,
Owner = oldEvent.Owner,
Message = oldEvent.Message,
Target = oldEvent.Target,
OnProcessed = oldEvent.OnProcessed,
Remote = oldEvent.Remote
};
e.Origin = newEvent.Origin;
// check if the target was assigned
@ -140,9 +140,10 @@ namespace IW4MAdmin.Application
}
}
Api.OnServerEvent(this, newEvent);
await newEvent.Owner.ExecuteEvent(newEvent);
#if DEBUG
Logger.WriteDebug("Processed Event");
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
#endif
}
@ -169,7 +170,7 @@ namespace IW4MAdmin.Application
Logger.WriteDebug("Error Trace: " + ex.StackTrace);
}
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
newEvent.OnProcessed.Release();
}
public IList<Server> GetServers()
@ -423,7 +424,15 @@ namespace IW4MAdmin.Application
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}");
// add the start event for this server
Handler.AddEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, ServerInstance));
var e = new GameEvent()
{
Type = GameEvent.EventType.Start,
Data = $"{ServerInstance.GameName} started",
Owner = ServerInstance
};
Handler.AddEvent(e);
}
catch (ServerException e)
@ -562,7 +571,6 @@ namespace IW4MAdmin.Application
public PenaltyService GetPenaltyService() => PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public IEventApi GetEventApi() => Api;
public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler;

View File

@ -27,11 +27,9 @@ namespace IW4MAdmin
{
private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex;
private GameLogEventDetection LogEvent;
private ClientAuthentication AuthQueue;
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
{
AuthQueue = new ClientAuthentication();
}
public override int GetHashCode()
@ -603,7 +601,7 @@ namespace IW4MAdmin
try
{
var polledClients = await PollPlayersAsync();
var waiterList = new List<ManualResetEventSlim>();
var waiterList = new List<SemaphoreSlim>();
foreach (var disconnectingClient in polledClients[1])
{
@ -625,7 +623,7 @@ namespace IW4MAdmin
waiterList.Add(e.OnProcessed);
}
// wait for all the disconnect tasks to finish
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000))));
await Task.WhenAll(waiterList.Select(t => t.WaitAsync()));
waiterList.Clear();
// this are our new connecting clients
@ -643,7 +641,7 @@ namespace IW4MAdmin
}
// wait for all the connect tasks to finish
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000))));
await Task.WhenAll(waiterList.Select(t => t.WaitAsync()));
if (ConnectionErrors > 0)
{
@ -835,7 +833,7 @@ namespace IW4MAdmin
logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{logPath}", @"[A-Z]:", "");
}
if (!File.Exists(logPath))
if (!File.Exists(logPath) && !logPath.StartsWith("http"))
{
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG
@ -850,6 +848,8 @@ namespace IW4MAdmin
}
Logger.WriteInfo($"Log file is {logPath}");
Task.Run(() => LogEvent.PollForChanges());
#if !DEBUG
await Broadcast(loc["BROADCAST_ONLINE"]);
#endif
@ -1002,15 +1002,17 @@ namespace IW4MAdmin
// this is set only because they're still in the server.
Target.Level = Player.Permission.Banned;
// let the api know that a ban occured
Manager.GetEventHandler().AddEvent(new GameEvent()
var e = new GameEvent()
{
Type = GameEvent.EventType.Ban,
Data = Message,
Origin = Origin,
Target = Target,
Owner = this
});
};
// let the api know that a ban occured
Manager.GetEventHandler().AddEvent(e);
#if !DEBUG
string formattedString = String.Format(RconParser.GetCommandPrefixes().Kick, Target.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{Message} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");