Skip to content

Commit c221ef0

Browse files
authored
System to automatically restart server after certain uptime. (space-wizards#32814)
1 parent eec533c commit c221ef0

File tree

3 files changed

+69
-12
lines changed

3 files changed

+69
-12
lines changed

Content.Server/ServerUpdates/ServerUpdateManager.cs

+53-12
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,57 @@
1212
namespace Content.Server.ServerUpdates;
1313

1414
/// <summary>
15-
/// Responsible for restarting the server for update, when not disruptive.
15+
/// Responsible for restarting the server periodically or for update, when not disruptive.
1616
/// </summary>
17-
public sealed class ServerUpdateManager
17+
/// <remarks>
18+
/// This was originally only designed for restarting on *update*,
19+
/// but now also handles periodic restarting to keep server uptime via <see cref="CCVars.ServerUptimeRestartMinutes"/>.
20+
/// </remarks>
21+
public sealed class ServerUpdateManager : IPostInjectInit
1822
{
1923
[Dependency] private readonly IGameTiming _gameTiming = default!;
2024
[Dependency] private readonly IWatchdogApi _watchdog = default!;
2125
[Dependency] private readonly IPlayerManager _playerManager = default!;
2226
[Dependency] private readonly IChatManager _chatManager = default!;
2327
[Dependency] private readonly IBaseServer _server = default!;
2428
[Dependency] private readonly IConfigurationManager _cfg = default!;
29+
[Dependency] private readonly ILogManager _logManager = default!;
30+
31+
private ISawmill _sawmill = default!;
2532

2633
[ViewVariables]
2734
private bool _updateOnRoundEnd;
2835

2936
private TimeSpan? _restartTime;
3037

38+
private TimeSpan _uptimeRestart;
39+
3140
public void Initialize()
3241
{
3342
_watchdog.UpdateReceived += WatchdogOnUpdateReceived;
3443
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
44+
45+
_cfg.OnValueChanged(
46+
CCVars.ServerUptimeRestartMinutes,
47+
minutes => _uptimeRestart = TimeSpan.FromMinutes(minutes),
48+
true);
3549
}
3650

3751
public void Update()
3852
{
39-
if (_restartTime != null && _restartTime < _gameTiming.RealTime)
53+
if (_restartTime != null)
4054
{
41-
DoShutdown();
55+
if (_restartTime < _gameTiming.RealTime)
56+
{
57+
DoShutdown();
58+
}
59+
}
60+
else
61+
{
62+
if (ShouldShutdownDueToUptime())
63+
{
64+
ServerEmptyUpdateRestartCheck("uptime");
65+
}
4266
}
4367
}
4468

@@ -48,7 +72,7 @@ public void Update()
4872
/// <returns>True if the server is going to restart.</returns>
4973
public bool RoundEnded()
5074
{
51-
if (_updateOnRoundEnd)
75+
if (_updateOnRoundEnd || ShouldShutdownDueToUptime())
5276
{
5377
DoShutdown();
5478
return true;
@@ -61,11 +85,14 @@ private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEve
6185
{
6286
switch (e.NewStatus)
6387
{
64-
case SessionStatus.Connecting:
88+
case SessionStatus.Connected:
89+
if (_restartTime != null)
90+
_sawmill.Debug("Aborting server restart timer due to player connection");
91+
6592
_restartTime = null;
6693
break;
6794
case SessionStatus.Disconnected:
68-
ServerEmptyUpdateRestartCheck();
95+
ServerEmptyUpdateRestartCheck("last player disconnect");
6996
break;
7097
}
7198
}
@@ -74,37 +101,51 @@ private void WatchdogOnUpdateReceived()
74101
{
75102
_chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received"));
76103
_updateOnRoundEnd = true;
77-
ServerEmptyUpdateRestartCheck();
104+
ServerEmptyUpdateRestartCheck("update notification");
78105
}
79106

80107
/// <summary>
81108
/// Checks whether there are still players on the server,
82109
/// and if not starts a timer to automatically reboot the server if an update is available.
83110
/// </summary>
84-
private void ServerEmptyUpdateRestartCheck()
111+
private void ServerEmptyUpdateRestartCheck(string reason)
85112
{
86113
// Can't simple check the current connected player count since that doesn't update
87114
// before PlayerStatusChanged gets fired.
88115
// So in the disconnect handler we'd still see a single player otherwise.
89116
var playersOnline = _playerManager.Sessions.Any(p => p.Status != SessionStatus.Disconnected);
90-
if (playersOnline || !_updateOnRoundEnd)
117+
if (playersOnline || !(_updateOnRoundEnd || ShouldShutdownDueToUptime()))
91118
{
92119
// Still somebody online.
93120
return;
94121
}
95122

96123
if (_restartTime != null)
97124
{
98-
// Do nothing because I guess we already have a timer running..?
125+
// Do nothing because we already have a timer running.
99126
return;
100127
}
101128

102129
var restartDelay = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.UpdateRestartDelay));
103130
_restartTime = restartDelay + _gameTiming.RealTime;
131+
132+
_sawmill.Debug("Started server-empty restart timer due to {Reason}", reason);
104133
}
105134

106135
private void DoShutdown()
107136
{
108-
_server.Shutdown(Loc.GetString("server-updates-shutdown"));
137+
_sawmill.Debug($"Shutting down via {nameof(ServerUpdateManager)}!");
138+
var reason = _updateOnRoundEnd ? "server-updates-shutdown" : "server-updates-shutdown-uptime";
139+
_server.Shutdown(Loc.GetString(reason));
140+
}
141+
142+
private bool ShouldShutdownDueToUptime()
143+
{
144+
return _uptimeRestart != TimeSpan.Zero && _gameTiming.RealTime > _uptimeRestart;
145+
}
146+
147+
void IPostInjectInit.PostInject()
148+
{
149+
_sawmill = _logManager.GetSawmill("restart");
109150
}
110151
}

Content.Shared/CCVar/CCVars.cs

+15
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ public sealed class CCVars : CVars
3232
public static readonly CVarDef<string> DefaultGuide =
3333
CVarDef.Create("server.default_guide", "NewPlayer", CVar.REPLICATED | CVar.SERVER);
3434

35+
/// <summary>
36+
/// If greater than 0, automatically restart the server after this many minutes of uptime.
37+
/// </summary>
38+
/// <remarks>
39+
/// <para>
40+
/// This is intended to work around various bugs and performance issues caused by long continuous server uptime.
41+
/// </para>
42+
/// <para>
43+
/// This uses the same non-disruptive logic as update restarts,
44+
/// i.e. the game will only restart at round end or when there is nobody connected.
45+
/// </para>
46+
/// </remarks>
47+
public static readonly CVarDef<int> ServerUptimeRestartMinutes =
48+
CVarDef.Create("server.uptime_restart_minutes", 0, CVar.SERVERONLY);
49+
3550
/*
3651
* Ambience
3752
*/
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
server-updates-received = Update has been received, server will automatically restart for update at the end of this round.
22
server-updates-shutdown = Server is shutting down for update and will automatically restart.
3+
server-updates-shutdown-uptime = Server is shutting down for periodic cleanup and will automatically restart.

0 commit comments

Comments
 (0)