mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 20:47:28 +01:00
Merge pull request #1451 from tgstation/FixesNSHit [TGSDeploy]
Fix some events getting relayed to DreamDaemon unintentionally
This commit is contained in:
+1
-1
@@ -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
@@ -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]")
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user