tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
SystemDManager.cs
Go to the documentation of this file.
1using System;
2using System.Globalization;
3using System.Threading;
4using System.Threading.Tasks;
5
6using Microsoft.Extensions.Hosting;
7using Microsoft.Extensions.Logging;
8
9using Mono.Unix;
10
13
15{
20 {
24 const string SDNotifyWatchdog = "WATCHDOG=1";
25
29 readonly IHostApplicationLifetime applicationLifetime;
30
35
40
44 readonly ILogger<SystemDManager> logger;
45
50
56 static long GetMonotonicUsec() => global::System.Diagnostics.Stopwatch.GetTimestamp(); // HACK: https://github.com/dotnet/runtime/blob/v8.0.0-preview.6.23329.7/src/native/libs/System.Native/pal_time.c#L84 clock_gettime_nsec_np is an OSX only thing apparently...
57
66 IHostApplicationLifetime applicationLifetime,
68 IServerControl serverControl,
69 ILogger<SystemDManager> logger)
70 {
71 this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
72 this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
73
74 ArgumentNullException.ThrowIfNull(serverControl);
75
76 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
77
78 restartRegistration = serverControl.RegisterForRestart(this);
79 }
80
82 public override void Dispose()
83 {
84 base.Dispose();
85 restartRegistration.Dispose();
86 }
87
89 public ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
90 {
91 // If this is set, we know a gracefule SHUTDOWN was requested
92 restartInProgress = !handlerMayDelayShutdownWithExtremelyLongRunningTasks;
93 return ValueTask.CompletedTask;
94 }
95
97 protected override async Task ExecuteAsync(CancellationToken cancellationToken)
98 {
100 {
101 logger.LogDebug("SystemD not detected");
102 return;
103 }
104
105 logger.LogDebug("SystemD detected");
106
107 if (applicationLifetime.ApplicationStarted.IsCancellationRequested)
108 throw new InvalidOperationException("RunAsync called after application started!");
109
110 logger.LogTrace("Installing lifetime handlers...");
111
112 var readyCounts = 0;
113 void CheckReady()
114 {
115 if (Interlocked.Increment(ref readyCounts) < 2)
116 return;
117
118 SendSDNotify("READY=1");
119 }
120
121 applicationLifetime.ApplicationStarted.Register(() => CheckReady());
122 applicationLifetime.ApplicationStopping.Register(
123 () => SendSDNotify(
125 ? $"RELOADING=1\nMONOTONIC_USEC={GetMonotonicUsec()}"
126 : "STOPPING=1"));
127
128 try
129 {
130 await instanceManager.Ready.WaitAsync(cancellationToken);
131 CheckReady();
132
133 var watchdogUsec = Environment.GetEnvironmentVariable("WATCHDOG_USEC");
134 if (String.IsNullOrWhiteSpace(watchdogUsec))
135 {
136 logger.LogDebug("WATCHDOG_USEC not present, not starting watchdog loop");
137 return;
138 }
139
140 var microseconds = UInt64.Parse(watchdogUsec, CultureInfo.InvariantCulture);
141 var timeoutIntervalMillis = (int)(microseconds / 1000);
142
143 logger.LogDebug("Starting watchdog loop with interval of {timeoutInterval}ms", timeoutIntervalMillis);
144
145 var timeoutInterval = TimeSpan.FromMilliseconds(timeoutIntervalMillis);
146 var nextExpectedTimeout = DateTimeOffset.UtcNow + timeoutInterval;
147 var timeToNextExpectedTimeout = nextExpectedTimeout - DateTimeOffset.UtcNow;
148 while (!cancellationToken.IsCancellationRequested)
149 {
150 var delayInterval = timeToNextExpectedTimeout / 2;
151 await Task.Delay(delayInterval, cancellationToken);
152
153 var notifySuccess = SendSDNotify(SDNotifyWatchdog);
154
155 var now = DateTimeOffset.UtcNow;
156 if (notifySuccess)
157 nextExpectedTimeout = now + timeoutInterval;
158
159 timeToNextExpectedTimeout = nextExpectedTimeout - now;
160
161 if (!notifySuccess)
162 logger.LogWarning("Missed systemd heartbeat! Expected timeout in {timeoutMs}ms...", timeToNextExpectedTimeout.TotalMilliseconds);
163 }
164 }
165 catch (OperationCanceledException ex)
166 {
167 logger.LogTrace(ex, "Watchdog loop cancelled!");
168 }
169 catch (Exception ex)
170 {
171 logger.LogError(ex, "Watchdog loop crashed!");
172 }
173
174 logger.LogDebug("Exited watchdog loop");
175 }
176
182 bool SendSDNotify(string command)
183 {
184 logger.LogTrace("Sending sd_notify {message}...", command);
185 int result;
186 try
187 {
188 result = NativeMethods.sd_notify(0, command);
189 }
190 catch (Exception ex)
191 {
192 logger.LogInformation(ex, "Exception attempting to invoke sd_notify!");
193 return false;
194 }
195
196 if (result > 0)
197 return true;
198
199 if (result < 0)
200 logger.LogError(new UnixIOException(result), "sd_notify {message} failed!", command);
201 else
202 logger.LogTrace("Could not send sd_notify {message}. Socket closed!", command);
203
204 return false;
205 }
206 }
207}
Native methods used by the code.
static int sd_notify(int unset_environment, [MarshalAs(UnmanagedType.LPUTF8Str)] string state)
See https://www.freedesktop.org/software/systemd/man/sd_notify.html.
Implements the SystemD notify service protocol.
readonly IRestartRegistration restartRegistration
The IRestartRegistration for the SystemDManager.
override async Task ExecuteAsync(CancellationToken cancellationToken)
static long GetMonotonicUsec()
Get the current total nanoseconds value of the CLOCK_MONOTONIC clock.
readonly ILogger< SystemDManager > logger
The ILogger for the SystemDManager.
SystemDManager(IHostApplicationLifetime applicationLifetime, IInstanceManager instanceManager, IServerControl serverControl, ILogger< SystemDManager > logger)
Initializes a new instance of the SystemDManager class.
ValueTask HandleRestart(Version? updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
Handle a restart of the server.A ValueTask representing the running operation.
const string SDNotifyWatchdog
The sd_notify command for notifying the watchdog we are alive.
bool restartInProgress
If TGS is going to restart.
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SystemDManager.
bool SendSDNotify(string command)
Send a sd_notify command .
readonly IInstanceManager instanceManager
The IInstanceManager for the SystemDManager.
Task Ready
Task that completes when the IInstanceManager finishes initializing.
Represents the lifetime of a IRestartHandler registration.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart.