diff --git a/Jenkins/jenkins-grab-se.ps1 b/Jenkins/jenkins-grab-se.ps1 index 5699e3d..f94193c 100644 --- a/Jenkins/jenkins-grab-se.ps1 +++ b/Jenkins/jenkins-grab-se.ps1 @@ -17,6 +17,6 @@ if (!(Test-Path $steamCMDPath)) { } cd "$steamData" -& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740 -beta publictest -betapassword nt8WuDw9kdvE validate" "+quit" +& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740 validate" "+quit" popd diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs index 61e7ee6..94d2072 100644 --- a/Torch.Server/Initializer.cs +++ b/Torch.Server/Initializer.cs @@ -254,6 +254,7 @@ quit"; private void HandleException(object sender, UnhandledExceptionEventArgs e) { + _server.FatalException = true; var ex = (Exception)e.ExceptionObject; LogException(ex); if (MyFakes.ENABLE_MINIDUMP_SENDING) diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index 3e10b1e..2e36d46 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -63,7 +63,7 @@ namespace Torch.Server // Breaks on Windows Server 2019 if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive) { - using (var service = new TorchService()) + using (var service = new TorchService(args)) ServiceBase.Run(service); return; } diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 503a277..389e2f8 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -49,6 +49,8 @@ namespace Torch.Server private Timer _watchdog; private int _players; private MultiplayerManagerDedicated _multiplayerManagerDedicated; + + internal bool FatalException { get; set; } /// public TorchServer(TorchConfig config = null) @@ -232,10 +234,16 @@ namespace Torch.Server private static void CheckServerResponding(object state) { + var server = (TorchServer)state; var mre = new ManualResetEvent(false); - ((TorchServer)state).Invoke(() => mre.Set()); + server.Invoke(() => mre.Set()); if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout))) { + if (server.FatalException) + { + server._watchdog.Dispose(); + return; + } #if DEBUG Log.Error( $"Server watchdog detected that the server was frozen for at least {((TorchServer) state).Config.TickTimeout} seconds."); diff --git a/Torch.Server/TorchService.cs b/Torch.Server/TorchService.cs index e33fda3..1a659b8 100644 --- a/Torch.Server/TorchService.cs +++ b/Torch.Server/TorchService.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ServiceProcess; +using System.Threading; using NLog; using Torch.API; @@ -12,12 +14,14 @@ namespace Torch.Server { class TorchService : ServiceBase { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); public const string Name = "Torch (SEDS)"; - private TorchServer _server; private Initializer _initializer; + private string[] _args; - public TorchService() + public TorchService(string[] args) { + _args = args; var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString(); Directory.SetCurrentDirectory(workingDir); _initializer = new Initializer(workingDir); @@ -29,19 +33,21 @@ namespace Torch.Server } /// - protected override void OnStart(string[] args) + protected override void OnStart(string[] _) { - base.OnStart(args); + base.OnStart(_args); - _initializer.Initialize(args); + _initializer.Initialize(_args); _initializer.Run(); } /// protected override void OnStop() { - _server.Stop(); - base.OnStop(); + var mre = new ManualResetEvent(false); + Task.Run(() => _initializer.Server.Stop()); + if (!mre.WaitOne(TimeSpan.FromMinutes(1))) + Process.GetCurrentProcess().Kill(); } } } diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index 10ed52e..52733be 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -29,6 +29,8 @@ namespace Torch.Commands { private static bool _restartPending = false; private static bool _cancelRestart = false; + private bool _stopPending = false; + private bool _cancelStop = false; private static readonly Logger Log = LogManager.GetCurrentClassLogger(); [Command("whatsmyip")] @@ -154,9 +156,25 @@ namespace Torch.Commands } [Command("stop", "Stops the server.")] - public void Stop(bool save = true) + public void Stop(bool save = true, int countdownSeconds = 0) { - Context.Respond("Stopping server."); + if (_stopPending) + { + Context.Respond("A stop is already pending."); + return; + } + + _stopPending = true; + Task.Run(() => + { + var countdown = StopCountdown(countdownSeconds, save).GetEnumerator(); + while (countdown.MoveNext()) + { + Thread.Sleep(1000); + } + }); + + /*Context.Respond("Stopping server."); if (save) DoSave()?.ContinueWith((a, mod) => { @@ -165,7 +183,7 @@ namespace Torch.Commands torch.Stop(); }, this, TaskContinuationOptions.RunContinuationsAsynchronously); else - Context.Torch.Stop(); + Context.Torch.Stop();*/ } [Command("restart", "Restarts the server.")] @@ -204,6 +222,69 @@ namespace Torch.Commands else Context.Respond("A restart is not pending."); } + + [Command("stop cancel", "Cancel a pending stop.")] + public void CancelStop() + { + if (_restartPending) + _cancelStop = true; + else + Context.Respond("Server Stop is not pending."); + } + + private IEnumerable StopCountdown(int countdown, bool save) + { + for (var i = countdown; i >= 0; i--) + { + if (_cancelStop) + { + Context.Torch.CurrentSession.Managers.GetManager() + .SendMessageAsSelf($"Stop cancelled."); + + _stopPending = false; + _cancelStop = false; + yield break; + } + + if (i >= 60 && i % 60 == 0) + { + Context.Torch.CurrentSession.Managers.GetManager() + .SendMessageAsSelf($"Stopping server in {i / 60} minute{Pluralize(i / 60)}."); + yield return null; + } + else if (i > 0) + { + if (i < 11) + Context.Torch.CurrentSession.Managers.GetManager() + .SendMessageAsSelf($"Stopping server in {i} second{Pluralize(i)}."); + yield return null; + } + else + { + if (save) + { + Log.Info("Saving game before stop."); + Context.Torch.CurrentSession.Managers.GetManager() + .SendMessageAsSelf($"Saving game before stop."); + DoSave()?.ContinueWith((a, mod) => + { + ITorchBase torch = (mod as CommandModule)?.Context?.Torch; + Debug.Assert(torch != null); + torch.Stop(); + }, this, TaskContinuationOptions.RunContinuationsAsynchronously); + + } + else + { + Log.Info("Stopping server."); + Context.Torch.Invoke(() => Context.Torch.Stop()); + } + + + yield break; + } + } + } private IEnumerable RestartCountdown(int countdown, bool save) { diff --git a/Torch/VRageGame.cs b/Torch/VRageGame.cs index 0f0530f..4f95c4d 100644 --- a/Torch/VRageGame.cs +++ b/Torch/VRageGame.cs @@ -199,7 +199,8 @@ namespace Torch } MyRenderProxy.Initialize(renderer); MyRenderProfiler.SetAutocommit(false); - MyRenderProfiler.InitMemoryHack("MainEntryPoint"); + //This broke services? + //MyRenderProfiler.InitMemoryHack("MainEntryPoint"); } // Loads object builder serializers. Intuitive, right?