ServerUpdateManager.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. using System.Linq;
  2. using Content.Server.Chat.Managers;
  3. using Content.Shared.CCVar;
  4. using Robust.Server;
  5. using Robust.Server.Player;
  6. using Robust.Server.ServerStatus;
  7. using Robust.Shared.Configuration;
  8. using Robust.Shared.Enums;
  9. using Robust.Shared.Player;
  10. using Robust.Shared.Timing;
  11. namespace Content.Server.ServerUpdates;
  12. /// <summary>
  13. /// Responsible for restarting the server periodically or for update, when not disruptive.
  14. /// </summary>
  15. /// <remarks>
  16. /// This was originally only designed for restarting on *update*,
  17. /// but now also handles periodic restarting to keep server uptime via <see cref="CCVars.ServerUptimeRestartMinutes"/>.
  18. /// </remarks>
  19. public sealed class ServerUpdateManager : IPostInjectInit
  20. {
  21. [Dependency] private readonly IGameTiming _gameTiming = default!;
  22. [Dependency] private readonly IWatchdogApi _watchdog = default!;
  23. [Dependency] private readonly IPlayerManager _playerManager = default!;
  24. [Dependency] private readonly IChatManager _chatManager = default!;
  25. [Dependency] private readonly IBaseServer _server = default!;
  26. [Dependency] private readonly IConfigurationManager _cfg = default!;
  27. [Dependency] private readonly ILogManager _logManager = default!;
  28. private ISawmill _sawmill = default!;
  29. [ViewVariables]
  30. private bool _updateOnRoundEnd;
  31. private TimeSpan? _restartTime;
  32. private TimeSpan _uptimeRestart;
  33. public void Initialize()
  34. {
  35. _watchdog.UpdateReceived += WatchdogOnUpdateReceived;
  36. _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
  37. _cfg.OnValueChanged(
  38. CCVars.ServerUptimeRestartMinutes,
  39. minutes => _uptimeRestart = TimeSpan.FromMinutes(minutes),
  40. true);
  41. }
  42. public void Update()
  43. {
  44. if (_restartTime != null)
  45. {
  46. if (_restartTime < _gameTiming.RealTime)
  47. {
  48. DoShutdown();
  49. }
  50. }
  51. else
  52. {
  53. if (ShouldShutdownDueToUptime())
  54. {
  55. ServerEmptyUpdateRestartCheck("uptime");
  56. }
  57. }
  58. }
  59. /// <summary>
  60. /// Notify that the round just ended, which is a great time to restart if necessary!
  61. /// </summary>
  62. /// <returns>True if the server is going to restart.</returns>
  63. public bool RoundEnded()
  64. {
  65. if (_updateOnRoundEnd || ShouldShutdownDueToUptime())
  66. {
  67. DoShutdown();
  68. return true;
  69. }
  70. return false;
  71. }
  72. private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
  73. {
  74. switch (e.NewStatus)
  75. {
  76. case SessionStatus.Connected:
  77. if (_restartTime != null)
  78. _sawmill.Debug("Aborting server restart timer due to player connection");
  79. _restartTime = null;
  80. break;
  81. case SessionStatus.Disconnected:
  82. ServerEmptyUpdateRestartCheck("last player disconnect");
  83. break;
  84. }
  85. }
  86. private void WatchdogOnUpdateReceived()
  87. {
  88. _chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received"));
  89. _updateOnRoundEnd = true;
  90. ServerEmptyUpdateRestartCheck("update notification");
  91. }
  92. /// <summary>
  93. /// Checks whether there are still players on the server,
  94. /// and if not starts a timer to automatically reboot the server if an update is available.
  95. /// </summary>
  96. private void ServerEmptyUpdateRestartCheck(string reason)
  97. {
  98. // Can't simple check the current connected player count since that doesn't update
  99. // before PlayerStatusChanged gets fired.
  100. // So in the disconnect handler we'd still see a single player otherwise.
  101. var playersOnline = _playerManager.Sessions.Any(p => p.Status != SessionStatus.Disconnected);
  102. if (playersOnline || !(_updateOnRoundEnd || ShouldShutdownDueToUptime()))
  103. {
  104. // Still somebody online.
  105. return;
  106. }
  107. if (_restartTime != null)
  108. {
  109. // Do nothing because we already have a timer running.
  110. return;
  111. }
  112. var restartDelay = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.UpdateRestartDelay));
  113. _restartTime = restartDelay + _gameTiming.RealTime;
  114. _sawmill.Debug("Started server-empty restart timer due to {Reason}", reason);
  115. }
  116. private void DoShutdown()
  117. {
  118. _sawmill.Debug($"Shutting down via {nameof(ServerUpdateManager)}!");
  119. var reason = _updateOnRoundEnd ? "server-updates-shutdown" : "server-updates-shutdown-uptime";
  120. _server.Shutdown(Loc.GetString(reason));
  121. }
  122. private bool ShouldShutdownDueToUptime()
  123. {
  124. return _uptimeRestart != TimeSpan.Zero && _gameTiming.RealTime > _uptimeRestart;
  125. }
  126. void IPostInjectInit.PostInject()
  127. {
  128. _sawmill = _logManager.GetSawmill("restart");
  129. }
  130. }