using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using DFEngine.Logging; namespace DFEngine.Network { /// /// The callback type for a network message handler. /// /// The message being handled. /// The client that sent the message. public delegate void NetworkMessageHandlerMethod(NetworkMessage message, NetworkConnection client); /// /// Class to register handlers for network messages. /// public class NetworkMessageHandler { /// /// The maximum time a task can run for before it is considered dead /// (can be used for debugging any locking issues with certain areas of code). /// public const int MaxTaskRuntime = 300; // In seconds, so 5 minutes. /// /// Returns the number of handlers. /// public static int Count => Handlers.Count; /// /// All registered message handlers. /// private static readonly Dictionary Handlers = new Dictionary(); /// /// Used for running asynchronous tasks, in this case executing packets. /// private static readonly TaskFactory HandlerFactory = new TaskFactory(TaskCreationOptions.PreferFairness, TaskContinuationOptions.None); /// /// Currently running tasks to keep track of what the current load is. /// private static readonly ConcurrentDictionary RunningHandlers = new ConcurrentDictionary(); /// /// Invokes the message handler. /// public static void Invoke(NetworkMessageHandlerMethod method, NetworkMessage message, NetworkConnection connection) { if (method == null || !connection.IsConnected) { return; } SocketLog.Write(SocketLogLevel.Debug, $"Calling handler for {message.Command}"); var stopwatch = new Stopwatch(); stopwatch.Start(); var cancelSource = new CancellationTokenSource(); var token = cancelSource.Token; var task = HandlerFactory.StartNew(() => { method.Invoke(message, connection); token.ThrowIfCancellationRequested(); }, token); RunningHandlers.TryAdd(task.Id, task); try { if (!task.Wait(MaxTaskRuntime * 1000, token)) { cancelSource.Cancel(); } } catch (AggregateException ex) { foreach (var e in ex.Flatten().InnerExceptions) { SocketLog.Write(SocketLogLevel.Exception, $"Unhandled exception: {e.Message} - {e.StackTrace}"); } connection.Disconnect(); } catch (OperationCanceledException) { connection.Disconnect(); } finally { stopwatch.Stop(); RunningHandlers.TryRemove(task.Id, out var unused); cancelSource.Dispose(); Object.Destroy(message); SocketLog.Write(SocketLogLevel.Debug, $"Handler took {stopwatch.ElapsedMilliseconds}ms to complete."); } } /// /// Registers a message handler to the collection of handlers. /// /// The command being handled. /// The method to invoke. public static void Store(NetworkCommand command, NetworkMessageHandlerMethod method) { if (!Handlers.ContainsKey(command)) { Handlers.Add(command, method); } } /// /// Tries to get a message handler from the collection. /// /// The message's command. /// The handler method. /// public static bool TryFetch(NetworkCommand command, out NetworkMessageHandlerMethod method) { return Handlers.TryGetValue(command, out method); } } }