Merge pull request #1787 from tgstation/v620 [TGSDeploy]

v6.2.0 Release
This commit is contained in:
Jordan Dominion
2024-02-03 21:10:10 -05:00
committed by GitHub
73 changed files with 5059 additions and 164 deletions
+5 -1
View File
@@ -539,7 +539,11 @@ Manual operations on the repository while an instance is running may lead to git
#### Byond
The `Byond` folder contains installations of [BYOND](https://www.byond.com/) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it.
The `Byond` folder contains installations of [BYOND](https://www.byond.com/) or [OpenDream](https://github.com/OpenDreamProject/OpenDream) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it.
##### Environment Variables
You can specify additional environment variables to launch your server/compiler with by adding `server.env`/`compiler.env` to your engine installation directory (i.e. `<instance>/Byond/515.1530/server.env`). These are [.env](https://hexdocs.pm/dotenvy/dotenv-file-format.html) files.
#### Compiler
+1 -1
View File
@@ -17,7 +17,7 @@
<ItemGroup>
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+2 -2
View File
@@ -18,9 +18,9 @@
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
<PackageReference Include="Moq" Version="4.20.70" />
<!-- Usage: MSTest execution -->
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
<!-- Usage: MSTest asserts etc... -->
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
</ItemGroup>
</Project>
+6 -6
View File
@@ -3,13 +3,13 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="WebpanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>6.1.5</TgsCoreVersion>
<TgsConfigVersion>5.0.0</TgsConfigVersion>
<TgsApiVersion>10.0.0</TgsApiVersion>
<TgsCoreVersion>6.2.0</TgsCoreVersion>
<TgsConfigVersion>5.1.0</TgsConfigVersion>
<TgsApiVersion>10.1.0</TgsApiVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>13.0.1</TgsApiLibraryVersion>
<TgsClientVersion>15.0.1</TgsClientVersion>
<TgsDmapiVersion>7.0.1</TgsDmapiVersion>
<TgsApiLibraryVersion>13.1.0</TgsApiLibraryVersion>
<TgsClientVersion>15.1.0</TgsClientVersion>
<TgsDmapiVersion>7.0.2</TgsDmapiVersion>
<TgsInteropVersion>5.8.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
+1 -1
View File
@@ -1,6 +1,6 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- This is in it's own file to help incremental building, changing it causes a complete rebuild of the web panel -->
<TgsWebpanelVersion>5.4.2</TgsWebpanelVersion>
<TgsWebpanelVersion>5.5.0</TgsWebpanelVersion>
</PropertyGroup>
</Project>
+4 -1
View File
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
#define TGS_DMAPI_VERSION "7.0.1"
#define TGS_DMAPI_VERSION "7.0.2"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -426,6 +426,7 @@
/**
* Send a message to connected chats. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies.
@@ -435,6 +436,7 @@
/**
* Send a private message to a specific user. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* user: The [/datum/tgs_chat_user] to PM.
@@ -444,6 +446,7 @@
/**
* Send a message to connected chats that are flagged as game-related in TGS. This function may sleep!
* If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game.
*
* message - The [/datum/tgs_message_content] to send.
* channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to.
+32 -22
View File
@@ -8,8 +8,12 @@
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/intercepted_message_queue
/// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call
var/list/offline_message_queue
var/list/custom_commands
var/list/test_merges
@@ -194,17 +198,7 @@
var/datum/tgs_chat_channel/channel = I
ids += channel.id
message2 = UpgradeDeprecatedChatMessage(message2)
if (!length(channels))
return
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids
if(intercepted_message_queue)
intercepted_message_queue += list(data)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
SendChatMessageRaw(message2, ids)
/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only)
var/list/channels = list()
@@ -213,26 +207,42 @@
if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only)))
channels += channel.id
SendChatMessageRaw(message2, channels)
/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user)
SendChatMessageRaw(message2, list(user.channel.id))
/datum/tgs_api/v5/proc/SendChatMessageRaw(datum/tgs_message_content/message2, list/channel_ids)
message2 = UpgradeDeprecatedChatMessage(message2)
if (!length(channels))
if (!length(channel_ids))
return
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids
if(intercepted_message_queue)
intercepted_message_queue += list(data)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
return
/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user)
message2 = UpgradeDeprecatedChatMessage(message2)
var/list/data = message2._interop_serialize()
data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id)
if(intercepted_message_queue)
intercepted_message_queue += list(data)
if(offline_message_queue)
offline_message_queue += list(data)
return
if(detached)
offline_message_queue = list(data)
WaitForReattach(FALSE)
data = offline_message_queue
offline_message_queue = null
for(var/queued_message in data)
SendChatDataRaw(queued_message)
else
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
SendChatDataRaw(data)
/datum/tgs_api/v5/proc/SendChatDataRaw(list/data)
Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
/datum/tgs_api/v5/ChatChannelInfo()
RequireInitialBridgeResponse()
@@ -98,6 +98,13 @@ namespace Tgstation.Server.Api.Models.Internal
[ResponseOptions]
public uint? MapThreads { get; set; }
/// <summary>
/// If minidumps should be taken instead of full dumps.
/// </summary>
[Required]
[ResponseOptions]
public bool? Minidumps { get; set; }
/// <summary>
/// Check if we match a given set of <paramref name="otherParameters"/>. <see cref="StartupTimeout"/> is excluded.
/// </summary>
@@ -116,7 +123,7 @@ namespace Tgstation.Server.Api.Models.Internal
&& AdditionalParameters == otherParameters.AdditionalParameters
&& StartProfiler == otherParameters.StartProfiler
&& LogOutput == otherParameters.LogOutput
&& MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, health check seconds, or health check dump as they don't matter in terms of the watchdog
&& MapThreads == otherParameters.MapThreads; // We intentionally don't check StartupTimeout, Minidumps, health check seconds, or health check dump as they don't matter in terms of the watchdog
}
}
}
@@ -117,5 +117,10 @@ namespace Tgstation.Server.Api.Rights
/// User can use <see cref="Models.Request.DreamDaemonRequest.BroadcastMessage"/>.
/// </summary>
BroadcastMessage = 1 << 20,
/// <summary>
/// User can use <see cref="Models.Internal.DreamDaemonLaunchParameters.Minidumps"/>.
/// </summary>
SetMinidumps = 1 << 21,
}
}
@@ -27,7 +27,7 @@
<!-- Usage: HTTP constants reference -->
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<!-- Usage: Decoding the 'nbf' property of JWTs -->
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.1.2" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
<!-- Usage: Primary JSON library -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- Usage: Data model annotating -->
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.0",
"version": "8.0.1",
"commands": [
"dotnet-ef"
]
@@ -852,6 +852,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in <see langword="true"/> if compilation succeeded, <see langword="false"/> otherwise.</returns>
async ValueTask<bool> RunDreamMaker(IEngineExecutableLock engineLock, Models.CompileJob job, CancellationToken cancellationToken)
{
var environment = await engineLock.LoadEnv(logger, true, cancellationToken);
var arguments = engineLock.FormatCompilerArguments($"{job.DmeName}.{DmeExtension}");
await using var dm = processExecutor.LaunchProcess(
@@ -859,6 +860,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ioManager.ResolvePath(
job.DirectoryName!.Value.ToString()),
arguments,
environment,
readStandardHandles: true,
noShellExecute: true);
@@ -953,6 +955,12 @@ namespace Tgstation.Server.Host.Components.Deployment
{
async ValueTask CleanDir()
{
if (sessionConfiguration.DelayCleaningFailedDeployments)
{
logger.LogDebug("Not cleaning up errored deployment directory {guid} due to config.", job.DirectoryName);
return;
}
logger.LogTrace("Cleaning compile directory...");
var jobPath = job.DirectoryName!.Value.ToString();
try
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Components.Engine
{
@@ -32,6 +33,9 @@ namespace Tgstation.Server.Host.Components.Engine
/// <inheritdoc />
public override bool PreferFileLogging => false;
/// <inheritdoc />
public override bool UseDotnetDump => false;
/// <inheritdoc />
public override Task InstallationTask { get; }
@@ -75,6 +79,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// <summary>
/// Initializes a new instance of the <see cref="ByondInstallation"/> class.
/// </summary>
/// <param name="installationIOManager">The <see cref="IIOManager"/> for the <see cref="EngineInstallationBase"/>.</param>
/// <param name="installationTask">The value of <see cref="InstallationTask"/>.</param>
/// <param name="version">The value of <see cref="Version"/>.</param>
/// <param name="dreamDaemonPath">The value of <see cref="ServerExePath"/>.</param>
@@ -82,12 +87,14 @@ namespace Tgstation.Server.Host.Components.Engine
/// <param name="supportsCli">If a CLI application is being used.</param>
/// <param name="supportsMapThreads">The value of <see cref="supportsMapThreads"/>.</param>
public ByondInstallation(
IIOManager installationIOManager,
Task installationTask,
EngineVersion version,
string dreamDaemonPath,
string dreamMakerPath,
bool supportsCli,
bool supportsMapThreads)
: base(installationIOManager)
{
InstallationTask = installationTask ?? throw new ArgumentNullException(nameof(installationTask));
ArgumentNullException.ThrowIfNull(version);
@@ -88,21 +88,22 @@ namespace Tgstation.Server.Host.Components.Engine
{
CheckVersionValidity(version);
var binPathForVersion = IOManager.ConcatPath(path, ByondBinPath);
var installationIOManager = new ResolvingIOManager(IOManager, path);
var supportsMapThreads = version.Version >= MapThreadsVersion;
return new ByondInstallation(
installationIOManager,
installationTask,
version,
IOManager.ResolvePath(
IOManager.ConcatPath(
binPathForVersion,
installationIOManager.ResolvePath(
installationIOManager.ConcatPath(
ByondBinPath,
GetDreamDaemonName(
version.Version!,
out var supportsCli))),
IOManager.ResolvePath(
IOManager.ConcatPath(
binPathForVersion,
installationIOManager.ResolvePath(
installationIOManager.ConcatPath(
ByondBinPath,
DreamMakerName)),
supportsCli,
supportsMapThreads);
@@ -41,8 +41,8 @@ namespace Tgstation.Server.Host.Components.Engine
=> DelegateCall(version, installer => installer.DownloadVersion(version, jobProgressReporter, cancellationToken));
/// <inheritdoc />
public ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken)
=> DelegateCall(version, installer => installer.Install(version, path, cancellationToken));
public ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
=> DelegateCall(version, installer => installer.Install(version, path, deploymentPipelineProcesses, cancellationToken));
/// <inheritdoc />
public ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
@@ -36,6 +36,9 @@ namespace Tgstation.Server.Host.Components.Engine
/// <inheritdoc />
public Task InstallationTask => Instance.InstallationTask;
/// <inheritdoc />
public bool UseDotnetDump => Instance.UseDotnetDump;
/// <inheritdoc />
public void DoNotDeleteThisSession() => DangerousDropReference();
@@ -62,5 +65,9 @@ namespace Tgstation.Server.Host.Components.Engine
accessIdentifier,
port,
cancellationToken);
/// <inheritdoc />
public ValueTask<Dictionary<string, string>?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken)
=> Instance.LoadEnv(logger, forCompiler, cancellationToken);
}
}
@@ -1,15 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using DotEnv.Core;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Components.Deployment;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components.Engine
@@ -35,9 +39,17 @@ namespace Tgstation.Server.Host.Components.Engine
/// <inheritdoc />
public abstract bool PromptsForNetworkAccess { get; }
/// <inheritdoc />
public abstract bool UseDotnetDump { get; }
/// <inheritdoc />
public abstract Task InstallationTask { get; }
/// <summary>
/// The <see cref="IIOManager"/> pointing to the installation directory.
/// </summary>
protected IIOManager InstallationIOManager { get; }
/// <summary>
/// Encode given parameters for passing as world.params on the command line.
/// </summary>
@@ -56,6 +68,15 @@ namespace Tgstation.Server.Host.Components.Engine
return parametersString;
}
/// <summary>
/// Initializes a new instance of the <see cref="EngineInstallationBase"/> class.
/// </summary>
/// <param name="installationIOManager">The value of <see cref="InstallationIOManager"/>.</param>
public EngineInstallationBase(IIOManager installationIOManager)
{
InstallationIOManager = installationIOManager ?? throw new ArgumentNullException(nameof(installationIOManager));
}
/// <inheritdoc />
public abstract string FormatCompilerArguments(string dmePath);
@@ -69,10 +90,45 @@ namespace Tgstation.Server.Host.Components.Engine
/// <inheritdoc />
public virtual async ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(logger);
cancellationToken.ThrowIfCancellationRequested();
logger.LogTrace("Terminating engine server process...");
process.Terminate();
await process.Lifetime;
}
/// <inheritdoc />
public async ValueTask<Dictionary<string, string>?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(logger);
var envFile = forCompiler
? "compiler.env"
: "server.env";
if (!await InstallationIOManager.FileExists(envFile, cancellationToken))
{
logger.LogTrace("No {envFile} present in engine installation {version}", envFile, Version);
return null;
}
logger.LogDebug("Loading {envFile} for engine installation {version}...", envFile, Version);
var fileBytes = await InstallationIOManager.ReadAllBytes(envFile, cancellationToken);
var fileContents = Encoding.UTF8.GetString(fileBytes);
var parser = new EnvParser();
try
{
var variables = parser.Parse(fileContents);
return variables.ToDictionary();
}
catch (Exception ex)
{
logger.LogWarning(ex, "Unable to parse {envFile}!", envFile);
return null;
}
}
}
}
@@ -46,7 +46,7 @@ namespace Tgstation.Server.Host.Components.Engine
public abstract Task CleanCache(CancellationToken cancellationToken);
/// <inheritdoc />
public abstract ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken);
public abstract ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken);
/// <inheritdoc />
public abstract ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken);
@@ -438,6 +438,7 @@ namespace Tgstation.Server.Host.Components.Engine
installLock = installationContainer.AddReference();
}
var deploymentPipelineProcesses = !neededForLock;
try
{
if (installedOrInstalling)
@@ -471,18 +472,18 @@ namespace Tgstation.Server.Host.Components.Engine
progressReporter.StageName = "Running event";
var versionString = version.ToString();
await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List<string> { versionString }, false, cancellationToken);
await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List<string> { versionString }, deploymentPipelineProcesses, cancellationToken);
await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken);
await InstallVersionFiles(progressReporter, version, customVersionStream, deploymentPipelineProcesses, cancellationToken);
ourTcs.SetResult();
await eventConsumer.HandleEvent(EventType.EngineInstallComplete, new List<string> { versionString }, false, cancellationToken);
await eventConsumer.HandleEvent(EventType.EngineInstallComplete, new List<string> { versionString }, deploymentPipelineProcesses, cancellationToken);
}
catch (Exception ex)
{
if (ex is not OperationCanceledException)
await eventConsumer.HandleEvent(EventType.EngineInstallFail, new List<string> { ex.Message }, false, cancellationToken);
await eventConsumer.HandleEvent(EventType.EngineInstallFail, new List<string> { ex.Message }, deploymentPipelineProcesses, cancellationToken);
lock (installedVersions)
installedVersions.Remove(version);
@@ -506,9 +507,15 @@ namespace Tgstation.Server.Host.Components.Engine
/// <param name="progressReporter">The optional <see cref="JobProgressReporter"/> for the operation.</param>
/// <param name="version">The <see cref="EngineVersion"/> being installed with the <see cref="Version.Build"/> number set if appropriate.</param>
/// <param name="customVersionStream">Custom zip file <see cref="Stream"/> to use. Will cause a <see cref="Version.Build"/> number to be added.</param>
/// <param name="deploymentPipelineProcesses">If processes should be launched as part of the deployment pipeline.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
async ValueTask InstallVersionFiles(JobProgressReporter? progressReporter, EngineVersion version, Stream? customVersionStream, CancellationToken cancellationToken)
async ValueTask InstallVersionFiles(
JobProgressReporter? progressReporter,
EngineVersion version,
Stream? customVersionStream,
bool deploymentPipelineProcesses,
CancellationToken cancellationToken)
{
var installFullPath = ioManager.ResolvePath(version.ToString());
async ValueTask DirectoryCleanup()
@@ -554,7 +561,7 @@ namespace Tgstation.Server.Host.Components.Engine
if (progressReporter != null)
progressReporter.StageName = "Running installation actions";
await engineInstaller.Install(version, installFullPath, cancellationToken);
await engineInstaller.Install(version, installFullPath, deploymentPipelineProcesses, cancellationToken);
if (progressReporter != null)
progressReporter.StageName = "Writing version file";
@@ -46,6 +46,11 @@ namespace Tgstation.Server.Host.Components.Engine
/// </summary>
bool PreferFileLogging { get; }
/// <summary>
/// If dotnet-dump should be used to create process dumps for this installation.
/// </summary>
bool UseDotnetDump { get; }
/// <summary>
/// The <see cref="Task"/> that completes when the BYOND version finished installing.
/// </summary>
@@ -82,5 +87,14 @@ namespace Tgstation.Server.Host.Components.Engine
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken);
/// <summary>
/// Loads the environment settings for either the server or compiler.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
/// <param name="forCompiler">If <see langword="false"/> server.env will be loaded. If <see langword="true"/> compiler.env will be loaded.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the environment <see cref="Dictionary{TKey, TValue}"/> or <see langword="null"/> if the target environment file doesn't exist.</returns>
ValueTask<Dictionary<string, string>?> LoadEnv(ILogger logger, bool forCompiler, CancellationToken cancellationToken);
}
}
@@ -34,9 +34,10 @@ namespace Tgstation.Server.Host.Components.Engine
/// </summary>
/// <param name="version">The <see cref="EngineVersion"/> being installed.</param>
/// <param name="path">The path to the installation.</param>
/// <param name="deploymentPipelineProcesses">If the operation should consider processes it launches to be part of the deployment pipeline.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken);
ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken);
/// <summary>
/// Does actions necessary to get upgrade a version installed by a previous version of TGS.
@@ -45,12 +45,10 @@ namespace Tgstation.Server.Host.Components.Engine
public override bool PreferFileLogging => true;
/// <inheritdoc />
public override Task InstallationTask { get; }
public override bool UseDotnetDump => true;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="OpenDreamInstallation"/>.
/// </summary>
readonly IIOManager ioManager;
/// <inheritdoc />
public override Task InstallationTask { get; }
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="OpenDreamInstallation"/>.
@@ -65,7 +63,7 @@ namespace Tgstation.Server.Host.Components.Engine
/// <summary>
/// Initializes a new instance of the <see cref="OpenDreamInstallation"/> class.
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
/// <param name="installationIOManager">The <see cref="IIOManager"/> for the <see cref="EngineInstallationBase"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="httpClientFactory">The value of <see cref="httpClientFactory"/>.</param>
/// <param name="serverExePath">The value of <see cref="ServerExePath"/>.</param>
@@ -73,15 +71,15 @@ namespace Tgstation.Server.Host.Components.Engine
/// <param name="installationTask">The value of <see cref="InstallationTask"/>.</param>
/// <param name="version">The value of <see cref="Version"/>.</param>
public OpenDreamInstallation(
IIOManager ioManager,
IIOManager installationIOManager,
IAsyncDelayer asyncDelayer,
IAbstractHttpClientFactory httpClientFactory,
string serverExePath,
string compilerExePath,
Task installationTask,
EngineVersion version)
: base(installationIOManager)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
ServerExePath = serverExePath ?? throw new ArgumentNullException(nameof(serverExePath));
@@ -109,7 +107,7 @@ namespace Tgstation.Server.Host.Components.Engine
var parametersString = EncodeParameters(parameters, launchParameters);
var arguments = $"--cvar {(logFilePath != null ? $"log.path=\"{ioManager.GetDirectoryName(logFilePath)}\" --cvar log.format=\"{ioManager.GetFileName(logFilePath)}\"" : "log.enabled=false")} --cvar watchdog.token={accessIdentifier} --cvar log.runtimelog=false --cvar net.port={launchParameters.Port!.Value} --cvar opendream.topic_port=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\"";
var arguments = $"--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=0 --cvar opendream.world_params=\"{parametersString}\" --cvar opendream.json_path=\"./{dmbProvider.DmbName}\"";
return arguments;
}
@@ -125,6 +123,8 @@ namespace Tgstation.Server.Host.Components.Engine
ushort port,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(logger);
const int MaximumTerminationSeconds = 5;
logger.LogTrace("Attempting Robust.Server graceful exit (Timeout: {seconds}s)...", MaximumTerminationSeconds);
@@ -9,7 +9,6 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.Common;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
@@ -124,7 +123,7 @@ namespace Tgstation.Server.Host.Components.Engine
CheckVersionValidity(version);
GetExecutablePaths(path, out var serverExePath, out var compilerExePath);
return new OpenDreamInstallation(
IOManager,
new ResolvingIOManager(IOManager, path),
asyncDelayer,
httpClientFactory,
serverExePath,
@@ -193,7 +192,7 @@ namespace Tgstation.Server.Host.Components.Engine
}
/// <inheritdoc />
public override async ValueTask Install(EngineVersion version, string installPath, CancellationToken cancellationToken)
public override async ValueTask Install(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
CheckVersionValidity(version);
ArgumentNullException.ThrowIfNull(installPath);
@@ -232,21 +231,10 @@ namespace Tgstation.Server.Host.Components.Engine
await Task.WhenAll(dirsMoveTasks.Concat(filesMoveTask));
}
var dotnetPaths = DotnetHelper.GetPotentialDotnetPaths(platformIdentifier.IsWindows)
.ToList();
var tasks = dotnetPaths
.Select(path => IOManager.FileExists(path, cancellationToken))
.ToList();
await Task.WhenAll(tasks);
var selectedPathIndex = tasks.FindIndex(pathValidTask => pathValidTask.Result);
if (selectedPathIndex == -1)
var dotnetPath = await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken);
if (dotnetPath == null)
throw new JobException(ErrorCode.OpenDreamCantFindDotnet);
var dotnetPath = dotnetPaths[selectedPathIndex];
const string DeployDir = "tgs_deploy";
int? buildExitCode = null;
await HandleExtremelyLongPathOperation(
@@ -258,10 +246,11 @@ namespace Tgstation.Server.Host.Components.Engine
shortenedPath,
$"run -c Release --project OpenDreamPackageTool -- --tgs -o {shortenedDeployPath}",
null,
null,
!GeneralConfiguration.OpenDreamSuppressInstallOutput,
!GeneralConfiguration.OpenDreamSuppressInstallOutput);
if (SessionConfiguration.LowPriorityDeploymentProcesses)
if (deploymentPipelineProcesses && SessionConfiguration.LowPriorityDeploymentProcesses)
buildProcess.AdjustPriority(false);
using (cancellationToken.Register(() => buildProcess.Terminate()))
@@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Engine
}
/// <inheritdoc />
public override ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken)
public override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
CheckVersionValidity(version);
ArgumentNullException.ThrowIfNull(path);
@@ -127,7 +127,7 @@ namespace Tgstation.Server.Host.Components.Engine
public void Dispose() => semaphore.Dispose();
/// <inheritdoc />
public override ValueTask Install(EngineVersion version, string path, CancellationToken cancellationToken)
public override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
CheckVersionValidity(version);
ArgumentNullException.ThrowIfNull(path);
@@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Engine
if (!generalConfiguration.SkipAddingByondFirewallException)
{
var firewallTask = AddDreamDaemonToFirewall(version, path, cancellationToken);
var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
tasks.Add(firewallTask);
}
@@ -165,7 +165,7 @@ namespace Tgstation.Server.Host.Components.Engine
return;
Logger.LogInformation("BYOND Version {version} needs dd.exe added to firewall", version);
await AddDreamDaemonToFirewall(version, path, cancellationToken);
await AddDreamDaemonToFirewall(version, path, true, cancellationToken);
}
/// <inheritdoc />
@@ -243,9 +243,10 @@ namespace Tgstation.Server.Host.Components.Engine
/// </summary>
/// <param name="version">The BYOND <see cref="EngineVersion"/>.</param>
/// <param name="path">The path to the BYOND installation.</param>
/// <param name="deploymentPipelineProcesses">If the operation is part of the deployment pipeline.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, CancellationToken cancellationToken)
async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
var dreamDaemonName = GetDreamDaemonName(version.Version!, out var usesDDExe);
@@ -268,7 +269,7 @@ namespace Tgstation.Server.Host.Components.Engine
Logger,
ruleName,
dreamDaemonPath,
sessionConfiguration.LowPriorityDeploymentProcesses,
deploymentPipelineProcesses && sessionConfiguration.LowPriorityDeploymentProcesses,
cancellationToken);
}
catch (Exception ex)
@@ -66,15 +66,17 @@ namespace Tgstation.Server.Host.Components.Engine
}
/// <inheritdoc />
public override ValueTask Install(EngineVersion version, string installPath, CancellationToken cancellationToken)
public override ValueTask Install(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
var installTask = base.Install(
version,
installPath,
deploymentPipelineProcesses,
cancellationToken);
var firewallTask = AddServerFirewallException(
version,
installPath,
deploymentPipelineProcesses,
cancellationToken);
return ValueTaskExtensions.WhenAll(installTask, firewallTask);
@@ -101,9 +103,10 @@ namespace Tgstation.Server.Host.Components.Engine
/// </summary>
/// <param name="version">The BYOND <see cref="EngineVersion"/>.</param>
/// <param name="path">The path to the BYOND installation.</param>
/// <param name="deploymentPipelineProcesses">If the operation is part of the deployment pipeline.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
async ValueTask AddServerFirewallException(EngineVersion version, string path, CancellationToken cancellationToken)
async ValueTask AddServerFirewallException(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
{
if (GeneralConfiguration.SkipAddingByondFirewallException)
return;
@@ -123,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Engine
Logger,
ruleName,
serverExePath,
SessionConfiguration.LowPriorityDeploymentProcesses,
deploymentPipelineProcesses && SessionConfiguration.LowPriorityDeploymentProcesses,
cancellationToken);
}
catch (Exception ex)
@@ -135,6 +135,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="IDotnetDumpService"/> for the <see cref="InstanceFactory"/>.
/// </summary>
readonly IDotnetDumpService dotnetDumpService;
/// <summary>
/// The <see cref="GeneralConfiguration"/> for the <see cref="InstanceFactory"/>.
/// </summary>
@@ -177,6 +182,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
/// <param name="remoteDeploymentManagerFactory">The value of <see cref="remoteDeploymentManagerFactory"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="dotnetDumpService">The value of <see cref="dotnetDumpService"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
/// <param name="sessionConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="sessionConfiguration"/>.</param>
public InstanceFactory(
@@ -201,6 +207,7 @@ namespace Tgstation.Server.Host.Components
IFileTransferTicketProvider fileTransferService,
IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory,
IAsyncDelayer asyncDelayer,
IDotnetDumpService dotnetDumpService,
IOptions<GeneralConfiguration> generalConfigurationOptions,
IOptions<SessionConfiguration> sessionConfigurationOptions)
{
@@ -225,6 +232,7 @@ namespace Tgstation.Server.Host.Components
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
this.remoteDeploymentManagerFactory = remoteDeploymentManagerFactory ?? throw new ArgumentNullException(nameof(remoteDeploymentManagerFactory));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService));
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
}
@@ -271,7 +279,11 @@ namespace Tgstation.Server.Host.Components
var repoManager = repositoryManagerFactory.CreateRepositoryManager(repoIoManager, eventConsumer);
try
{
var engineManager = new EngineManager(byondIOManager, engineInstaller, eventConsumer, loggerFactory.CreateLogger<EngineManager>());
var engineManager = new EngineManager(
byondIOManager,
engineInstaller,
eventConsumer,
loggerFactory.CreateLogger<EngineManager>());
var dmbFactory = new DmbFactory(
databaseContextFactory,
@@ -309,6 +321,7 @@ namespace Tgstation.Server.Host.Components
serverPortProvider,
eventConsumer,
asyncDelayer,
dotnetDumpService,
loggerFactory,
loggerFactory.CreateLogger<SessionControllerFactory>(),
sessionConfiguration,
@@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
bool DMApiAvailable { get; }
/// <summary>
/// The file extension to use for process dumps created from this session.
/// </summary>
string DumpFileExtension { get; }
/// <summary>
/// Releases the <see cref="IProcess"/> without terminating it. Also calls <see cref="IDisposable.Dispose"/>.
/// </summary>
@@ -104,6 +104,11 @@ namespace Tgstation.Server.Host.Components.Session
/// <inheritdoc />
public bool ProcessingRebootBridgeRequest => rebootBridgeRequestsProcessing > 0;
/// <inheritdoc />
public string DumpFileExtension => engineLock.UseDotnetDump
? ".net.dmp"
: ".dmp";
/// <summary>
/// The up to date <see cref="Session.ReattachInformation"/>.
/// </summary>
@@ -149,6 +154,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="IDotnetDumpService"/> for the <see cref="SessionController"/>.
/// </summary>
readonly IDotnetDumpService dotnetDumpService;
/// <summary>
/// The <see cref="TaskCompletionSource"/> that completes when DD makes it's first bridge request.
/// </summary>
@@ -236,7 +246,8 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="chat">The value of <see cref="chat"/>.</param>
/// <param name="chatTrackingContext">The value of <see cref="chatTrackingContext"/>.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="SessionController"/>.</param>
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="SessionController"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="dotnetDumpService">The value of <see cref="dotnetDumpService"/>.</param>
/// <param name="logger">The value of <see cref="Chunker.Logger"/>.</param>
/// <param name="postLifetimeCallback">The <see cref="Func{TResult}"/> returning a <see cref="ValueTask"/> to be run after the <paramref name="process"/> ends.</param>
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/>.</param>
@@ -253,6 +264,7 @@ namespace Tgstation.Server.Host.Components.Session
IChatManager chat,
IAssemblyInformationProvider assemblyInformationProvider,
IAsyncDelayer asyncDelayer,
IDotnetDumpService dotnetDumpService,
ILogger<SessionController> logger,
Func<ValueTask> postLifetimeCallback,
uint? startupTimeout,
@@ -272,6 +284,7 @@ namespace Tgstation.Server.Host.Components.Session
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService));
apiValidationSession = apiValidate;
@@ -474,7 +487,13 @@ namespace Tgstation.Server.Host.Components.Session
cancellationToken);
/// <inheritdoc />
public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken) => process.CreateDump(outputFile, cancellationToken);
public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
{
if (engineLock.UseDotnetDump)
return dotnetDumpService.Dump(process, outputFile, minidump, cancellationToken);
return process.CreateDump(outputFile, minidump, cancellationToken);
}
/// <summary>
/// The <see cref="Task{TResult}"/> for <see cref="LaunchResult"/>.
@@ -106,6 +106,11 @@ namespace Tgstation.Server.Host.Components.Session
/// </summary>
readonly IAsyncDelayer asyncDelayer;
/// <summary>
/// The <see cref="IDotnetDumpService"/> for the <see cref="SessionControllerFactory"/>.
/// </summary>
readonly IDotnetDumpService dotnetDumpService;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="SessionControllerFactory"/>.
/// </summary>
@@ -178,6 +183,7 @@ namespace Tgstation.Server.Host.Components.Session
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/>.</param>
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
/// <param name="dotnetDumpService">The value of <see cref="dotnetDumpService"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
/// <param name="sessionConfiguration">The value of <see cref="sessionConfiguration"/>.</param>
@@ -196,6 +202,7 @@ namespace Tgstation.Server.Host.Components.Session
IServerPortProvider serverPortProvider,
IEventConsumer eventConsumer,
IAsyncDelayer asyncDelayer,
IDotnetDumpService dotnetDumpService,
ILoggerFactory loggerFactory,
ILogger<SessionControllerFactory> logger,
SessionConfiguration sessionConfiguration,
@@ -215,6 +222,7 @@ namespace Tgstation.Server.Host.Components.Session
this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
this.dotnetDumpService = dotnetDumpService ?? throw new ArgumentNullException(nameof(dotnetDumpService));
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.sessionConfiguration = sessionConfiguration ?? throw new ArgumentNullException(nameof(sessionConfiguration));
@@ -346,6 +354,7 @@ namespace Tgstation.Server.Host.Components.Session
chat,
assemblyInformationProvider,
asyncDelayer,
dotnetDumpService,
loggerFactory.CreateLogger<SessionController>(),
() => LogDDOutput(
process,
@@ -436,6 +445,7 @@ namespace Tgstation.Server.Host.Components.Session
chat,
assemblyInformationProvider,
asyncDelayer,
dotnetDumpService,
loggerFactory.CreateLogger<SessionController>(),
() => ValueTask.CompletedTask,
null,
@@ -490,6 +500,7 @@ namespace Tgstation.Server.Host.Components.Session
CancellationToken cancellationToken)
{
// important to run on all ports to allow port changing
var environment = await engineLock.LoadEnv(logger, false, cancellationToken);
var arguments = engineLock.FormatServerArguments(
dmbProvider,
new Dictionary<string, string>
@@ -507,6 +518,7 @@ namespace Tgstation.Server.Host.Components.Session
engineLock.ServerExePath,
dmbProvider.Directory,
arguments,
environment,
logFilePath,
engineLock.HasStandardOutput,
true);
@@ -1225,25 +1225,31 @@ namespace Tgstation.Server.Host.Components.Watchdog
async ValueTask CreateDumpNoLock(CancellationToken cancellationToken)
{
const string DumpDirectory = "ProcessDumps";
var dumpFileNameTemplate = diagnosticsIOManager.ResolvePath(
diagnosticsIOManager.ConcatPath(
DumpDirectory,
$"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}.dmp"));
var dumpFileName = dumpFileNameTemplate;
var iteration = 0;
while (await diagnosticsIOManager.FileExists(dumpFileName, cancellationToken))
dumpFileName = $"{dumpFileNameTemplate} ({++iteration})";
if (iteration == 0)
await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken);
var session = GetActiveController();
if (session?.Lifetime.IsCompleted != false)
throw new JobException(ErrorCode.GameServerOffline);
var dumpFileExtension = session.DumpFileExtension;
var dumpFileNameTemplate = diagnosticsIOManager.ResolvePath(
diagnosticsIOManager.ConcatPath(
DumpDirectory,
$"DreamDaemon-{DateTimeOffset.UtcNow.ToFileStamp()}"));
var dumpFileName = $"{dumpFileNameTemplate}{dumpFileExtension}";
var iteration = 0;
while (await diagnosticsIOManager.FileExists(dumpFileName, cancellationToken))
dumpFileName = $"{dumpFileNameTemplate} ({++iteration}){dumpFileExtension}";
if (iteration == 0)
await diagnosticsIOManager.CreateDirectory(DumpDirectory, cancellationToken);
if (session.Lifetime.IsCompleted)
throw new JobException(ErrorCode.GameServerOffline);
Logger.LogInformation("Dumping session to {dumpFileName}...", dumpFileName);
await session.CreateDump(dumpFileName, cancellationToken);
await session.CreateDump(dumpFileName, ActiveLaunchParameters.Minidumps!.Value, cancellationToken);
}
}
}
@@ -187,6 +187,9 @@ namespace Tgstation.Server.Host.Configuration
else if (this.GetCopyDirectoryTaskThrottle() < 1)
throw new InvalidOperationException(
$"{nameof(DeploymentDirectoryCopyTasksPerCore)} is too large for the CPU core count of {Environment.ProcessorCount} and overflows a 32-bit signed integer. Please lower the value!");
if (ByondTopicTimeout <= 1000)
logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!", ByondTopicTimeout);
}
}
}
@@ -24,5 +24,10 @@
/// If the deployment DreamMaker and DreamDaemon instances are set to be below normal priority processes.
/// </summary>
public bool LowPriorityDeploymentProcesses { get; set; }
/// <summary>
/// If <see langword="true"/>, deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined.
/// </summary>
public bool DelayCleaningFailedDeployments { get; set; }
}
}
@@ -149,7 +149,8 @@ namespace Tgstation.Server.Host.Controllers
| DreamDaemonRights.SetProfiler
| DreamDaemonRights.SetLogOutput
| DreamDaemonRights.SetMapThreads
| DreamDaemonRights.BroadcastMessage)]
| DreamDaemonRights.BroadcastMessage
| DreamDaemonRights.SetMinidumps)]
[ProducesResponseType(typeof(DreamDaemonResponse), 200)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -222,7 +223,8 @@ namespace Tgstation.Server.Host.Controllers
|| CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
|| CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
|| CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
|| CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads))
|| CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads)
|| CheckModified(x => x.Minidumps, DreamDaemonRights.SetMinidumps))
return Forbid();
return await WithComponentInstance(
@@ -379,6 +381,7 @@ namespace Tgstation.Server.Host.Controllers
result.StartProfiler = settings.StartProfiler;
result.LogOutput = settings.LogOutput;
result.MapThreads = settings.MapThreads;
result.Minidumps = settings.Minidumps;
}
if (revision)
@@ -732,6 +732,7 @@ namespace Tgstation.Server.Host.Controllers
StartProfiler = false,
LogOutput = false,
MapThreads = 0,
Minidumps = true,
},
DreamMakerSettings = new DreamMakerSettings
{
@@ -365,11 +365,9 @@ namespace Tgstation.Server.Host.Core
}
// only global repo manager should be for the OD repo
// god help me if we need more
var openDreamRepositoryDirectory = ioManager.ConcatPath(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData,
Environment.SpecialFolderOption.DoNotVerify),
assemblyInformationProvider.VersionPrefix,
ioManager.GetPathInLocalDirectory(assemblyInformationProvider),
"OpenDreamRepository");
services.AddSingleton(
services => services
@@ -416,6 +414,7 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton<IChatManagerFactory, ChatManagerFactory>();
services.AddSingleton<IServerUpdater, ServerUpdater>();
services.AddSingleton<IServerUpdateInitiator, ServerUpdateInitiator>();
services.AddSingleton<IDotnetDumpService, DotnetDumpService>();
// configure misc services
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
@@ -375,22 +375,22 @@ namespace Tgstation.Server.Host.Database
/// <summary>
/// Used by unit tests to remind us to setup the correct MSSQL migration downgrades.
/// </summary>
internal static readonly Type MSLatestMigration = typeof(MSAddTopicPort);
internal static readonly Type MSLatestMigration = typeof(MSAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct MYSQL migration downgrades.
/// </summary>
internal static readonly Type MYLatestMigration = typeof(MYAddTopicPort);
internal static readonly Type MYLatestMigration = typeof(MYAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades.
/// </summary>
internal static readonly Type PGLatestMigration = typeof(PGAddTopicPort);
internal static readonly Type PGLatestMigration = typeof(PGAddMinidumpsOption);
/// <summary>
/// Used by unit tests to remind us to setup the correct SQLite migration downgrades.
/// </summary>
internal static readonly Type SLLatestMigration = typeof(SLAddTopicPort);
internal static readonly Type SLLatestMigration = typeof(SLAddMinidumpsOption);
/// <inheritdoc />
#pragma warning disable CA1502 // Cyclomatic complexity
@@ -419,6 +419,16 @@ namespace Tgstation.Server.Host.Database
string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType));
if (targetVersion < new Version(6, 2, 0))
targetMigration = currentDatabaseType switch
{
DatabaseType.MySql => nameof(MYAddTopicPort),
DatabaseType.PostgresSql => nameof(PGAddTopicPort),
DatabaseType.SqlServer => nameof(MSAddTopicPort),
DatabaseType.Sqlite => nameof(SLAddTopicPort),
_ => BadDatabaseType(),
};
if (targetVersion < new Version(6, 0, 0))
targetMigration = currentDatabaseType switch
{
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class MSAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "bit",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class MYAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "tinyint(1)",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class PGAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "boolean",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <inheritdoc />
public partial class SLAddMinidumpsOption : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
// This was originally minidumps on Linux and full dumps on Windows
var defaultValue = !new PlatformIdentifier().IsWindows;
migrationBuilder.AddColumn<bool>(
name: "Minidumps",
table: "DreamDaemonSettings",
type: "INTEGER",
nullable: false,
defaultValue: defaultValue);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
ArgumentNullException.ThrowIfNull(migrationBuilder);
migrationBuilder.DropColumn(
name: "Minidumps",
table: "DreamDaemonSettings");
}
}
}
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
@@ -219,6 +219,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired()
.HasColumnType("int unsigned");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("tinyint(1)");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("smallint unsigned");
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -209,6 +209,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("MapThreads")
.HasColumnType("bigint");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("boolean");
b.Property<int>("Port")
.HasColumnType("integer");
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("ProductVersion", "8.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
@@ -211,6 +211,10 @@ namespace Tgstation.Server.Host.Database.Migrations
b.Property<long>("MapThreads")
.HasColumnType("bigint");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("bit");
b.Property<int>("Port")
.HasColumnType("int");
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
@@ -201,6 +201,10 @@ namespace Tgstation.Server.Host.Database.Migrations
.IsRequired()
.HasColumnType("INTEGER");
b.Property<bool?>("Minidumps")
.IsRequired()
.HasColumnType("INTEGER");
b.Property<ushort?>("Port")
.IsRequired()
.HasColumnType("INTEGER");
@@ -0,0 +1,31 @@
using System;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Extensions
{
/// <summary>
/// Extension methods for <see cref="IIOManager"/>.
/// </summary>
static class IOManagerExtensions
{
/// <summary>
/// Gets the local application data folder used by TGS.
/// </summary>
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to use.</param>
/// <returns>The path to the local application data directory used by TGS.</returns>
public static string GetPathInLocalDirectory(this IIOManager ioManager, IAssemblyInformationProvider assemblyInformationProvider)
{
ArgumentNullException.ThrowIfNull(ioManager);
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
return ioManager.ConcatPath(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData, // we use local application data here instead of comman application data because we store stuff here we don't want other users interfering with
Environment.SpecialFolderOption.DoNotVerify),
assemblyInformationProvider.VersionPrefix);
}
}
}
@@ -0,0 +1,54 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Extensions.Logging;
namespace Tgstation.Server.Host.System
{
/// <inheritdoc />
sealed class DotnetDumpService : IDotnetDumpService
{
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="DotnetDumpService"/>.
/// </summary>
readonly ILogger<DotnetDumpService> logger;
/// <summary>
/// Initializes a new instance of the <see cref="DotnetDumpService"/> class.
/// </summary>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public DotnetDumpService(
ILogger<DotnetDumpService> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
// need to use an extra timeout here because if the process is truly deadlocked. A cooperative dump will hang forever
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
const int TimeoutMinutes = 5;
cts.CancelAfter(TimeSpan.FromMinutes(TimeoutMinutes));
cts.Token.Register(() =>
{
if (!cancellationToken.IsCancellationRequested)
logger.LogError("dotnet-dump timed out after {minutes} minutes!", TimeoutMinutes);
});
var pid = process.Id;
logger.LogDebug("dotnet-dump requested for PID {pid}...", pid);
var client = new DiagnosticsClient(pid);
await client.WriteDumpAsync(
minidump
? DumpType.Normal
: DumpType.Full,
outputFile,
false,
cts.Token);
}
}
}
@@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.System
{
/// <summary>
/// Helper methods for working with the dotnet executable.
/// </summary>
static class DotnetHelper
{
/// <summary>
/// Locate a dotnet executable to use.
/// </summary>
/// <param name="platformIdentifier">The <see cref="IPlatformIdentifier"/> to use.</param>
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a dotnet executable path to use on success, <see langword="null"/> otherwise.</returns>
public static async ValueTask<string?> GetDotnetPath(IPlatformIdentifier platformIdentifier, IIOManager ioManager, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(platformIdentifier);
ArgumentNullException.ThrowIfNull(ioManager);
var dotnetPaths = Common.DotnetHelper.GetPotentialDotnetPaths(platformIdentifier.IsWindows)
.ToList();
var tasks = dotnetPaths
.Select(path => ioManager.FileExists(path, cancellationToken))
.ToList();
await Task.WhenAll(tasks);
var selectedPathIndex = tasks.FindIndex(pathValidTask => pathValidTask.Result);
if (selectedPathIndex == -1)
return null;
var dotnetPath = dotnetPaths[selectedPathIndex];
return dotnetPath;
}
}
}
@@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.System
{
/// <summary>
/// Service for managing the dotnet-dump installation.
/// </summary>
public interface IDotnetDumpService
{
/// <summary>
/// Attempt to dump a given <paramref name="process"/>.
/// </summary>
/// <param name="process">The <see cref="IProcess"/> to dump.</param>
/// <param name="outputFile">The path to the output dump file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask Dump(IProcess process, string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -33,8 +33,9 @@ namespace Tgstation.Server.Host.System
/// Create a dump file of the process.
/// </summary>
/// <param name="outputFile">The full path to the output file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask CreateDump(string outputFile, CancellationToken cancellationToken);
ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Host.System
using System.Collections.Generic;
namespace Tgstation.Server.Host.System
{
/// <summary>
/// For launching <see cref="IProcess"/>'.
@@ -11,6 +13,7 @@
/// <param name="fileName">The full path to the executable file.</param>
/// <param name="workingDirectory">The working directory for the <see cref="IProcess"/>.</param>
/// <param name="arguments">The arguments for the <see cref="IProcess"/>.</param>
/// <param name="environment">A <see cref="IReadOnlyDictionary{TKey, TValue}"/> of environment variables to set.</param>
/// <param name="fileRedirect">File to write process output and error streams to. Requires <paramref name="readStandardHandles"/> to be <see langword="true"/>.</param>
/// <param name="readStandardHandles">If the process output and error streams should be read.</param>
/// <param name="noShellExecute">If shell execute should not be used. Must be set if <paramref name="readStandardHandles"/> is set.</param>
@@ -19,6 +22,7 @@
string fileName,
string workingDirectory,
string arguments,
IReadOnlyDictionary<string, string>? environment = null,
string? fileRedirect = null,
bool readStandardHandles = false,
bool noShellExecute = false);
@@ -32,8 +32,9 @@ namespace Tgstation.Server.Host.System
/// </summary>
/// <param name="process">The <see cref="Process"/> to dump.</param>
/// <param name="outputFile">The full path to the output file.</param>
/// <param name="minidump">If a minidump should be taken as opposed to a full dump.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken);
ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken);
}
}
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.System
=> throw new NotSupportedException();
/// <inheritdoc />
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(process);
ArgumentNullException.ThrowIfNull(outputFile);
@@ -91,7 +91,7 @@ namespace Tgstation.Server.Host.System
await using (var gcoreProc = lazyLoadedProcessExecutor.Value.LaunchProcess(
GCorePath,
Environment.CurrentDirectory,
$"-o {outputFile} {process.Id}",
$"{(!minidump ? "-a " : String.Empty)}-o {outputFile} {process.Id}",
readStandardHandles: true,
noShellExecute: true))
{
@@ -99,7 +99,7 @@ namespace Tgstation.Server.Host.System
exitCode = (await gcoreProc.Lifetime).Value;
output = await gcoreProc.GetCombinedOutput(cancellationToken);
logger.LogDebug("gcore output:{0}{1}", Environment.NewLine, output);
logger.LogDebug("gcore output:{newline}{output}", Environment.NewLine, output);
}
if (exitCode != 0)
+2 -2
View File
@@ -224,13 +224,13 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
public ValueTask CreateDump(string outputFile, bool minidump, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(outputFile);
CheckDisposed();
logger.LogTrace("Dumping PID {pid} to {dumpFilePath}...", Id, outputFile);
return processFeatures.CreateDump(handle, outputFile, cancellationToken);
return processFeatures.CreateDump(handle, outputFile, minidump, cancellationToken);
}
/// <summary>
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
@@ -107,6 +109,7 @@ namespace Tgstation.Server.Host.System
string fileName,
string workingDirectory,
string arguments,
IReadOnlyDictionary<string, string>? environment,
string? fileRedirect,
bool readStandardHandles,
bool noShellExecute)
@@ -115,24 +118,33 @@ namespace Tgstation.Server.Host.System
ArgumentNullException.ThrowIfNull(workingDirectory);
ArgumentNullException.ThrowIfNull(arguments);
var enviromentLogLines = environment == null
? String.Empty
: String.Concat(environment.Select(kvp => $"{Environment.NewLine}\t- {kvp.Key}={kvp.Value}"));
if (noShellExecute)
logger.LogDebug(
"Launching process in {workingDirectory}: {exe} {arguments}",
"Launching process in {workingDirectory}: {exe} {arguments}{environment}",
workingDirectory,
fileName,
arguments);
arguments,
enviromentLogLines);
else
logger.LogDebug(
"Shell launching process in {workingDirectory}: {exe} {arguments}",
"Shell launching process in {workingDirectory}: {exe} {arguments}{environment}",
workingDirectory,
fileName,
arguments);
arguments,
enviromentLogLines);
var handle = new global::System.Diagnostics.Process();
try
{
handle.StartInfo.FileName = fileName;
handle.StartInfo.Arguments = arguments;
if (environment != null)
foreach (var kvp in environment)
handle.StartInfo.Environment.Add(kvp!);
handle.StartInfo.WorkingDirectory = workingDirectory;
handle.StartInfo.UseShellExecute = !noShellExecute;
@@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.System
}
/// <inheritdoc />
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, CancellationToken cancellationToken)
public async ValueTask CreateDump(global::System.Diagnostics.Process process, string outputFile, bool minidump, CancellationToken cancellationToken)
{
try
{
@@ -137,15 +137,19 @@ namespace Tgstation.Server.Host.System
await Task.Factory.StartNew(
() =>
{
var flags = NativeMethods.MiniDumpType.WithHandleData
| NativeMethods.MiniDumpType.WithThreadInfo
| NativeMethods.MiniDumpType.WithUnloadedModules;
if (!minidump)
flags |= NativeMethods.MiniDumpType.WithDataSegs
| NativeMethods.MiniDumpType.WithFullMemory;
if (!NativeMethods.MiniDumpWriteDump(
process.Handle,
(uint)process.Id,
fileStream.SafeFileHandle,
NativeMethods.MiniDumpType.WithDataSegs
| NativeMethods.MiniDumpType.WithFullMemory
| NativeMethods.MiniDumpType.WithHandleData
| NativeMethods.MiniDumpType.WithThreadInfo
| NativeMethods.MiniDumpType.WithUnloadedModules,
flags,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero))
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../build/SrcCommon.props" />
<PropertyGroup>
@@ -69,6 +69,8 @@
<PackageReference Include="Cyberboss.AspNetCore.AsyncInitializer" Version="1.2.0" />
<!-- Usage: IRC interop -->
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.7" />
<!-- Usage: .env file parsing -->
<PackageReference Include="DotEnv.Core" Version="3.0.0" />
<!-- Usage: Text formatter for Elasticsearch logging plugin -->
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.6.1" />
<!-- Usage: GitLab interop -->
@@ -81,6 +83,8 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.1" />
<!-- Usage: Using target JSON serializer for API -->
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.1" />
<!-- Usage: Generating dumps of dotnet engine processes -->
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.505301" />
<!-- Usage: Database ORM -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
<!-- Usage: Automatic migration generation using command line -->
@@ -98,7 +102,7 @@
<!-- Usage: PostgresSQL ORM plugin -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<!-- Usage: GitHub.com interop -->
<PackageReference Include="Octokit" Version="9.1.0" />
<PackageReference Include="Octokit" Version="9.1.2" />
<!-- Usage: MYSQL/MariaDB ORM plugin -->
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
<!-- Usage: Discord interop -->
@@ -124,7 +128,7 @@
<!-- Usage: Temporary resolution to compatibility issues with EFCore 7 and .NET 8 -->
<PackageReference Include="System.Security.Permissions" Version="8.0.0" />
<!-- Usage: .DeleteAsync() support for IQueryable<T>s -->
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.101.1.3" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.101.2.1" />
</ItemGroup>
<ItemGroup>
@@ -22,6 +22,7 @@ General:
Session:
HighPriorityLiveDreamDaemon: false # If DreamDaemon instances should run as higher priority processes
LowPriorityDeploymentProcesses: true # If TGS Deployments should run as lower priority processes
DelayCleaningFailedDeployments: false # If true, deployments that fail will not be immediately cleaned up. They will be cleaned up the next time the instance is onlined
FileLogging:
Directory: # Directory in which log files are stored. Windows default: %PROGRAMDATA%/tgstation-server. Linux default: /var/log/tgstation-server
Disable: true # Disable file logging entirely
@@ -0,0 +1,4 @@
cd /D "%~dp0"
cd ../../Byond/%1
echo # Comment > server.env
echo NOTA=Real Comment>> server.env
@@ -0,0 +1,7 @@
#!/bin/bash
set -e
cd "../../Byond/$1"
echo -e '# This is a comment\nNOTA=Real Comment\n\n\n' > server.env
@@ -90,7 +90,7 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
const string FakePath = "fake";
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(null, null, default).AsTask());
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(null, null, false, default).AsTask());
var byondVersion = new EngineVersion
{
@@ -98,10 +98,10 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
Version = new Version(123, 252345),
};
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(byondVersion, null, default).AsTask());
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(byondVersion, null, false, default).AsTask());
byondVersion.Version = new Version(511, 1385);
await installer.Install(byondVersion, FakePath, default);
await installer.Install(byondVersion, FakePath, false, default);
mockPostWriteHandler.Verify(x => x.HandleWrite(It.IsAny<string>()), Times.Exactly(4));
}
@@ -69,6 +69,7 @@ namespace Tgstation.Server.Host.System.Tests
pathToSignalTestApp,
$"run -c {CurrentConfig} --no-build",
null,
null,
true,
true);
@@ -118,18 +118,24 @@ namespace Tgstation.Server.Tests.Live.Instance
await using var memoryStream2 = new MemoryStream(Encoding.UTF8.GetBytes("bbb"));
await configurationClient.Write(staticFile2, memoryStream2, cancellationToken);
var shellScriptExtension = new PlatformIdentifier().IsWindows ? ".bat" : ".sh";
var scriptName = $"PreCompile-GenerateRandomResource{shellScriptExtension}";
var resourcingScript = new ConfigurationFileRequest
async ValueTask UploadScript(string scriptId)
{
Path = $"/EventScripts/{scriptName}"
};
var shellScriptExtension = new PlatformIdentifier().IsWindows ? ".bat" : ".sh";
var scriptName = $"{scriptId}{shellScriptExtension}";
var resourcingScript = new ConfigurationFileRequest
{
Path = $"/EventScripts/{scriptName}"
};
await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/LongRunning/{scriptName}", false);
await configurationClient.Write(
resourcingScript,
readStream,
cancellationToken);
await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/LongRunning/{scriptName}", false);
await configurationClient.Write(
resourcingScript,
readStream,
cancellationToken);
}
await UploadScript("PreCompile-GenerateRandomResource");
await UploadScript("EngineActiveVersionChange-SetupEnv");
}
return ValueTaskExtensions.WhenAll(
@@ -351,13 +351,11 @@ namespace Tgstation.Server.Tests.Live.Instance
var deleteJob = await deleteJobTask;
// And this freezes DD
await DumpTests(cancellationToken);
// And this freezes DD (also restarts it)
await DumpTests(false, cancellationToken);
await DumpTests(true, cancellationToken);
// Restart to unlock previous BYOND version
var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken);
await WaitForJob(deleteJob, 15, false, null, cancellationToken);
await WaitForJob(restartJob, 15, false, null, cancellationToken);
}
async ValueTask RegressionTest1550(CancellationToken cancellationToken)
@@ -519,14 +517,19 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.AreEqual("sent", topicRequestResult.StringData);
}
async Task DumpTests(CancellationToken cancellationToken)
async Task DumpTests(bool mini, CancellationToken cancellationToken)
{
System.Console.WriteLine("TEST: WATCHDOG DUMP TESTS");
var updated = await instanceClient.DreamDaemon.Update(new DreamDaemonRequest
{
Minidumps = mini,
}, cancellationToken);
Assert.AreEqual(mini, updated.Minidumps);
var dumpJob = await instanceClient.DreamDaemon.CreateDump(cancellationToken);
await WaitForJob(dumpJob, 30, false, null, cancellationToken);
var dumpFiles = Directory.GetFiles(Path.Combine(
instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), "*.dmp");
instanceClient.Metadata.Path, "Diagnostics", "ProcessDumps"), testVersion.Engine == EngineType.OpenDream ? "*.net.dmp" : "*.dmp");
Assert.AreEqual(1, dumpFiles.Length);
File.Delete(dumpFiles.Single());
@@ -813,7 +816,21 @@ namespace Tgstation.Server.Tests.Live.Instance
ourProcessHandler.SuspendProcess();
global::System.Console.WriteLine($"WATCHDOG TEST {instanceClient.Metadata.Id}: FINISH PROCESS SUSPEND FOR HEALTH CHECK DEATH. WAITING FOR LIFETIME {ourProcessHandler.Id}.");
if (testVersion.Engine == EngineType.OpenDream && checkDump)
{
// because dotnet diagnostics relies on the engine process to write its own dump, we actually have to unpause it after the watchdog has decided to kill it
// incredibly cursed, because we don't have the means to accurately tell when that will happen. ESP in CI
return; // CBA rn
/*
await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
ourProcessHandler.ResumeProcess();
global::System.Console.WriteLine($"WATCHDOG TEST {instanceClient.Metadata.Id}: PROCESS RESUMING FOR DOTNET DUMP. WAITING FOR LIFETIME {ourProcessHandler.Id}.");*/
}
await Task.WhenAny(ourProcessHandler.Lifetime, Task.Delay(TimeSpan.FromMinutes(4), cancellationToken));
if (testVersion.Engine == EngineType.OpenDream && checkDump && !ourProcessHandler.Lifetime.IsCompleted)
return;
Assert.IsTrue(ourProcessHandler.Lifetime.IsCompleted);
var timeout = 20;
@@ -1105,6 +1105,7 @@ namespace Tgstation.Server.Tests.Live
repoPath,
args,
null,
null,
true,
true);
@@ -122,6 +122,7 @@ namespace Tgstation.Server.Tests
StartProfiler = false,
LogOutput = true,
MapThreads = 69,
Minidumps = true,
},
DreamMakerSettings = new Host.Models.DreamMakerSettings
{
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Tests
Mock.Of<ILogger<ProcessExecutor>>(),
loggerFactory);
await using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, true, true);
await using var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, null, true, true);
using var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
var exitCode = await process.Lifetime.WaitAsync(cts.Token);
@@ -63,7 +63,7 @@ namespace Tgstation.Server.Tests
File.Delete(tempFile);
try
{
await using (var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, tempFile, true, true))
await using (var process = processExecutor.LaunchProcess("test." + platformIdentifier.ScriptFileExtension, ".", string.Empty, null, tempFile, true, true))
{
using var cts = new CancellationTokenSource();
cts.CancelAfter(3000);
+2 -1
View File
@@ -477,7 +477,7 @@ namespace Tgstation.Server.Tests
if (byondInstaller is WindowsByondInstaller)
typeof(WindowsByondInstaller).GetField("installedDirectX", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(byondInstaller, true);
await byondInstaller.Install(engineVersion, tempPath, default);
await byondInstaller.Install(engineVersion, tempPath, false, default);
var binPath = (string)typeof(ByondInstallerBase).GetField("ByondBinPath", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
var ddNameFunc = installerType.GetMethod("GetDreamDaemonName", BindingFlags.Instance | BindingFlags.NonPublic);
@@ -503,6 +503,7 @@ namespace Tgstation.Server.Tests
Environment.CurrentDirectory,
"fake.dmb -map-threads 3 -close",
null,
null,
true,
true);
+3 -1
View File
@@ -155,6 +155,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LongRunning", "LongRunning", "{EB1DDE8C-CA6F-4BE3-947B-597CA8EABEA5}"
ProjectSection(SolutionItems) = preProject
tests\DMAPI\LongRunning\Config.dm = tests\DMAPI\LongRunning\Config.dm
tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.bat = tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.bat
tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.sh = tests\DMAPI\LongRunning\EngineActiveVersionChange-SetupEnv.sh
tests\DMAPI\LongRunning\long_running_test.dme = tests\DMAPI\LongRunning\long_running_test.dme
tests\DMAPI\LongRunning\long_running_test_copy.dme = tests\DMAPI\LongRunning\long_running_test_copy.dme
tests\DMAPI\LongRunning\long_running_test_rooted.dme = tests\DMAPI\LongRunning\long_running_test_rooted.dme
@@ -171,8 +173,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiFree", "ApiFree", "{7B8F
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicOperation", "BasicOperation", "{F32B9514-AAD9-429D-841A-ED810FC2598C}"
ProjectSection(SolutionItems) = preProject
tests\DMAPI\BasicOperation\Config.dm = tests\DMAPI\BasicOperation\Config.dm
tests\DMAPI\BasicOperation\basic operation_test.dme = tests\DMAPI\BasicOperation\basic operation_test.dme
tests\DMAPI\BasicOperation\Config.dm = tests\DMAPI\BasicOperation\Config.dm
tests\DMAPI\BasicOperation\Test.dm = tests\DMAPI\BasicOperation\Test.dm
EndProjectSection
EndProject