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