Skip to content

Commit

Permalink
Add system to stop game server gracefully without restart
Browse files Browse the repository at this point in the history
Intended use is manually taking down game servers for maintenance purposes.
  • Loading branch information
PJB3005 committed Apr 5, 2024
1 parent 58b078c commit 73bb7d1
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 4 deletions.
27 changes: 26 additions & 1 deletion SS14.Watchdog/Components/ServerManagement/IServerInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,30 @@ public interface IServerInstance

Task ForceShutdownServerAsync(CancellationToken cancel = default);
Task DoRestartCommandAsync(CancellationToken cancel = default);

/// <summary>
/// Instruct that the server instance should be stopped gracefully.
/// It will not be restarted automatically after shutdown.
/// </summary>
/// <remarks>
/// The server will be asked to gracefully shut down via the <c>/update</c> end point.
/// </remarks>
Task DoStopCommandAsync(ServerInstanceStopCommand stopCommand, CancellationToken cancel = default);
}

/// <summary>
/// Information about a stop command sent to a server instance.
/// </summary>
/// <seealso cref="IServerInstance.DoStopCommandAsync"/>
public sealed class ServerInstanceStopCommand
{
public ServerInstanceStopReason StopReason;
}

/// <seealso cref="ServerInstanceStopCommand"/>
public enum ServerInstanceStopReason
{
Unknown,
Maintenance,
}
}
}
44 changes: 42 additions & 2 deletions SS14.Watchdog/Components/ServerManagement/ServerInstance.Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public sealed partial class ServerInstance
private int _serverTimeoutNumber;

private int _startNumber;
// Server got an explicit stop command, will not be automatically restarted.
private bool _stopped;

public async Task StartAsync(string baseServerAddress, CancellationToken cancel)
{
Expand Down Expand Up @@ -134,6 +136,9 @@ private async Task RunCommand(Command command, CancellationToken cancel)
case CommandRestart:
await RunCommandRestart(cancel);
break;
case CommandStop stop:
await RunCommandStop(stop.StopCommand, cancel);
break;
case CommandServerPing ping:
await RunCommandServerPing(ping, cancel);
break;
Expand All @@ -150,6 +155,12 @@ private async Task RunCommand(Command command, CancellationToken cancel)

private async Task RunCommandRestart(CancellationToken cancel)
{
if (_stopped)
{
_logger.LogDebug("Clearing stopped flag due to manual server restart");
_stopped = false;
}

if (_runningServer == null)
{
_loadFailCount = 0;
Expand All @@ -161,6 +172,18 @@ private async Task RunCommandRestart(CancellationToken cancel)
await ForceShutdownServerAsync(cancel);
}

private async Task RunCommandStop(ServerInstanceStopCommand stopCommand, CancellationToken cancel)
{
// TODO: use stopCommand to indicate more extensive error message to error.

_stopped = true;
if (IsRunning)
{
_logger.LogTrace("Server is running, sending fake update notification to make it stop");
await SendUpdateNotificationAsync(cancel);
}
}

private async Task RunCommandUpdateAvailable(CommandUpdateAvailable command, CancellationToken cancel)
{
_updateOnRestart = command.UpdateAvailable;
Expand All @@ -171,6 +194,10 @@ private async Task RunCommandUpdateAvailable(CommandUpdateAvailable command, Can
_logger.LogTrace("Server is running, sending update notification.");
await SendUpdateNotificationAsync(cancel);
}
else if (_stopped)
{
_logger.LogInformation("Not restarting server for update as it was manually stopped.");
}
else if (_startupFailUpdateWait)
{
_startupFailUpdateWait = false;
Expand Down Expand Up @@ -247,8 +274,16 @@ private async Task RunCommandServerExit(CommandServerExit exit, CancellationToke
_loadFailCount = 0;
}

_logger.LogInformation("{Key}: Restarting server after exit...", Key);
await StartServer(cancel);
if (!_stopped)
{
_logger.LogInformation("{Key}: Restarting server after exit...", Key);
await StartServer(cancel);
}
else
{
_logger.LogInformation("{Key}: Not restarting server as it was manually stopped.", Key);
_notificationManager.SendNotification($"Server `{Key}` has exited after manual stop request.");
}
}

private async Task RunCommandStart(CancellationToken cancel)
Expand Down Expand Up @@ -421,6 +456,11 @@ private sealed record CommandStart : Command;
/// </summary>
private sealed record CommandRestart : Command;

/// <summary>
/// Command to stop the server gracefully, without restarting it afterwards.
/// </summary>
private sealed record CommandStop(ServerInstanceStopCommand StopCommand) : Command;

/// <summary>
/// The server has failed to ping back in time, grab the axe!
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions SS14.Watchdog/Components/ServerManagement/ServerInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,11 @@ public async Task DoRestartCommandAsync(CancellationToken cancel = default)
await _commandQueue.Writer.WriteAsync(new CommandRestart(), cancel);
}

public async Task DoStopCommandAsync(ServerInstanceStopCommand stopCommand, CancellationToken cancel = default)
{
await _commandQueue.Writer.WriteAsync(new CommandStop(stopCommand), cancel);
}

public async Task ForceShutdownServerAsync(CancellationToken cancel = default)
{
var proc = _runningServer;
Expand Down
14 changes: 13 additions & 1 deletion SS14.Watchdog/Controllers/InstanceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ public async Task<IActionResult> Restart([FromHeader(Name = "Authorization")] st
return Ok();
}

[HttpPost("stop")]
public async Task<IActionResult> Stop([FromHeader(Name = "Authorization")] string authorization, string key)
{
if (!TryAuthorize(authorization, key, out var failure, out var instance))
{
return failure;
}

await instance.DoStopCommandAsync(new ServerInstanceStopCommand());
return Ok();
}

[HttpPost("update")]
public IActionResult Update([FromHeader(Name = "Authorization")] string authorization, string key)
{
Expand Down Expand Up @@ -79,4 +91,4 @@ public bool TryAuthorize(string authorization,
return true;
}
}
}
}

0 comments on commit 73bb7d1

Please sign in to comment.