diff --git a/TGS.Interface/Components/Interop.cs b/TGS.Interface/Components/Interop.cs index 80dc2c7c7c..4ebf21bf90 100644 --- a/TGS.Interface/Components/Interop.cs +++ b/TGS.Interface/Components/Interop.cs @@ -12,7 +12,7 @@ namespace TGS.Interface.Components /// Called from /world/ExportService(command) /// /// The command to run - /// on success, on failure + /// [OperationContract] bool InteropMessage(string command); } diff --git a/TGS.Server/CallLogger.cs b/TGS.Server/CallLogger.cs new file mode 100644 index 0000000000..7ddb7e409d --- /dev/null +++ b/TGS.Server/CallLogger.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ServiceModel.Channels; +using System.ServiceModel.Description; +using System.ServiceModel.Dispatcher; + +namespace TGS.Server +{ + sealed class AddCallLoggerBehavior : IOperationBehavior + { + public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) + { + } + + public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) + { + clientOperation.ClientParameterInspectors.Add(new CallLogger()); + } + + public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) + { + dispatchOperation.ParameterInspectors.Add(new CallLogger()); + } + + public void Validate(OperationDescription operationDescription) + { + } + } + sealed class CallLogger : IParameterInspector + { + static readonly IDictionary ActiveCalls = new Dictionary(); + + public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) + { + lock (ActiveCalls) + --ActiveCalls[operationName]; + } + + public object BeforeCall(string operationName, object[] inputs) + { + lock (ActiveCalls) + { + string res = String.Empty; + foreach (var I in ActiveCalls) + { + if (I.Value > 0) + res += Environment.NewLine + I.Key + ": " + I.Value; + } + if (res != String.Empty) + { + res = String.Format("Starting call {0}. Warning: other calls in progress!{1}", operationName, res); + Server.Logger.WriteInfo(res, EventID.CallTracking, 0); + } + if (!ActiveCalls.ContainsKey(operationName)) + ActiveCalls.Add(operationName, 0); + ++ActiveCalls[operationName]; + return null; + } + } + } +} diff --git a/TGS.Server/EventID.cs b/TGS.Server/EventID.cs index 9981fd199f..05043646ef 100644 --- a/TGS.Server/EventID.cs +++ b/TGS.Server/EventID.cs @@ -331,5 +331,9 @@ namespace TGS.Server /// Warning: When a testmerge commit failed to be published /// ReferencePush = 7700, + /// + /// Info: When a call starts and another call hasn't completed + /// + CallTracking = 7800, } } diff --git a/TGS.Server/Instance/Instance.cs b/TGS.Server/Instance/Instance.cs index e28d58dc72..a5663c1bed 100644 --- a/TGS.Server/Instance/Instance.cs +++ b/TGS.Server/Instance/Instance.cs @@ -1,7 +1,7 @@ using System; -using System.Diagnostics; using System.IO; using System.ServiceModel; +using System.Threading; using TGS.Interface.Components; namespace TGS.Server @@ -104,6 +104,23 @@ namespace TGS.Server return Path.Combine(Config.Directory, path); } + /// + /// Dumps the status of all locks in the + /// + public void DumpLocks() + { + var res = Monitor.TryEnter(RepoLock); + string rb; + if (res) + { + rb = RepoBusy.ToString(); + Monitor.Exit(RepoLock); + } + else + rb = "UNKNOWN"; + Server.Logger.WriteWarning(String.Format("Started HandleCommand with others running! Active locks: Repo: {0}, Byond: {1}, Compiler: {2}, Topic: {3}, Config: {4}, Watchdog: {5}, Chat: {6}, this: {7}, restartLock: {8}, autoUpdateLock: {9}, RepoBusy: {10}", !res, CheckLocked(ByondLock), CheckLocked(CompilerLock), CheckLocked(topicLock), CheckLocked(configLock), CheckLocked(watchdogLock), CheckLocked(ChatLock), CheckLocked(this), CheckLocked(restartLock), CheckLocked(autoUpdateTimer), rb), EventID.CallTracking, LoggingID); + } + /// public string Version() { diff --git a/TGS.Server/Instance/Interop.cs b/TGS.Server/Instance/Interop.cs index f24f3101f1..b56bc27251 100644 --- a/TGS.Server/Instance/Interop.cs +++ b/TGS.Server/Instance/Interop.cs @@ -6,6 +6,7 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Web.Security; using TGS.Server.ChatCommands; using TGS.Interface; @@ -16,7 +17,6 @@ namespace TGS.Server //handles talking between the world and us sealed partial class Instance : ITGInterop { - object topicLock = new object(); const int CommsKeyLen = 64; string serviceCommsKey; //regenerated every DD restart @@ -65,6 +65,8 @@ namespace TGS.Server List ServerChatCommands; + int RunningCommandHandlers = 0; + void LoadServerChatCommands() { if (DaemonStatus() != DreamDaemonStatus.Online) @@ -88,71 +90,91 @@ namespace TGS.Server catch { } } - //raw command string sent here via world.ExportService - void HandleCommand(string cmd) + static bool CheckLocked(object o) { - var splits = new List(cmd.Split(' ')); - cmd = splits[0]; - splits.RemoveAt(0); + var res = Monitor.TryEnter(o); + if (res) + Monitor.Exit(o); + return !res; + } - bool APIValid; - lock (topicLock) + //raw command string sent here via world.ExportService + Task HandleCommand(string cmd) + { + return Task.Run(() => { - APIValid = CheckAPIVersionConstraints(); - } + if (Interlocked.Increment(ref RunningCommandHandlers) > 1) + DumpLocks(); + try + { + var splits = new List(cmd.Split(' ')); + cmd = splits[0]; + splits.RemoveAt(0); - if (!APIValid && cmd != SRAPIVersion) - return; //SPEAK THE LANGUAGE!!! - - switch (cmd) - { - case SRIRCBroadcast: - SendMessage("GAME: " + String.Join(" ", splits), MessageType.GameInfo); - break; - case SRKillProcess: - KillMe(); - break; - case SRIRCAdminChannelMessage: - SendMessage("RELAY: " + String.Join(" ", splits), MessageType.AdminInfo); - break; - case SRWorldReboot: - WriteInfo("World Rebooted", EventID.WorldReboot); - WriteCurrentDDLog("World rebooted"); - ServerChatCommands = null; - ChatConnectivityCheck(); - lock (CompilerLock) - { - if (UpdateStaged) - { - UpdateStaged = false; - lock (topicLock) - { - GameAPIVersion = null; //needs updating - } - WriteInfo("Staged update applied", EventID.ServerUpdateApplied); - } - } - break; - case SRAPIVersion: + bool APIValid; lock (topicLock) { - try - { - GameAPIVersion = new Version(splits[0]); - if (!CheckAPIVersionConstraints()) - throw new Exception(); - } - catch - { - WriteWarning(String.Format("API version of the game ({0}) is incompatible with the current supported API versions (3.{2}.x.x). Interop disabled.", splits.Count > 1 ? splits[1] : "NULL", AllowedMajorAPIVersion), EventID.APIVersionMismatch); - GameAPIVersion = null; - break; - } + APIValid = CheckAPIVersionConstraints(); } - //This needs to be done asyncronously otherwise DD won't be able to process it, because it's waiting for THIS THREAD to return - ThreadPool.QueueUserWorkItem(_ => SendCommand(SCAPICompat)); - break; - } + + if (!APIValid && cmd != SRAPIVersion) + return; //SPEAK THE LANGUAGE!!! + + switch (cmd) + { + case SRIRCBroadcast: + SendMessage("GAME: " + String.Join(" ", splits), MessageType.GameInfo); + break; + case SRKillProcess: + KillMe(); + break; + case SRIRCAdminChannelMessage: + SendMessage("RELAY: " + String.Join(" ", splits), MessageType.AdminInfo); + break; + case SRWorldReboot: + WriteInfo("World Rebooted", EventID.WorldReboot); + WriteCurrentDDLog("World rebooted"); + ServerChatCommands = null; + ChatConnectivityCheck(); + lock (CompilerLock) + { + if (UpdateStaged) + { + UpdateStaged = false; + lock (topicLock) + { + GameAPIVersion = null; //needs updating + } + WriteInfo("Staged update applied", EventID.ServerUpdateApplied); + } + } + break; + case SRAPIVersion: + lock (topicLock) + { + try + { + GameAPIVersion = new Version(splits[0]); + if (!CheckAPIVersionConstraints()) + throw new Exception(); + } + catch + { + WriteWarning(String.Format("API version of the game ({0}) is incompatible with the current supported API versions (3.{2}.x.x). Interop disabled.", splits.Count > 1 ? splits[1] : "NULL", AllowedMajorAPIVersion), EventID.APIVersionMismatch); + GameAPIVersion = null; + break; + } + } + //This needs to be done asyncronously otherwise DD won't be able to process it, because it's waiting for THIS THREAD to return + ThreadPool.QueueUserWorkItem(_ => SendCommand(SCAPICompat)); + break; + } + } + finally + { + Interlocked.Decrement(ref RunningCommandHandlers); + } + }); } public string SendCommand(string cmd) @@ -261,16 +283,11 @@ namespace TGS.Server /// public bool InteropMessage(string command) { - try + HandleCommand(command).ContinueWith((t) => { - HandleCommand(command); - return true; - } - catch(Exception e) - { - WriteWarning(String.Format("Handle command for \"{0}\" failed: {1}", command, e.ToString()), EventID.InteropCallException); - return false; - } + WriteWarning(String.Format("Handle command for \"{0}\" failed: {1}", command, t.Exception.ToString()), EventID.InteropCallException); + }, TaskContinuationOptions.OnlyOnFaulted); + return true; } } } diff --git a/TGS.Server/Server.cs b/TGS.Server/Server.cs index eff1d58fa9..59f0abd5d4 100644 --- a/TGS.Server/Server.cs +++ b/TGS.Server/Server.cs @@ -4,6 +4,7 @@ using System.IO; using System.Reflection; using System.Security.Principal; using System.ServiceModel; +using System.ServiceModel.Description; using TGS.Interface; using TGS.Interface.Components; using TGS.Server.Security; @@ -342,7 +343,9 @@ namespace TGS.Server void AddEndpoint(ServiceHost host, Type typetype) { var bindingName = typetype.Name; - host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Definitions.TransferLimitLocal }, bindingName); + var endpint = host.AddServiceEndpoint(typetype, new NetNamedPipeBinding() { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = Definitions.TransferLimitLocal }, bindingName); + foreach (OperationDescription od in endpint.Contract.Operations) + od.OperationBehaviors.Add(new AddCallLoggerBehavior()); var httpsBinding = new BasicHttpsBinding() { SendTimeout = new TimeSpan(0, 0, 40), diff --git a/TGS.Server/TGS.Server.csproj b/TGS.Server/TGS.Server.csproj index 83f970c8c1..14e9e076e2 100644 --- a/TGS.Server/TGS.Server.csproj +++ b/TGS.Server/TGS.Server.csproj @@ -81,6 +81,7 @@ +