Merge branch 'app_domains' into event-mgr
This commit is contained in:
@@ -27,7 +27,6 @@ login anonymous
|
|||||||
app_update 298740
|
app_update 298740
|
||||||
quit";
|
quit";
|
||||||
|
|
||||||
private TorchAssemblyResolver _resolver;
|
|
||||||
private TorchConfig _config;
|
private TorchConfig _config;
|
||||||
private TorchServer _server;
|
private TorchServer _server;
|
||||||
private string _basePath;
|
private string _basePath;
|
||||||
@@ -50,7 +49,6 @@ quit";
|
|||||||
if (!args.Contains("-noupdate"))
|
if (!args.Contains("-noupdate"))
|
||||||
RunSteamCmd();
|
RunSteamCmd();
|
||||||
|
|
||||||
_resolver = new TorchAssemblyResolver(Path.Combine(_basePath, "DedicatedServer64"));
|
|
||||||
_config = InitConfig();
|
_config = InitConfig();
|
||||||
if (!_config.Parse(args))
|
if (!_config.Parse(args))
|
||||||
return false;
|
return false;
|
||||||
@@ -94,8 +92,6 @@ quit";
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
_server.Start();
|
_server.Start();
|
||||||
|
|
||||||
_resolver?.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TorchConfig InitConfig()
|
private TorchConfig InitConfig()
|
||||||
|
@@ -44,13 +44,16 @@ namespace Torch.Server
|
|||||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||||
Directory.SetCurrentDirectory(workingDir);
|
Directory.SetCurrentDirectory(workingDir);
|
||||||
|
|
||||||
|
if (!TorchLauncher.IsTorchWrapped())
|
||||||
|
{
|
||||||
|
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName,args, binDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Environment.UserInteractive)
|
if (!Environment.UserInteractive)
|
||||||
{
|
{
|
||||||
using (var service = new TorchService())
|
using (var service = new TorchService())
|
||||||
using (new TorchAssemblyResolver(binDir))
|
|
||||||
{
|
|
||||||
ServiceBase.Run(service);
|
ServiceBase.Run(service);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,12 +3,14 @@ using Sandbox.Engine.Utils;
|
|||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
using Microsoft.Xml.Serialization.GeneratedAssembly;
|
||||||
@@ -194,16 +196,62 @@ namespace Torch.Server
|
|||||||
((TorchServer)state).Invoke(() => mre.Set());
|
((TorchServer)state).Invoke(() => mre.Set());
|
||||||
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
|
||||||
{
|
{
|
||||||
var mainThread = MySandboxGame.Static.UpdateThread;
|
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
|
||||||
if (mainThread.IsAlive)
|
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
|
||||||
mainThread.Suspend();
|
|
||||||
var stackTrace = new StackTrace(mainThread, true);
|
|
||||||
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Debug("Server watchdog responded");
|
Log.Debug("Server watchdog responded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)
|
||||||
|
{
|
||||||
|
var stacks = new List<string>(traces);
|
||||||
|
var totalSize = 0;
|
||||||
|
for (var i = 0; i < traces; i++)
|
||||||
|
{
|
||||||
|
string dump = DumpStack(thread).ToString();
|
||||||
|
totalSize += dump.Length;
|
||||||
|
stacks.Add(dump);
|
||||||
|
Thread.Sleep(pause);
|
||||||
|
}
|
||||||
|
string commonPrefix = StringUtils.CommonSuffix(stacks);
|
||||||
|
// Advance prefix to include the line terminator.
|
||||||
|
commonPrefix = commonPrefix.Substring(commonPrefix.IndexOf('\n') + 1);
|
||||||
|
|
||||||
|
var result = new StringBuilder(totalSize - (stacks.Count - 1) * commonPrefix.Length);
|
||||||
|
result.AppendLine($"Frozen thread dump {thread.Name}");
|
||||||
|
result.AppendLine("Common prefix:").AppendLine(commonPrefix);
|
||||||
|
for (var i = 0; i < stacks.Count; i++)
|
||||||
|
if (stacks[i].Length > commonPrefix.Length)
|
||||||
|
{
|
||||||
|
result.AppendLine($"Suffix {i}");
|
||||||
|
result.AppendLine(stacks[i].Substring(0, stacks[i].Length - commonPrefix.Length));
|
||||||
|
}
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StackTrace DumpStack(Thread thread)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
thread.Suspend();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
var stack = new StackTrace(thread, true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
thread.Resume();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Stop()
|
public override void Stop()
|
||||||
{
|
{
|
||||||
@@ -253,28 +301,32 @@ namespace Torch.Server
|
|||||||
/// <param name="callerId">Caller of the save operation</param>
|
/// <param name="callerId">Caller of the save operation</param>
|
||||||
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
|
||||||
{
|
{
|
||||||
|
string response = null;
|
||||||
switch (statusCode)
|
switch (statusCode)
|
||||||
{
|
{
|
||||||
case SaveGameStatus.Success:
|
case SaveGameStatus.Success:
|
||||||
Log.Info("Save completed.");
|
Log.Info("Save completed.");
|
||||||
// TODO
|
response = "Saved game.";
|
||||||
// Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.SaveInProgress:
|
case SaveGameStatus.SaveInProgress:
|
||||||
Log.Error("Save failed, a save is already in progress.");
|
Log.Error("Save failed, a save is already in progress.");
|
||||||
// Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, a save is already in progress.";
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.GameNotReady:
|
case SaveGameStatus.GameNotReady:
|
||||||
Log.Error("Save failed, game was not ready.");
|
Log.Error("Save failed, game was not ready.");
|
||||||
// Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, game was not ready.";
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.TimedOut:
|
case SaveGameStatus.TimedOut:
|
||||||
Log.Error("Save failed, save timed out.");
|
Log.Error("Save failed, save timed out.");
|
||||||
// Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
response = "Save failed, save timed out.";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (MySession.Static.Players.TryGetPlayerId(callerId, out MyPlayer.PlayerId result))
|
||||||
|
{
|
||||||
|
Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther("Server", response, statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ using System.Reflection.Emit;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Label = System.Windows.Controls.Label;
|
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager.MSIL
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
{
|
{
|
||||||
|
@@ -215,6 +215,7 @@
|
|||||||
<Compile Include="Plugins\PluginManifest.cs" />
|
<Compile Include="Plugins\PluginManifest.cs" />
|
||||||
<Compile Include="Utils\Reflection.cs" />
|
<Compile Include="Utils\Reflection.cs" />
|
||||||
<Compile Include="Managers\ScriptingManager.cs" />
|
<Compile Include="Managers\ScriptingManager.cs" />
|
||||||
|
<Compile Include="Utils\StringUtils.cs" />
|
||||||
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
<Compile Include="Utils\TorchAssemblyResolver.cs" />
|
||||||
<Compile Include="Utils\ReflectedManager.cs" />
|
<Compile Include="Utils\ReflectedManager.cs" />
|
||||||
<Compile Include="Session\TorchSessionManager.cs" />
|
<Compile Include="Session\TorchSessionManager.cs" />
|
||||||
@@ -222,6 +223,7 @@
|
|||||||
<Compile Include="SteamService.cs" />
|
<Compile Include="SteamService.cs" />
|
||||||
<Compile Include="TorchPluginBase.cs" />
|
<Compile Include="TorchPluginBase.cs" />
|
||||||
<Compile Include="Session\TorchSession.cs" />
|
<Compile Include="Session\TorchSession.cs" />
|
||||||
|
<Compile Include="Utils\TorchLauncher.cs" />
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
|
64
Torch/Utils/StringUtils.cs
Normal file
64
Torch/Utils/StringUtils.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for strings
|
||||||
|
/// </summary>
|
||||||
|
public static class StringUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common prefix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common prefix</returns>
|
||||||
|
public static string CommonPrefix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(other.Length, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
if (builder[i] != other[i])
|
||||||
|
{
|
||||||
|
builder.Remove(i, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common suffix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common suffix</returns>
|
||||||
|
public static string CommonSuffix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(0, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
{
|
||||||
|
if (builder[builder.Length - 1 - i] != other[other.Length - 1 - i])
|
||||||
|
{
|
||||||
|
builder.Remove(0, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
Torch/Utils/TorchLauncher.cs
Normal file
51
Torch/Utils/TorchLauncher.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.API;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
public class TorchLauncher
|
||||||
|
{
|
||||||
|
private const string TorchKey = "TorchWrapper";
|
||||||
|
|
||||||
|
public static bool IsTorchWrapped()
|
||||||
|
{
|
||||||
|
return AppDomain.CurrentDomain.GetData(TorchKey) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Launch(string entryPoint, string[] args, params string[] binaryPaths)
|
||||||
|
{
|
||||||
|
if (IsTorchWrapped())
|
||||||
|
throw new Exception("Can't wrap torch twice");
|
||||||
|
string exePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)?.ToLower().Replace('/', '\\');
|
||||||
|
if (exePath == null)
|
||||||
|
throw new ArgumentException("Unable to determine executing assembly's path");
|
||||||
|
var allPaths = new HashSet<string> { exePath };
|
||||||
|
foreach (string other in binaryPaths)
|
||||||
|
allPaths.Add(other.ToLower().Replace('/', '\\'));
|
||||||
|
var pathPrefix = StringUtils.CommonPrefix(allPaths);
|
||||||
|
AppDomain.CurrentDomain.AppendPrivatePath(String.Join(Path.PathSeparator.ToString(), allPaths));
|
||||||
|
AppDomain.CurrentDomain.SetData(TorchKey, true);
|
||||||
|
AppDomain.CurrentDomain.ExecuteAssemblyByName(entryPoint, args);
|
||||||
|
return;
|
||||||
|
// this would be way better but HAVOK IS UNMANAGED :clang:
|
||||||
|
// exclude application base from probing
|
||||||
|
var setup = new AppDomainSetup
|
||||||
|
{
|
||||||
|
ApplicationBase = pathPrefix.ToString(),
|
||||||
|
PrivateBinPathProbe = "",
|
||||||
|
PrivateBinPath = string.Join(";", allPaths)
|
||||||
|
};
|
||||||
|
AppDomain domain = AppDomain.CreateDomain($"TorchDomain-{Assembly.GetEntryAssembly().GetName().Name}-{new Random().Next():X}", null, setup);
|
||||||
|
domain.SetData(TorchKey, true);
|
||||||
|
domain.ExecuteAssemblyByName(entryPoint, args);
|
||||||
|
AppDomain.Unload(domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user