Merge pull request #1451 from tgstation/FixesNSHit [TGSDeploy]

Fix some events getting relayed to DreamDaemon unintentionally
This commit is contained in:
Jordan Dominion
2023-04-10 19:36:02 -04:00
committed by GitHub
36 changed files with 375 additions and 194 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="ControlPanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>5.8.0</TgsCoreVersion>
<TgsCoreVersion>5.9.0</TgsCoreVersion>
<TgsConfigVersion>4.5.0</TgsConfigVersion>
<TgsApiVersion>9.9.0</TgsApiVersion>
<TgsApiLibraryVersion>10.3.0</TgsApiLibraryVersion>
+15 -11
View File
@@ -101,8 +101,7 @@
var/list/response = list()
if(error_message)
response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message
return json_encode(response)
return "{}"
return response
/datum/tgs_api/v5/OnTopic(T)
RequireInitialBridgeResponse()
@@ -127,17 +126,20 @@
if(!isnum(command))
return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] from: [json]!")
var/result = ProcessTopicCommand(command, topic_parameters)
if(!length(result))
return "{}" // quirk of json_encode is an empty list returns "[]"
return json_encode(result)
/datum/tgs_api/v5/proc/ProcessTopicCommand(command, list/topic_parameters)
switch(command)
if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND)
intercepted_message_queue = list()
var/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
var/list/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND])
if(!result)
result = TopicResponse("Error running chat command!")
//TODO: make this not need the decode/encode.
if (length(intercepted_message_queue))
var/list/result_array = json_decode(result)
result_array[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
result = json_encode(result_array)
result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return result
if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION)
@@ -161,10 +163,10 @@
if(event_handler != null)
event_handler.HandleEvent(arglist(event_call))
var/list/response = list()
var/list/response = TopicResponse()
response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue
intercepted_message_queue = null
return json_encode(response)
return response
if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT)
var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT]
if (!isnum(new_port) || !(new_port > 0))
@@ -236,7 +238,9 @@
version = new_version
return json_encode(list(DMAPI5_RESPONSE_ERROR_MESSAGE = error_message, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
return reattach_response
return TopicResponse("Unknown command: [command]")
+2 -2
View File
@@ -36,10 +36,10 @@
var/datum/tgs_message_content/response = sc.Run(u, params)
response = UpgradeDeprecatedCommandResponse(response, command)
var/list/topic_response = list()
var/list/topic_response = TopicResponse()
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = response?.text
topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE] = response?._interop_serialize()
return json_encode(topic_response)
return topic_response
return TopicResponse("Unknown custom chat command: [command]!")
// Common proc b/c it's used by the V3/V4 APIs
@@ -46,12 +46,16 @@
</Target>
<ItemGroup>
<!-- Usage: HTTP constants reference -->
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<!-- Usage: Primary JSON library -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: Data model annotating -->
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/Version.props" />
<PropertyGroup>
@@ -32,6 +32,7 @@
</PropertyGroup>
<ItemGroup>
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -24,8 +24,9 @@
</PropertyGroup>
<ItemGroup>
<!-- DO NOT UPDATE UNLESS YOU WANT TO DEAL WITH THE LOGGERFACTORY REFACTOR -->
<!-- Usage: Console logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
@@ -23,17 +23,18 @@
</PropertyGroup>
<ItemGroup>
<!-- Usage: Command line argument support -->
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: Windows event log logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="6.0.0" />
<!-- Usage: Console logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: OS identification -->
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
</ItemGroup>
@@ -24,12 +24,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- DO NOT UPDATE UNLESS YOU WANT TO DEAL WITH THE LOGGERFACTORY REFACTOR -->
<!-- Usage: Logging abstractions -->
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -150,15 +150,13 @@ namespace Tgstation.Server.Host.Components.Byond
/// <inheritdoc />
public async Task<IByondExecutableLock> UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
{
var versionToUse = requiredVersion ?? ActiveVersion;
if (versionToUse == null)
throw new JobException(ErrorCode.ByondNoVersionsInstalled);
var versionToUse = requiredVersion ?? ActiveVersion ?? throw new JobException(ErrorCode.ByondNoVersionsInstalled);
await InstallVersion(versionToUse, null, cancellationToken);
var versionKey = VersionKey(versionToUse, true);
var binPathForVersion = ioManager.ConcatPath(versionKey, BinPath);
logger.LogTrace("Creating ByondExecutableLock lock for version {0}", versionToUse);
logger.LogTrace("Creating ByondExecutableLock lock for version {versionToUse}", versionToUse);
return new ByondExecutableLock(
ioManager,
semaphore,
@@ -203,7 +201,7 @@ namespace Tgstation.Server.Host.Components.Byond
ioManager.ConcatPath(
localCfgDirectory,
TrustedDmbFileName);
logger.LogTrace("Deleting trusted .dmbs file {0}", trustedFilePath);
logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
await ioManager.DeleteFile(
trustedFilePath,
cancellationToken);
@@ -219,7 +217,7 @@ namespace Tgstation.Server.Host.Components.Byond
var versionFile = ioManager.ConcatPath(path, VersionFileName);
if (!await ioManager.FileExists(versionFile, cancellationToken))
{
logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
logger.LogInformation("Cleaning unparsable version path: {versionPath}", ioManager.ResolvePath(path));
await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
return;
}
@@ -232,7 +230,7 @@ namespace Tgstation.Server.Host.Components.Byond
lock (installedVersions)
if (!installedVersions.ContainsKey(key))
{
logger.LogDebug("Adding detected BYOND version {0}...", key);
logger.LogDebug("Adding detected BYOND version {versionKey}...", key);
installedVersions.Add(key, Task.CompletedTask);
installedVersionPaths.Add(ioManager.ResolvePath(key), version);
return;
@@ -276,7 +274,7 @@ namespace Tgstation.Server.Host.Components.Byond
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task<string> InstallVersion(Version version, Stream customVersionStream, CancellationToken cancellationToken)
{
var ourTcs = new TaskCompletionSource<object>();
var ourTcs = new TaskCompletionSource();
Task inProgressTask;
string versionKey;
bool installed;
@@ -308,11 +306,11 @@ namespace Tgstation.Server.Host.Components.Byond
}
if (customVersionStream != null)
logger.LogInformation("Installing custom BYOND version as {0}...", versionKey);
logger.LogInformation("Installing custom BYOND version as {versionKey}...", versionKey);
else if (version.Build > 0)
throw new JobException(ErrorCode.ByondNonExistentCustomVersion);
else
logger.LogDebug("Requested BYOND version {0} not currently installed. Doing so now...", versionKey);
logger.LogDebug("Requested BYOND version {versionKey} not currently installed. Doing so now...", versionKey);
// okay up to us to install it then
try
@@ -342,7 +340,7 @@ namespace Tgstation.Server.Host.Components.Byond
using (downloadedStream)
{
await directoryCleanupTask;
logger.LogTrace("Extracting downloaded BYOND zip to {0}...", extractPath);
logger.LogTrace("Extracting downloaded BYOND zip to {extractPath}...", extractPath);
await ioManager.ZipToDirectory(extractPath, versionZipStream, cancellationToken);
}
@@ -370,11 +368,11 @@ namespace Tgstation.Server.Host.Components.Byond
throw;
}
ourTcs.SetResult(null);
ourTcs.SetResult();
}
catch (Exception e)
{
if (!(e is OperationCanceledException))
if (e is not OperationCanceledException)
await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List<string> { e.Message }, cancellationToken);
lock (installedVersions)
installedVersions.Remove(versionKey);
@@ -109,9 +109,9 @@ namespace Tgstation.Server.Host.Components.Chat
Task messageSendTask;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when <see cref="ChatBotSettings"/>s change.
/// The <see cref="TaskCompletionSource"/> that completes when <see cref="ChatBotSettings"/>s change.
/// </summary>
TaskCompletionSource<object> connectionsUpdated;
TaskCompletionSource connectionsUpdated;
/// <summary>
/// Used for remapping <see cref="ChannelRepresentation.RealId"/>s.
@@ -157,7 +157,7 @@ namespace Tgstation.Server.Host.Components.Chat
mappedChannels = new Dictionary<ulong, ChannelMapping>();
trackingContexts = new List<IChatTrackingContext>();
handlerCts = new CancellationTokenSource();
connectionsUpdated = new TaskCompletionSource<object>();
connectionsUpdated = new TaskCompletionSource();
messageSendTask = Task.CompletedTask;
channelIdCounter = 1;
@@ -293,8 +293,8 @@ namespace Tgstation.Server.Host.Components.Chat
{
// same thread shennanigans
var oldOne = connectionsUpdated;
connectionsUpdated = new TaskCompletionSource<object>();
oldOne.SetResult(null);
connectionsUpdated = new TaskCompletionSource();
oldOne.SetResult();
}
var reconnectionUpdateTask = provider?.SetReconnectInterval(
@@ -483,6 +483,8 @@ namespace Tgstation.Server.Host.Components.Chat
logger.LogTrace("DeleteConnection {connectionId}", connectionId);
var provider = await RemoveProviderChannels(connectionId, true, cancellationToken);
if (provider != null)
{
var startTime = DateTimeOffset.UtcNow;
try
{
await provider.Disconnect(cancellationToken);
@@ -490,7 +492,11 @@ namespace Tgstation.Server.Host.Components.Chat
finally
{
await provider.DisposeAsync();
var duration = DateTimeOffset.UtcNow - startTime;
if (duration.TotalSeconds > 3)
logger.LogWarning("Disconnecting a {providerType} took {totalSeconds}s!", provider.GetType().Name, duration.TotalSeconds);
}
}
else
logger.LogTrace("DeleteConnection: ID {connectionId} doesn't exist!", connectionId);
}
@@ -88,9 +88,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
CancellationTokenSource gatewayCts;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> for the initial gateway connection event.
/// The <see cref="TaskCompletionSource"/> for the initial gateway connection event.
/// </summary>
TaskCompletionSource<object> gatewayReadyTcs;
TaskCompletionSource gatewayReadyTcs;
/// <summary>
/// The <see cref="Task"/> representing the lifetime of the client.
@@ -145,14 +145,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
new EmbedField(
"Local Commit",
localCommitPushed && gitHub
? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
: revisionInformation.CommitSha.Substring(0, 7),
? $"[{revisionInformation.CommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
: revisionInformation.CommitSha[..7],
true),
new EmbedField(
"Branch Commit",
gitHub
? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
: revisionInformation.OriginCommitSha.Substring(0, 7),
? $"[{revisionInformation.OriginCommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
: revisionInformation.OriginCommitSha[..7],
true),
};
@@ -160,7 +160,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
.Select(x => x.TestMerge)
.Select(x => new EmbedField(
$"#{x.Number}",
$"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
$"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha[..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}",
false)));
return fields;
@@ -253,7 +253,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!result.IsSuccess)
Logger.LogWarning(
"Failed to send to channel {0}: {1}",
"Failed to send to channel {channelId}: {error}",
channelId,
result.Error);
}
@@ -267,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!currentGuildsResponse.IsSuccess)
{
Logger.LogWarning(
"Error retrieving current discord guilds: {0}",
"Error retrieving current discord guilds: {error}",
currentGuildsResponse.Error.Message);
return;
}
@@ -559,7 +559,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
throw new ArgumentNullException(nameof(readyEvent));
Logger.LogTrace("Gatway ready. Version: {version}", readyEvent.Version);
gatewayReadyTcs?.TrySetResult(null);
gatewayReadyTcs?.TrySetResult();
return Task.FromResult(Result.FromSuccess());
}
@@ -580,7 +580,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var gatewayClient = serviceProvider.GetRequiredService<DiscordGatewayClient>();
Task<Result> localGatewayTask;
gatewayReadyTcs = new TaskCompletionSource<object>();
gatewayReadyTcs = new TaskCompletionSource();
using var gatewayConnectionAbortRegistration = cancellationToken.Register(() => gatewayReadyTcs.TrySetCanceled());
gatewayCancellationToken.Register(() => Logger.LogTrace("Stopping gateway client..."));
@@ -758,10 +758,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (embed == null)
return default;
List<string> embedErrors = new List<string>();
var embedErrors = new List<string>();
Optional<Color> colour = default;
if (embed.Colour != null)
if (Int32.TryParse(embed.Colour.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
if (Int32.TryParse(embed.Colour[1..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var argb))
colour = Color.FromArgb(argb);
else
embedErrors.Add(
@@ -168,8 +168,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
// IRC doesn't allow newlines
// Explicitly ignore embeds
var messageText = message.Text;
if (messageText == null)
messageText = $"Embed Only: {JsonConvert.SerializeObject(message.Embed)}";
messageText ??= $"Embed Only: {JsonConvert.SerializeObject(message.Embed)}";
messageText = String.Concat(
messageText
@@ -380,7 +379,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Logger.LogTrace("Processing initial messages...");
await NonBlockingListen(cancellationToken);
var nickCheckCompleteTcs = new TaskCompletionSource<object>();
var nickCheckCompleteTcs = new TaskCompletionSource();
using (cancellationToken.Register(() => nickCheckCompleteTcs.TrySetCanceled()))
{
listenTask = Task.Factory.StartNew(
@@ -399,7 +398,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
client.RfcNick(nickname);
}
nickCheckCompleteTcs.TrySetResult(null);
nickCheckCompleteTcs.TrySetResult();
Logger.LogTrace("Starting blocking listen...");
try
@@ -40,9 +40,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
readonly Queue<Message> messageQueue;
/// <summary>
/// The backing <see cref="TaskCompletionSource{TResult}"/> for <see cref="InitialConnectionJob"/>.
/// The backing <see cref="TaskCompletionSource"/> for <see cref="InitialConnectionJob"/>.
/// </summary>
readonly TaskCompletionSource<object> initialConnectionTcs;
readonly TaskCompletionSource initialConnectionTcs;
/// <summary>
/// Used for synchronizing access to <see cref="reconnectCts"/> and <see cref="reconnectTask"/>.
@@ -50,9 +50,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
readonly object reconnectTaskLock;
/// <summary>
/// <see cref="TaskCompletionSource{TResult}"/> that completes while <see cref="messageQueue"/> isn't empty.
/// <see cref="TaskCompletionSource"/> that completes while <see cref="messageQueue"/> isn't empty.
/// </summary>
TaskCompletionSource<object> nextMessage;
TaskCompletionSource nextMessage;
/// <summary>
/// The auto reconnect <see cref="Task"/>.
@@ -77,8 +77,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
ChatBot = chatBot ?? throw new ArgumentNullException(nameof(chatBot));
messageQueue = new Queue<Message>();
nextMessage = new TaskCompletionSource<object>();
initialConnectionTcs = new TaskCompletionSource<object>();
nextMessage = new TaskCompletionSource();
initialConnectionTcs = new TaskCompletionSource();
reconnectTaskLock = new object();
logger.LogTrace("Created.");
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
/// <inheritdoc />
public void InitialMappingComplete() => initialConnectionTcs.TrySetResult(null);
public void InitialMappingComplete() => initialConnectionTcs.TrySetResult();
/// <inheritdoc />
public async Task<IReadOnlyCollection<Tuple<ChatChannel, ChannelRepresentation>>> MapChannels(IEnumerable<ChatChannel> channels, CancellationToken cancellationToken)
@@ -126,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
catch
{
initialConnectionTcs.TrySetResult(null);
initialConnectionTcs.TrySetResult();
throw;
}
}
@@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
{
var result = messageQueue.Dequeue();
if (messageQueue.Count == 0)
nextMessage = new TaskCompletionSource<object>();
nextMessage = new TaskCompletionSource();
return result;
}
}
@@ -215,7 +215,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
lock (messageQueue)
{
messageQueue.Enqueue(message);
nextMessage.TrySetResult(null);
nextMessage.TrySetResult();
}
}
@@ -290,7 +290,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
catch
{
initialConnectionTcs.TrySetResult(null);
initialConnectionTcs.TrySetResult();
throw;
}
},
@@ -80,9 +80,9 @@ namespace Tgstation.Server.Host.Components.Deployment
Task cleanupTask;
/// <summary>
/// <see cref="TaskCompletionSource{TResult}"/> resulting in the latest <see cref="DmbProvider"/> yet to exist.
/// <see cref="TaskCompletionSource"/> resulting in the latest <see cref="DmbProvider"/> yet to exist.
/// </summary>
TaskCompletionSource<object> newerDmbTcs;
TaskCompletionSource newerDmbTcs;
/// <summary>
/// The latest <see cref="DmbProvider"/>.
@@ -119,7 +119,7 @@ namespace Tgstation.Server.Host.Components.Deployment
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
cleanupTask = Task.CompletedTask;
newerDmbTcs = new TaskCompletionSource<object>();
newerDmbTcs = new TaskCompletionSource();
cleanupCts = new CancellationTokenSource();
jobLockCounts = new Dictionary<long, int>();
}
@@ -156,8 +156,8 @@ namespace Tgstation.Server.Host.Components.Deployment
// Oh god dammit
var temp = newerDmbTcs;
newerDmbTcs = new TaskCompletionSource<object>();
temp.SetResult(nextDmbProvider);
newerDmbTcs = new TaskCompletionSource();
temp.SetResult();
}
}
@@ -3,6 +3,7 @@
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Host.Components.Interop.Bridge;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Components
{
@@ -18,5 +19,12 @@ namespace Tgstation.Server.Host.Components
/// <param name="metadata">The <see cref="Models.Instance"/>.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IInstance"/>.</returns>
Task<IInstance> CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata);
/// <summary>
/// Create an <see cref="IIOManager"/> that resolves to the "Game" directory of the <see cref="Models.Instance"/> defined by <paramref name="metadata"/>.
/// </summary>
/// <param name="metadata">The <see cref="Models.Instance"/>.</param>
/// <returns>The <see cref="IIOManager"/> for the instance's "Game" directory.</returns>
IIOManager CreateGameIOManager(Models.Instance metadata);
}
}
@@ -35,9 +35,9 @@ namespace Tgstation.Server.Host.Components
readonly object referenceCountLock;
/// <summary>
/// Backing <see cref="TaskCompletionSource{TResult}"/> for <see cref="OnZeroReferences"/>.
/// Backing <see cref="TaskCompletionSource"/> for <see cref="OnZeroReferences"/>.
/// </summary>
TaskCompletionSource<object> onZeroReferencesTcs;
TaskCompletionSource onZeroReferencesTcs;
/// <summary>
/// Count of active <see cref="IInstanceReference"/>s.
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Components
lock (referenceCountLock)
{
if (referenceCount++ == 0)
onZeroReferencesTcs = new TaskCompletionSource<object>();
onZeroReferencesTcs = new TaskCompletionSource();
try
{
@@ -72,7 +72,7 @@ namespace Tgstation.Server.Host.Components
{
lock (referenceCountLock)
if (--referenceCount == 0)
onZeroReferencesTcs.SetResult(null);
onZeroReferencesTcs.SetResult();
});
}
catch
@@ -144,6 +144,13 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly SessionConfiguration sessionConfiguration;
/// <summary>
/// Create the <see cref="IIOManager"/> pointing to the "Game" directory of a given <paramref name="instanceIOManager"/>.
/// </summary>
/// <param name="instanceIOManager">The instance's <see cref="IIOManager"/>.</param>
/// <returns>The <see cref="IIOManager"/> for the instance's "Game" directory.</returns>
static IIOManager CreateGameIOManager(IIOManager instanceIOManager) => new ResolvingIOManager(instanceIOManager, "Game");
#pragma warning disable CA1502 // TODO: Decomplexify
/// <summary>
/// Initializes a new instance of the <see cref="InstanceFactory"/> class.
@@ -222,17 +229,32 @@ namespace Tgstation.Server.Host.Components
}
#pragma warning restore CA1502
/// <inheritdoc />
public IIOManager CreateGameIOManager(Models.Instance metadata)
{
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
var instanceIoManager = CreateInstanceIOManager(metadata);
return CreateGameIOManager(instanceIoManager);
}
/// <inheritdoc />
#pragma warning disable CA1506 // TODO: Decomplexify
public async Task<IInstance> CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
{
if (bridgeRegistrar == null)
throw new ArgumentNullException(nameof(bridgeRegistrar));
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
// Create the ioManager for the instance
var instanceIoManager = new ResolvingIOManager(ioManager, metadata.Path);
var instanceIoManager = CreateInstanceIOManager(metadata);
// various other ioManagers
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game");
var gameIoManager = CreateGameIOManager(instanceIoManager);
var diagnosticsIOManager = new ResolvingIOManager(instanceIoManager, "Diagnostics");
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
@@ -305,7 +327,7 @@ namespace Tgstation.Server.Host.Components
sessionControllerFactory,
gameIoManager,
diagnosticsIOManager,
eventConsumer,
configuration, // watchdog doesn't need itself as an event consumer
remoteDeploymentManagerFactory,
metadata,
metadata.DreamDaemonSettings);
@@ -386,5 +408,12 @@ namespace Tgstation.Server.Host.Components
/// Test that the <see cref="repositoryFactory"/> is functional.
/// </summary>
void CheckSystemCompatibility() => repositoryFactory.CreateInMemory();
/// <summary>
/// Create the <see cref="IIOManager"/> for a given set of instance <paramref name="metadata"/>.
/// </summary>
/// <param name="metadata">The <see cref="Models.Instance"/>.</param>
/// <returns>The <see cref="IIOManager"/> for the <paramref name="metadata"/>.</returns>
IIOManager CreateInstanceIOManager(Models.Instance metadata) => new ResolvingIOManager(ioManager, metadata.Path);
}
}
@@ -117,9 +117,9 @@ namespace Tgstation.Server.Host.Components
readonly SwarmConfiguration swarmConfiguration;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> for <see cref="Ready"/>.
/// The <see cref="TaskCompletionSource"/> for <see cref="Ready"/>.
/// </summary>
readonly TaskCompletionSource<object> readyTcs;
readonly TaskCompletionSource readyTcs;
/// <summary>
/// If the <see cref="InstanceManager"/> has been <see cref="DisposeAsync"/>'d.
@@ -173,7 +173,7 @@ namespace Tgstation.Server.Host.Components
instances = new Dictionary<long, InstanceContainer>();
bridgeHandlers = new Dictionary<string, IBridgeHandler>();
readyTcs = new TaskCompletionSource<object>();
readyTcs = new TaskCompletionSource();
instanceStateChangeSemaphore = new SemaphoreSlim(1);
}
@@ -218,6 +218,8 @@ namespace Tgstation.Server.Host.Components
{
if (oldPath == null)
throw new ArgumentNullException(nameof(oldPath));
using var lockContext = await SemaphoreSlimContext.Lock(instanceStateChangeSemaphore, cancellationToken);
using var instanceReferenceCheck = GetInstanceReference(instance);
if (instanceReferenceCheck != null)
throw new InvalidOperationException("Cannot move an online instance!");
@@ -225,6 +227,10 @@ namespace Tgstation.Server.Host.Components
try
{
await ioManager.MoveDirectory(oldPath, newPath, cancellationToken);
// Delete the Game directory to clear out broken symlinks
var instanceGameIOManager = instanceFactory.CreateGameIOManager(instance);
await instanceGameIOManager.DeleteDirectory(".", cancellationToken);
}
catch (Exception ex)
{
@@ -307,23 +313,21 @@ namespace Tgstation.Server.Host.Components
// we are the one responsible for cancelling his jobs
var tasks = new List<Task>();
await databaseContextFactory.UseContext(async db =>
{
var jobs = db
.Jobs
.AsQueryable()
.Where(x => x.Instance.Id == metadata.Id)
.Select(x => new Models.Job
{
Id = x.Id,
});
await jobs.ForEachAsync(
job =>
await databaseContextFactory.UseContext(
async db =>
{
lock (tasks)
var jobs = await db
.Jobs
.AsQueryable()
.Where(x => x.Instance.Id == metadata.Id && !x.StoppedAt.HasValue)
.Select(x => new Models.Job
{
Id = x.Id,
})
.ToListAsync(cancellationToken);
foreach (var job in jobs)
tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
}, cancellationToken);
});
});
await Task.WhenAll(tasks);
@@ -430,7 +434,7 @@ namespace Tgstation.Server.Host.Components
jobManager.Activate();
logger.LogInformation("Server ready!");
readyTcs.SetResult(null);
readyTcs.SetResult();
}
catch (OperationCanceledException ex)
{
@@ -76,9 +76,9 @@ namespace Tgstation.Server.Host.Components.Session
public ReattachInformation ReattachInformation { get; }
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when DD makes it's first bridge request.
/// The <see cref="TaskCompletionSource"/> that completes when DD makes it's first bridge request.
/// </summary>
readonly TaskCompletionSource<object> initialBridgeRequestTcs;
readonly TaskCompletionSource initialBridgeRequestTcs;
/// <summary>
/// The <see cref="Instance"/> metadata.
@@ -141,14 +141,14 @@ namespace Tgstation.Server.Host.Components.Session
ushort? nextPort;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when DD tells us about a reboot.
/// The <see cref="TaskCompletionSource"/> that completes when DD tells us about a reboot.
/// </summary>
TaskCompletionSource<object> rebootTcs;
TaskCompletionSource rebootTcs;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes when DD tells us it's primed.
/// The <see cref="TaskCompletionSource"/> that completes when DD tells us it's primed.
/// </summary>
TaskCompletionSource<object> primeTcs;
TaskCompletionSource primeTcs;
/// <summary>
/// If we know DreamDaemon currently has it's port closed.
@@ -219,9 +219,9 @@ namespace Tgstation.Server.Host.Components.Session
apiValidationStatus = ApiValidationStatus.NeverValidated;
released = false;
rebootTcs = new TaskCompletionSource<object>();
primeTcs = new TaskCompletionSource<object>();
initialBridgeRequestTcs = new TaskCompletionSource<object>();
rebootTcs = new TaskCompletionSource();
primeTcs = new TaskCompletionSource();
initialBridgeRequestTcs = new TaskCompletionSource();
reattachTopicCts = new CancellationTokenSource();
synchronizationLock = new object();
@@ -298,7 +298,7 @@ namespace Tgstation.Server.Host.Components.Session
using (LogContext.PushProperty("Instance", metadata.Id))
{
logger.LogTrace("Handling bridge request...");
initialBridgeRequestTcs.TrySetResult(null);
initialBridgeRequestTcs.TrySetResult();
var response = new BridgeResponse();
switch (parameters.CommandType)
@@ -333,8 +333,8 @@ namespace Tgstation.Server.Host.Components.Session
break;
case BridgeCommandType.Prime:
var oldPrimeTcs = primeTcs;
primeTcs = new TaskCompletionSource<object>();
oldPrimeTcs.SetResult(null);
primeTcs = new TaskCompletionSource();
oldPrimeTcs.SetResult();
break;
case BridgeCommandType.Kill:
logger.LogInformation("Bridge requested process termination!");
@@ -425,8 +425,8 @@ namespace Tgstation.Server.Host.Components.Session
}
var oldRebootTcs = rebootTcs;
rebootTcs = new TaskCompletionSource<object>();
oldRebootTcs.SetResult(null);
rebootTcs = new TaskCompletionSource();
oldRebootTcs.SetResult();
break;
case null:
response.ErrorMessage = "Missing commandType!";
@@ -57,9 +57,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
public abstract RebootState? RebootState { get; }
/// <summary>
/// <see cref="TaskCompletionSource{TResult}"/> that completes when <see cref="ActiveLaunchParameters"/> are changed and we are running.
/// <see cref="TaskCompletionSource"/> that completes when <see cref="ActiveLaunchParameters"/> are changed and we are running.
/// </summary>
protected TaskCompletionSource<object> ActiveParametersUpdated { get; set; }
protected TaskCompletionSource ActiveParametersUpdated { get; set; }
/// <summary>
/// The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.
@@ -220,7 +220,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
ActiveLaunchParameters = initialLaunchParameters;
releaseServers = false;
ActiveParametersUpdated = new TaskCompletionSource<object>();
ActiveParametersUpdated = new TaskCompletionSource();
restartRegistration = serverControl.RegisterForRestart(this);
try
@@ -261,8 +261,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
if (match || Status == WatchdogStatus.Offline)
return;
ActiveParametersUpdated.TrySetResult(null); // queue an update
ActiveParametersUpdated = new TaskCompletionSource<object>();
ActiveParametersUpdated.TrySetResult(); // queue an update
ActiveParametersUpdated = new TaskCompletionSource();
}
}
@@ -872,7 +872,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
cancellationToken);
// cancel waiting if requested
var cancelTcs = new TaskCompletionSource<object>();
var cancelTcs = new TaskCompletionSource();
var toWaitOn = Task.WhenAny(
activeServerLifetime,
activeServerReboot,
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Cyberboss.AspNetCore.AsyncInitializer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -420,9 +419,7 @@ namespace Tgstation.Server.Host.Core
// 503 requests made while the application is starting
applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
{
var tcs = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => tcs.SetCanceled()))
await Task.WhenAny(tcs.Task, instanceManager.Ready);
await instanceManager.Ready.WithToken(cancellationToken);
});
// suppress OperationCancelledExceptions, they are just aborted HTTP requests
@@ -42,10 +42,8 @@ namespace Tgstation.Server.Host.Extensions
const string SectionFieldName = nameof(GeneralConfiguration.Section);
var configType = typeof(TConfig);
var sectionField = configType.GetField(SectionFieldName);
if (sectionField == null)
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "{0} has no {1} field!", configType, SectionFieldName));
var sectionField = configType.GetField(SectionFieldName) ?? throw new InvalidOperationException(
String.Format(CultureInfo.InvariantCulture, "{0} has no {1} field!", configType, SectionFieldName));
var stringType = typeof(string);
if (sectionField.FieldType != stringType)
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "{0} has invalid {1} field type, must be {2}!", configType, SectionFieldName, stringType));
@@ -9,6 +9,11 @@ namespace Tgstation.Server.Host.Extensions
/// </summary>
static class TaskExtensions
{
/// <summary>
/// A <see cref="TaskCompletionSource"/> that never completes.
/// </summary>
static readonly TaskCompletionSource InfiniteTaskCompletionSource = new ();
/// <summary>
/// Create a <see cref="Task"/> that can be awaited while respecting a given <paramref name="cancellationToken"/>.
/// </summary>
@@ -41,7 +46,7 @@ namespace Tgstation.Server.Host.Extensions
if (task == null)
throw new ArgumentNullException(nameof(task));
var cancelTcs = new TaskCompletionSource<object>();
var cancelTcs = new TaskCompletionSource();
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
await Task.WhenAny(task, cancelTcs.Task);
cancellationToken.ThrowIfCancellationRequested();
@@ -53,6 +58,6 @@ namespace Tgstation.Server.Host.Extensions
/// Creates a <see cref="Task"/> that never completes.
/// </summary>
/// <returns>A never ending <see cref="Task"/>.</returns>
public static Task InfiniteTask() => new TaskCompletionSource<object>().Task;
public static Task InfiniteTask() => InfiniteTaskCompletionSource.Task;
}
}
+4 -4
View File
@@ -44,9 +44,9 @@ namespace Tgstation.Server.Host.Jobs
readonly Dictionary<long, JobHandler> jobs;
/// <summary>
/// <see cref="TaskCompletionSource{TResult}"/> to delay starting jobs until the server is ready.
/// <see cref="TaskCompletionSource"/> to delay starting jobs until the server is ready.
/// </summary>
readonly TaskCompletionSource<object> activationTcs;
readonly TaskCompletionSource activationTcs;
/// <summary>
/// <see langword="lock"/> <see cref="object"/> for various operations.
@@ -76,7 +76,7 @@ namespace Tgstation.Server.Host.Jobs
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
jobs = new Dictionary<long, JobHandler>();
activationTcs = new TaskCompletionSource<object>();
activationTcs = new TaskCompletionSource();
synchronizationLock = new object();
addCancelLock = new object();
}
@@ -268,7 +268,7 @@ namespace Tgstation.Server.Host.Jobs
public void Activate()
{
logger.LogTrace("Activating job manager...");
activationTcs.SetResult(null);
activationTcs.SetResult();
}
/// <summary>
+28 -14
View File
@@ -34,6 +34,11 @@ namespace Tgstation.Server.Host
updatePath != null;
#endif
/// <summary>
/// The <see cref="IHost"/> of the running server.
/// </summary>
internal IHost Host { get; private set; }
/// <summary>
/// The <see cref="IHostBuilder"/> for the <see cref="Server"/>.
/// </summary>
@@ -119,26 +124,35 @@ namespace Tgstation.Server.Host
fsWatcher.EnableRaisingEvents = true;
}
using var host = hostBuilder.Build();
try
{
swarmService = host.Services.GetRequiredService<ISwarmService>();
logger = host.Services.GetRequiredService<ILogger<Server>>();
using (cancellationToken.Register(() => logger.LogInformation("Server termination requested!")))
using (Host = hostBuilder.Build())
{
var generalConfigurationOptions = host.Services.GetRequiredService<IOptions<GeneralConfiguration>>();
generalConfiguration = generalConfigurationOptions.Value;
await host.RunAsync(cancellationTokenSource.Token);
try
{
swarmService = Host.Services.GetRequiredService<ISwarmService>();
logger = Host.Services.GetRequiredService<ILogger<Server>>();
using (cancellationToken.Register(() => logger.LogInformation("Server termination requested!")))
{
var generalConfigurationOptions = Host.Services.GetRequiredService<IOptions<GeneralConfiguration>>();
generalConfiguration = generalConfigurationOptions.Value;
await Host.RunAsync(cancellationTokenSource.Token);
}
}
catch (OperationCanceledException ex)
{
logger?.LogDebug(ex, "Server run cancelled!");
}
catch (Exception ex)
{
CheckExceptionPropagation(ex);
throw;
}
}
}
catch (OperationCanceledException ex)
finally
{
logger?.LogDebug(ex, "Server run cancelled!");
}
catch (Exception ex)
{
CheckExceptionPropagation(ex);
throw;
Host = null;
}
}
@@ -80,9 +80,9 @@ namespace Tgstation.Server.Host.Setup
readonly GeneralConfiguration generalConfiguration;
/// <summary>
/// A <see cref="TaskCompletionSource{TResult}"/> that will complete when the <see cref="IConfiguration"/> is reloaded.
/// A <see cref="TaskCompletionSource"/> that will complete when the <see cref="IConfiguration"/> is reloaded.
/// </summary>
TaskCompletionSource<object> reloadTcs;
TaskCompletionSource reloadTcs;
/// <summary>
/// Initializes a new instance of the <see cref="SetupWizard"/> class.
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Setup
configuration
.GetReloadToken()
.RegisterChangeCallback(
state => reloadTcs?.TrySetResult(null),
state => reloadTcs?.TrySetResult(),
null);
}
@@ -942,7 +942,7 @@ namespace Tgstation.Server.Host.Setup
var configBytes = Encoding.UTF8.GetBytes(serializedYaml);
reloadTcs = new TaskCompletionSource<object>();
reloadTcs = new TaskCompletionSource();
try
{
+10 -10
View File
@@ -51,7 +51,7 @@ namespace Tgstation.Server.Host.Swarm
/// <summary>
/// See <see cref="JsonSerializerSettings"/> for the swarm system.
/// </summary>
static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
static readonly JsonSerializerSettings SerializerSettings = new ()
{
ContractResolver = new DefaultContractResolver
{
@@ -142,9 +142,9 @@ namespace Tgstation.Server.Host.Swarm
readonly bool swarmController;
/// <summary>
/// A <see cref="TaskCompletionSource{TResult}"/> that is used to force a health check.
/// A <see cref="TaskCompletionSource"/> that is used to force a health check.
/// </summary>
TaskCompletionSource<object> forceHealthCheckTcs;
TaskCompletionSource forceHealthCheckTcs;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that is used to proceed with committing an update.
@@ -233,7 +233,7 @@ namespace Tgstation.Server.Host.Swarm
if (SwarmMode)
{
serverHealthCheckCancellationTokenSource = new CancellationTokenSource();
forceHealthCheckTcs = new TaskCompletionSource<object>();
forceHealthCheckTcs = new TaskCompletionSource();
if (swarmController)
registrationIds = new Dictionary<string, Guid>();
@@ -917,7 +917,7 @@ namespace Tgstation.Server.Host.Swarm
response.EnsureSuccessStatusCode();
return;
}
catch (Exception ex) when (!(ex is OperationCanceledException))
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogWarning(
ex,
@@ -963,8 +963,8 @@ namespace Tgstation.Server.Host.Swarm
bool TriggerHealthCheck()
{
var currentTcs = forceHealthCheckTcs;
forceHealthCheckTcs = new TaskCompletionSource<object>();
return currentTcs.TrySetResult(null);
forceHealthCheckTcs = new TaskCompletionSource();
return currentTcs.TrySetResult();
}
/// <summary>
@@ -1068,7 +1068,7 @@ namespace Tgstation.Server.Host.Swarm
logger.LogWarning("Error registering with swarm controller: HTTP {0}", response.StatusCode);
try
{
var responseData = await response.Content.ReadAsStringAsync();
var responseData = await response.Content.ReadAsStringAsync(cancellationToken);
if (!String.IsNullOrWhiteSpace(responseData))
logger.LogDebug("Response:{0}{1}", Environment.NewLine, responseData);
}
@@ -1114,7 +1114,7 @@ namespace Tgstation.Server.Host.Swarm
using var response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
}
catch (Exception ex) when (!(ex is OperationCanceledException))
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogWarning(ex, "Error during swarm server list update for node '{0}'! Unregistering...", swarmServer.Identifier);
@@ -1268,7 +1268,7 @@ namespace Tgstation.Server.Host.Swarm
else
await HealthCheckController(cancellationToken);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "Health check error!");
}
@@ -61,45 +61,79 @@
</Target>
<ItemGroup>
<!-- Usage: Concise throw statements for native Win32 errors -->
<PackageReference Include="BetterWin32Errors" Version="0.2.0" />
<!-- Usage: Interop with BYOND's /world/Topic -->
<PackageReference Include="Byond.TopicSender" Version="5.0.0" />
<!-- Usage: 503'ing request pipeline until server is finished initializing -->
<PackageReference Include="Cyberboss.AspNetCore.AsyncInitializer" Version="1.2.0" />
<!-- Usage: IRC interop -->
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.7" />
<!-- Usage: Text formatter for Elasticsearch logging plugin -->
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="1.5.3" />
<!-- Usage: GitLab interop -->
<PackageReference Include="GitLabApiClient" Version="1.8.0" />
<!-- Usage: git interop -->
<!-- Pinned: Later versions are known to show issues with TGS. Do not upgrade without thorough git testing -->
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0034" />
<!-- Usage: JWT injection into HTTP pipeline -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.15" />
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.15" />
<!-- Usage: Database ORM -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.15" />
<!-- Usage: Automatic migration generation using command line -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: Sqlite ORM plugin -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.15" />
<!-- Usage: MSSQL ORM plugin -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.15" />
<!-- Required for https://github.com/coverlet-coverage/coverlet/issues/1381 -->
<!-- Usage: Included transitively through other packages, workaround for https://github.com/coverlet-coverage/coverlet/issues/1381 -->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.3" />
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<!-- Usage: YAML config plugin -->
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.0.0" />
<!-- Usage: PostgresSQL ORM plugin -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.8" />
<PackageReference Include="Octokit" Version="5.0.2" />
<!-- Usage: GitHub.com interop -->
<PackageReference Include="Octokit" Version="5.0.3" />
<!-- Usage: MYSQL/MariaDB ORM plugin -->
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
<!-- Usage: Discord interop -->
<!-- Pinned: 2022.49.0 is the last .NET 6.0 version but has issues -->
<PackageReference Include="Remora.Discord" Version="2022.48.0" />
<!-- Usage: Rich logger builder -->
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<!-- Usage: Async logging plugin -->
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<!-- Usage: Console logging plugin -->
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<!-- Usage: Elasticsearch logging plugin -->
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="9.0.0" />
<!-- Usage: File logging plugin -->
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<!-- Usage: Linting -->
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: OpenAPI spec generator -->
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<!-- Usage: Newtonsoft.Json plugin for OpenAPI spec generator -->
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<!-- Usage: Access to raw database connectors primarily for setup wizard -->
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<!-- Usage: Windows authentication plugin allowing searching for users by name -->
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="6.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<!-- Usage: JWT plugin needed to undo some Microsoft meddling in the HTTP pipeline -->
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.28.1" />
<!-- Usage: Identifying owning user of Windows Process objects -->
<PackageReference Include="System.Management" Version="6.0.0" />
<!-- Usage: .DeleteAsync() support for IQueryable<T>s -->
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="6.20.1" />
</ItemGroup>
@@ -29,9 +29,9 @@ namespace Tgstation.Server.Host.Transfer
readonly TaskCompletionSource<Stream> taskCompletionSource;
/// <summary>
/// The <see cref="TaskCompletionSource{TResult}"/> that completes in <see cref="IDisposable.Dispose"/> or when <see cref="SetErrorMessage(ErrorMessageResponse)"/> is called.
/// The <see cref="TaskCompletionSource"/> that completes in <see cref="IDisposable.Dispose"/> or when <see cref="SetErrorMessage(ErrorMessageResponse)"/> is called.
/// </summary>
readonly TaskCompletionSource<object> completionTcs;
readonly TaskCompletionSource completionTcs;
/// <summary>
/// If synchronous IO is required. Uses a <see cref="FileBufferingReadStream"/> as a backend if set.
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Transfer
ticketExpiryCts = new CancellationTokenSource();
taskCompletionSource = new TaskCompletionSource<Stream>();
completionTcs = new TaskCompletionSource<object>();
completionTcs = new TaskCompletionSource();
this.requireSynchronousIO = requireSynchronousIO;
}
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Transfer
public void Dispose()
{
ticketExpiryCts.Dispose();
completionTcs.TrySetResult(null);
completionTcs.TrySetResult();
}
/// <inheritdoc />
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Transfer
throw new InvalidOperationException("ErrorMessage already set!");
this.errorMessage = errorMessage;
completionTcs.TrySetResult(null);
completionTcs.TrySetResult();
}
}
}
@@ -18,10 +18,10 @@ namespace Tgstation.Server.Host.Tests.Signals
{
var mockServerControl = new Mock<IServerControl>();
var tcs = new TaskCompletionSource<object>();
var tcs = new TaskCompletionSource();
mockServerControl
.Setup(x => x.GracefulShutdown())
.Callback(() => tcs.SetResult(null))
.Callback(() => tcs.SetResult())
.Returns(Task.CompletedTask);
var mockAsyncDelayer = new Mock<IAsyncDelayer>();
@@ -20,13 +20,17 @@ namespace Tgstation.Server.Host.IO.Tests
Directory.CreateDirectory(tempPath);
try
{
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
var subDir = Path.Combine(tempPath, "subdir");
Directory.CreateDirectory(subDir);
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
await ioManager.DeleteDirectory(tempPath, default);
Assert.IsFalse(Directory.Exists(tempPath));
}
catch
{
Directory.Delete(tempPath);
Directory.Delete(tempPath, true);
throw;
}
}
@@ -35,14 +35,14 @@ namespace Tgstation.Server.Host.Jobs.Tests
//test with a cancelled cts
using (var cts = new CancellationTokenSource())
{
var tcs = new TaskCompletionSource<object>();
var tcs = new TaskCompletionSource();
currentWaitTask = tcs.Task;
cts.Cancel();
using var handler = new JobHandler(TestJob);
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => handler.Wait(cts.Token));
handler.Start();
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => handler.Wait(cts.Token));
tcs.SetResult(null);
tcs.SetResult();
await handler.Wait(default);
}
Assert.IsFalse(cancelled);
@@ -65,14 +65,14 @@ namespace Tgstation.Server.Host.Jobs.Tests
[TestMethod]
public async Task TestCancellation()
{
var tcs = new TaskCompletionSource<object>();
var tcs = new TaskCompletionSource();
currentWaitTask = tcs.Task;
cancelled = false;
using (var handler = new JobHandler(TestJob))
{
handler.Start();
handler.Cancel();
tcs.SetResult(null);
tcs.SetResult();
await handler.Wait(default);
}
Assert.IsTrue(cancelled);
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Host.Components;
namespace Tgstation.Server.Tests.Instance
{
@@ -11,11 +12,13 @@ namespace Tgstation.Server.Tests.Instance
{
readonly IInstanceClient instanceClient;
readonly IInstanceManagerClient instanceManagerClient;
readonly IInstanceManager instanceManager;
public InstanceTest(IInstanceClient instanceClient, IInstanceManagerClient instanceManagerClient)
public InstanceTest(IInstanceClient instanceClient, IInstanceManagerClient instanceManagerClient, IInstanceManager instanceManager)
{
this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient));
this.instanceManagerClient = instanceManagerClient ?? throw new ArgumentNullException(nameof(instanceManagerClient));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
}
public async Task RunTests(CancellationToken cancellationToken)
@@ -35,7 +38,7 @@ namespace Tgstation.Server.Tests.Instance
await configTest.Run(cancellationToken);
await chatTests;
await repoTests;
await new WatchdogTest(instanceClient).Run(cancellationToken);
await new WatchdogTest(instanceClient, instanceManager).Run(cancellationToken);
}
}
}
@@ -1,8 +1,11 @@
using Byond.TopicSender;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@@ -17,7 +20,9 @@ using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
@@ -27,13 +32,15 @@ namespace Tgstation.Server.Tests.Instance
sealed class WatchdogTest : JobsRequiredTest
{
readonly IInstanceClient instanceClient;
readonly IInstanceManager instanceManager;
bool ranTimeoutTest = false;
public WatchdogTest(IInstanceClient instanceClient)
public WatchdogTest(IInstanceClient instanceClient, IInstanceManager instanceManager)
: base(instanceClient.Jobs)
{
this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
}
public async Task Run(CancellationToken cancellationToken)
@@ -66,6 +73,9 @@ namespace Tgstation.Server.Tests.Instance
await TestDMApiFreeDeploy(cancellationToken);
await RunLongRunningTestThenUpdate(cancellationToken);
await GhettoChatCommandTest(cancellationToken);
await RunLongRunningTestThenUpdateWithNewDme(cancellationToken);
await RunLongRunningTestThenUpdateWithByondVersionSwitch(cancellationToken);
@@ -91,11 +101,11 @@ namespace Tgstation.Server.Tests.Instance
File.Delete(dumpFiles.Single());
KillDD(true);
TaskCompletionSource<object> jobTcs = new TaskCompletionSource<object>();
TaskCompletionSource<object> killTaskStarted = new TaskCompletionSource<object>();
var jobTcs = new TaskCompletionSource();
var killTaskStarted = new TaskCompletionSource();
var killTask = Task.Run(() =>
{
killTaskStarted.SetResult(null);
killTaskStarted.SetResult();
while (!jobTcs.Task.IsCompleted)
KillDD(false);
}, cancellationToken);
@@ -109,7 +119,7 @@ namespace Tgstation.Server.Tests.Instance
}
finally
{
jobTcs.SetResult(null);
jobTcs.SetResult();
await killTask;
}
Assert.IsTrue(job.ErrorCode == ErrorCode.DreamDaemonOffline || job.ErrorCode == ErrorCode.GCoreFailure, $"{job.ErrorCode}: {job.ExceptionDetails}");
@@ -318,6 +328,66 @@ namespace Tgstation.Server.Tests.Instance
return await instanceClient.DreamDaemon.Start(cancellationToken);
}
async Task GhettoChatCommandTest(CancellationToken cancellationToken)
{
var startJob = await StartDD(cancellationToken);
await WaitForJob(startJob, 40, false, null, cancellationToken);
// oh god, oh fuck, blackbox testing
MessageContent response;
var startTime = DateTimeOffset.UtcNow - TimeSpan.FromSeconds(5);
using (var instanceReference = instanceManager.GetInstanceReference(instanceClient.Metadata))
{
response = await ((BasicWatchdog)instanceReference.Watchdog).HandleChatCommand(
"embeds_test",
String.Empty,
new Host.Components.Chat.ChatUser
{
Channel = new Host.Components.Chat.ChannelRepresentation
{
IsAdminChannel = true,
ConnectionName = "test_connection",
EmbedsSupported = true,
FriendlyName = "Test Connection",
Id = "test_channel_id",
IsPrivateChannel = false,
},
FriendlyName = "Test Sender",
Id = "test_user_id",
Mention = "test_user_mention",
RealId = 1234,
},
cancellationToken);
}
var endTime = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5);
Assert.IsNotNull(response);
Assert.AreEqual("Embed support test2", response.Text);
Assert.AreEqual("desc", response.Embed.Description);
Assert.AreEqual("title", response.Embed.Title);
Assert.AreEqual("#0000FF", response.Embed.Colour);
Assert.AreEqual("Dominion", response.Embed.Author?.Name);
Assert.AreEqual("https://github.com/Cyberboss", response.Embed.Author.Url);
Assert.IsTrue(DateTimeOffset.TryParse(response.Embed.Timestamp, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var timestamp));
Assert.IsTrue(startTime < timestamp && endTime > timestamp);
Assert.AreEqual("https://github.com/tgstation/tgstation-server", response.Embed.Url);
Assert.AreEqual(3, response.Embed.Fields?.Count);
Assert.AreEqual("field1", response.Embed.Fields.ElementAt(0).Name);
Assert.AreEqual("value1", response.Embed.Fields.ElementAt(0).Value);
Assert.IsNull(response.Embed.Fields.ElementAt(0).IsInline);
Assert.AreEqual("field2", response.Embed.Fields.ElementAt(1).Name);
Assert.AreEqual("value2", response.Embed.Fields.ElementAt(1).Value);
Assert.IsTrue(response.Embed.Fields.ElementAt(1).IsInline);
Assert.AreEqual("field3", response.Embed.Fields.ElementAt(2).Name);
Assert.AreEqual("value3", response.Embed.Fields.ElementAt(2).Value);
Assert.IsTrue(response.Embed.Fields.ElementAt(2).IsInline);
Assert.AreEqual("Footer text", response.Embed.Footer?.Text);
await instanceClient.DreamDaemon.Shutdown(cancellationToken);
}
async Task RunLongRunningTestThenUpdate(CancellationToken cancellationToken)
{
global::System.Console.WriteLine("TEST: WATCHDOG LONG RUNNING WITH UPDATE TEST");
@@ -27,6 +27,8 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
using Tgstation.Server.Host;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Components.Events;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Configuration;
@@ -827,6 +829,8 @@ namespace Tgstation.Server.Tests
TerminateAllDDs();
IInstanceManager GetInstanceManager() => ((Host.Server)server.RealServer).Host.Services.GetRequiredService<IInstanceManager>();
// main run
var serverTask = server.Run(cancellationToken);
@@ -874,7 +878,7 @@ namespace Tgstation.Server.Tests
Assert.IsTrue(Directory.Exists(instanceClient.Metadata.Path));
var instanceTests = FailFast(new InstanceTest(instanceClient, adminClient.Instances).RunTests(cancellationToken));
var instanceTests = FailFast(new InstanceTest(instanceClient, adminClient.Instances, GetInstanceManager()).RunTests(cancellationToken));
await Task.WhenAll(rootTest, adminTest, instanceTests, usersTest);
@@ -994,7 +998,7 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value);
var compileJob = await instanceClient.DreamMaker.Compile(cancellationToken);
var wdt = new WatchdogTest(instanceClient);
var wdt = new WatchdogTest(instanceClient, GetInstanceManager());
await wdt.WaitForJob(compileJob, 30, false, null, cancellationToken);
dd = await instanceClient.DreamDaemon.Read(cancellationToken);
@@ -1041,7 +1045,7 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(WatchdogStatus.Online, currentDD.Status);
Assert.AreEqual(expectedStaged, currentDD.StagedCompileJob.Job.Id.Value);
var wdt = new WatchdogTest(instanceClient);
var wdt = new WatchdogTest(instanceClient, GetInstanceManager());
currentDD = await wdt.TellWorldToReboot(cancellationToken);
Assert.AreEqual(expectedStaged, currentDD.ActiveCompileJob.Job.Id.Value);
Assert.IsNull(currentDD.StagedCompileJob);
@@ -27,11 +27,11 @@ namespace Tgstation.Server.Tests
public bool DumpOpenApiSpecpath { get; }
public bool RestartRequested => realServer.RestartRequested;
public bool RestartRequested => RealServer.RestartRequested;
string[] args;
IServer realServer;
public IServer RealServer { get; private set; }
public TestingServer(SwarmConfiguration swarmConfiguration, bool enableOAuth, ushort port = 5010)
{
@@ -154,8 +154,8 @@ namespace Tgstation.Server.Tests
public async Task Run(CancellationToken cancellationToken)
{
Console.WriteLine("TEST SERVER START");
var firstRun = realServer == null;
realServer = await Application
var firstRun = RealServer == null;
RealServer = await Application
.CreateDefaultServerFactory()
.CreateServer(
args,
@@ -169,7 +169,7 @@ namespace Tgstation.Server.Tests
args = tmp.ToArray();
}
await realServer.Run(cancellationToken);
await RealServer.Run(cancellationToken);
Console.WriteLine("TEST SERVER END");
}
}