tgstation-server 5.12.7
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
14
16{
21 {
25 const string SDNotifyWatchdog = "WATCHDOG=1";
26
30 readonly IHostApplicationLifetime applicationLifetime;
31
36
41
45 readonly ILogger<SystemDManager> logger;
46
50 readonly CancellationTokenSource watchdogCts;
51
55 Task runTask;
56
61
67 static long GetMonotonicUsec() => global::System.Diagnostics.Stopwatch.GetTimestamp(); // HACK: https://github.com/dotnet/runtime/blob/v6.0.19/src/libraries/Native/Unix/System.Native/pal_time.c#L51 clock_gettime_nsec_np is an OSX only thing apparently...
68
77 IHostApplicationLifetime applicationLifetime,
79 IServerControl serverControl,
80 ILogger<SystemDManager> logger)
81 {
82 this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
83 this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
84
85 ArgumentNullException.ThrowIfNull(serverControl);
86
87 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
88
89 restartRegistration = serverControl.RegisterForRestart(this);
90 try
91 {
92 watchdogCts = new CancellationTokenSource();
93 }
94 catch
95 {
96 restartRegistration.Dispose();
97 throw;
98 }
99 }
100
102 public void Dispose()
103 {
104 restartRegistration.Dispose();
105 watchdogCts.Dispose();
106 }
107
109 public Task HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
110 {
111 // If this is set, we know a gracefule SHUTDOWN was requested
112 restartInProgress = !handlerMayDelayShutdownWithExtremelyLongRunningTasks;
113 return Task.CompletedTask;
114 }
115
117 public Task StartAsync(CancellationToken cancellationToken)
118 {
120 {
121 logger.LogDebug("SystemD detected");
123 }
124 else
125 {
126 logger.LogDebug("SystemD not detected");
127 runTask = Task.CompletedTask;
128 }
129
130 return Task.CompletedTask;
131 }
132
134 public async Task StopAsync(CancellationToken cancellationToken)
135 {
136 watchdogCts.Cancel();
137 await runTask.WithToken(cancellationToken);
138 }
139
145 async Task RunAsync(CancellationToken cancellationToken)
146 {
147 if (applicationLifetime.ApplicationStarted.IsCancellationRequested)
148 throw new InvalidOperationException("RunAsync called after application started!");
149
150 logger.LogTrace("Installing lifetime handlers...");
151
152 var readyCounts = 0;
153 void CheckReady()
154 {
155 if (Interlocked.Increment(ref readyCounts) < 2)
156 return;
157
158 SendSDNotify("READY=1");
159 }
160
161 applicationLifetime.ApplicationStarted.Register(() => CheckReady());
162 applicationLifetime.ApplicationStopping.Register(
163 () => SendSDNotify(
165 ? $"RELOADING=1\nMONOTONIC_USEC={GetMonotonicUsec()}"
166 : "STOPPING=1"));
167
168 try
169 {
170 await instanceManager.Ready.WithToken(cancellationToken);
171 CheckReady();
172
173 var watchdogUsec = Environment.GetEnvironmentVariable("WATCHDOG_USEC");
174 if (String.IsNullOrWhiteSpace(watchdogUsec))
175 {
176 logger.LogDebug("WATCHDOG_USEC not present, not starting watchdog loop");
177 return;
178 }
179
180 var microseconds = UInt64.Parse(watchdogUsec, CultureInfo.InvariantCulture);
181 var timeoutIntervalMillis = (int)(microseconds / 1000);
182
183 logger.LogDebug("Starting watchdog loop with interval of {timeoutInterval}ms", timeoutIntervalMillis);
184
185 var timeoutInterval = TimeSpan.FromMilliseconds(timeoutIntervalMillis);
186 var nextExpectedTimeout = DateTimeOffset.UtcNow + timeoutInterval;
187 var timeToNextExpectedTimeout = nextExpectedTimeout - DateTimeOffset.UtcNow;
188 while (!cancellationToken.IsCancellationRequested)
189 {
190 var delayInterval = timeToNextExpectedTimeout / 2;
191 await Task.Delay(delayInterval, cancellationToken);
192
193 var notifySuccess = SendSDNotify(SDNotifyWatchdog);
194
195 var now = DateTimeOffset.UtcNow;
196 if (notifySuccess)
197 nextExpectedTimeout = now + timeoutInterval;
198
199 timeToNextExpectedTimeout = nextExpectedTimeout - now;
200
201 if (!notifySuccess)
202 logger.LogWarning("Missed systemd heartbeat! Expected timeout in {timeoutMs}ms...", timeToNextExpectedTimeout.TotalMilliseconds);
203 }
204 }
205 catch (OperationCanceledException ex)
206 {
207 logger.LogTrace(ex, "Watchdog loop cancelled!");
208 }
209 catch (Exception ex)
210 {
211 logger.LogError(ex, "Watchdog loop crashed!");
212 }
213
214 logger.LogDebug("Exited watchdog loop");
215 }
216
222 bool SendSDNotify(string command)
223 {
224 logger.LogTrace("Sending sd_notify {message}...", command);
225 int result;
226 try
227 {
228 result = NativeMethods.sd_notify(0, command);
229 }
230 catch (Exception ex)
231 {
232 logger.LogInformation(ex, "Exception attempting to invoke sd_notify!");
233 return false;
234 }
235
236 if (result > 0)
237 return true;
238
239 if (result < 0)
240 logger.LogError(new UnixIOException(result), "sd_notify READY=1 failed!");
241 else
242 logger.LogTrace("Could not send sd_notify {message}. Socket closed!", command);
243
244 return false;
245 }
246 }
247}
Native Windows 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.
Task runTask
The main task executing in the SystemDManager.
Task StartAsync(CancellationToken cancellationToken)
readonly IRestartRegistration restartRegistration
The IRestartRegistration for the SystemDManager.
async Task StopAsync(CancellationToken cancellationToken)
Task HandleRestart(Version updateVersion, bool handlerMayDelayShutdownWithExtremelyLongRunningTasks, CancellationToken cancellationToken)
Handle a restart of the server. A Task representing the running operation.
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.
const string SDNotifyWatchdog
The sd_notify command for notifying the watchdog we are alive.
bool restartInProgress
If TGS is going to restart.
readonly CancellationTokenSource watchdogCts
The CancellationTokenSource for runTask.
async Task RunAsync(CancellationToken cancellationToken)
Runs the SystemDManager.
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.