tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
OpenDreamInstallation.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Net.Http;
5using System.Net.Http.Headers;
6using System.Net.Mime;
7using System.Text;
8using System.Threading;
9using System.Threading.Tasks;
10
11using Microsoft.Extensions.Logging;
12
19
21{
26 {
28 public override EngineVersion Version { get; }
29
31 public override string ServerExePath { get; }
32
34 public override string CompilerExePath { get; }
35
37 public override bool PromptsForNetworkAccess => false;
38
40 public override bool HasStandardOutput => true;
41
43 public override bool PreferFileLogging => true;
44
46 public override bool UseDotnetDump => true;
47
49 public override Task InstallationTask { get; }
50
55
59 readonly IHttpClientFactory httpClientFactory;
60
64 readonly string serverDllPath;
65
69 readonly string compilerDllPath;
70
83 IIOManager installationIOManager,
85 IHttpClientFactory httpClientFactory,
86 string dotnetPath,
87 string serverDllPath,
88 string compilerDllPath,
89 Task installationTask,
90 EngineVersion version)
91 : base(installationIOManager)
92 {
93 this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
94 this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
95
96 ServerExePath = dotnetPath ?? throw new ArgumentNullException(nameof(dotnetPath));
97 CompilerExePath = dotnetPath;
98
99 this.serverDllPath = serverDllPath ?? throw new ArgumentNullException(nameof(serverDllPath));
100 this.compilerDllPath = compilerDllPath ?? throw new ArgumentNullException(nameof(compilerDllPath));
101 InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask));
102 Version = version ?? throw new ArgumentNullException(nameof(version));
103
104 if (version.Engine!.Value != EngineType.OpenDream)
105 throw new ArgumentException($"Invalid EngineType: {version.Engine.Value}", nameof(version));
106 }
107
109 public override string FormatServerArguments(
110 IDmbProvider dmbProvider,
111 IReadOnlyDictionary<string, string>? parameters,
112 DreamDaemonLaunchParameters launchParameters,
113 string accessIdentifier,
114 string? logFilePath)
115 {
116 ArgumentNullException.ThrowIfNull(dmbProvider);
117 ArgumentNullException.ThrowIfNull(launchParameters);
118 ArgumentNullException.ThrowIfNull(accessIdentifier);
119
120 var encodedParameters = EncodeParameters(parameters, launchParameters);
121 var parametersString = !String.IsNullOrEmpty(encodedParameters)
122 ? $" --cvar opendream.world_params=\"{encodedParameters}\""
123 : String.Empty;
124
125 var arguments = $"{serverDllPath} --cvar {(logFilePath != null ? $"log.path=\"{InstallationIOManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{InstallationIOManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar watchdog.token={accessIdentifier} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port!.Value} --cvar opendream.topic_port={launchParameters.OpenDreamTopicPort!.Value}{parametersString} --cvar opendream.json_path=\"./{dmbProvider.DmbName}\"";
126 return arguments;
127 }
128
130 public override string FormatCompilerArguments(string dmePath, string? additionalArguments)
131 {
132 if (String.IsNullOrWhiteSpace(additionalArguments))
133 additionalArguments = String.Empty;
134 else
135 additionalArguments = $"{additionalArguments.Trim()} ";
136
137 return $"{compilerDllPath} --suppress-unimplemented --notices-enabled {additionalArguments}\"{dmePath ?? throw new ArgumentNullException(nameof(dmePath))}\"";
138 }
139
141 public override async ValueTask StopServerProcess(
142 ILogger logger,
143 IProcess process,
144 string accessIdentifier,
145 ushort port,
146 CancellationToken cancellationToken)
147 {
148 ArgumentNullException.ThrowIfNull(logger);
149
150 const int MaximumTerminationSeconds = 5;
151
152 logger.LogTrace("Attempting Robust.Server graceful exit (Timeout: {seconds}s)...", MaximumTerminationSeconds);
153 var timeout = asyncDelayer.Delay(TimeSpan.FromSeconds(MaximumTerminationSeconds), cancellationToken).AsTask();
154 var lifetime = process.Lifetime;
155 if (lifetime.IsCompleted)
156 logger.LogTrace("Robust.Server already exited");
157
158 var stopwatch = Stopwatch.StartNew();
159 try
160 {
161 using var httpClient = httpClientFactory.CreateClient();
162 using var request = new HttpRequestMessage();
163 request.Headers.Add("WatchdogToken", accessIdentifier);
164 request.RequestUri = new Uri($"http://localhost:{port}/shutdown");
165 request.Content = new StringContent(
166 "{\"Reason\":\"TGS session termination\"}",
167 Encoding.UTF8,
168 new MediaTypeHeaderValue(MediaTypeNames.Application.Json));
169 request.Method = HttpMethod.Post;
170
171 var responseTask = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
172 try
173 {
174 await Task.WhenAny(timeout, lifetime, responseTask);
175 if (responseTask.IsCompleted)
176 {
177 using var response = await responseTask;
178 if (response.IsSuccessStatusCode)
179 {
180 logger.LogDebug("Robust.Server responded to the shutdown command successfully ({requestMs}ms). Waiting for exit...", stopwatch.ElapsedMilliseconds);
181 await Task.WhenAny(timeout, lifetime);
182 }
183 }
184
185 if (!lifetime.IsCompleted)
186 logger.LogWarning("Robust.Server graceful exit timed out!");
187 }
188 catch (Exception ex) when (ex is not OperationCanceledException)
189 {
190 logger.LogDebug(ex, "Unable to send graceful exit request to Robust.Server watchdog API!");
191 }
192
193 if (lifetime.IsCompleted)
194 {
195 logger.LogTrace("Robust.Server exited without termination");
196 return;
197 }
198 }
199 finally
200 {
201 logger.LogTrace("Robust.Server graceful shutdown attempt took {totalMs}ms", stopwatch.ElapsedMilliseconds);
202 }
203
204 await base.StopServerProcess(logger, process, accessIdentifier, port, cancellationToken);
205 }
206 }
207}
Information about an engine installation.
Implementation of IEngineInstallation for EngineType.OpenDream.
readonly IHttpClientFactory httpClientFactory
The IHttpClientFactory for the OpenDreamInstallation.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the OpenDreamInstallation.
override string FormatServerArguments(IDmbProvider dmbProvider, IReadOnlyDictionary< string, string >? parameters, DreamDaemonLaunchParameters launchParameters, string accessIdentifier, string? logFilePath)
override async ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
override string FormatCompilerArguments(string dmePath, string? additionalArguments)
OpenDreamInstallation(IIOManager installationIOManager, IAsyncDelayer asyncDelayer, IHttpClientFactory httpClientFactory, string dotnetPath, string serverDllPath, string compilerDllPath, Task installationTask, EngineVersion version)
Initializes a new instance of the OpenDreamInstallation class.
readonly string serverDllPath
Path to the Robust.Server.dll.
Provides absolute paths to the latest compiled .dmbs.
Interface for using filesystems.
Definition IIOManager.cs:14
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
Abstraction over a global::System.Diagnostics.Process.
Definition IProcess.cs:11
EngineType
The type of engine the codebase is using.
Definition EngineType.cs:7