mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-18 03:23:07 +01:00
More CI cleanups
This commit is contained in:
@@ -513,7 +513,7 @@ jobs:
|
||||
|
||||
deploy-http:
|
||||
name: Deploy HTTP API
|
||||
needs: [linux-unit-tests, linux-integration-tests, windows-unit-tests, windows-integration-test]
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && github.ref == 'master' && contains(github.event.head_commit.message, '[APIDeploy]')
|
||||
steps:
|
||||
@@ -556,7 +556,7 @@ jobs:
|
||||
|
||||
deploy-dm:
|
||||
name: Deploy DreamMaker API
|
||||
needs: [linux-unit-tests, linux-integration-tests, windows-unit-tests, windows-integration-test]
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && github.ref == 'master' && contains(github.event.head_commit.message, '[DMDeploy]')
|
||||
steps:
|
||||
@@ -597,7 +597,7 @@ jobs:
|
||||
|
||||
deploy-client:
|
||||
name: Deploy Nuget Packages
|
||||
needs: [linux-unit-tests, linux-integration-tests, windows-unit-tests, windows-integration-test]
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && github.ref == 'master' && contains(github.event.head_commit.message, '[NugetDeploy]')
|
||||
steps:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tgstation-server v4:
|
||||
|
||||
 [](https://travis-ci.org/tgstation/tgstation-server) [](https://codecov.io/gh/tgstation/tgstation-server)
|
||||
 [](https://travis-ci.org/tgstation/tgstation-server) [](https://codecov.io/gh/tgstation/tgstation-server)
|
||||
|
||||
[](LICENSE) [](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [](https://www.nuget.org/packages/Tgstation.Server.Api) [](https://www.nuget.org/packages/Tgstation.Server.Client)
|
||||
|
||||
|
||||
@@ -131,13 +131,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Mention = NormalizeMentions(e.Author.Mention)
|
||||
}
|
||||
};
|
||||
|
||||
EnqueueMessage(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Connect(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Connecting...");
|
||||
try
|
||||
{
|
||||
await client.LoginAsync(TokenType.Bot, BotToken, true).ConfigureAwait(false);
|
||||
@@ -150,14 +150,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Logger.LogTrace("Started.");
|
||||
|
||||
var channelsAvailable = new TaskCompletionSource<object>();
|
||||
client.Ready += () =>
|
||||
Task ReadyCallback()
|
||||
{
|
||||
channelsAvailable.TrySetResult(null);
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
using (cancellationToken.Register(() => channelsAvailable.SetCanceled()))
|
||||
await channelsAvailable.Task.ConfigureAwait(false);
|
||||
Logger.LogDebug("Connection established!");
|
||||
}
|
||||
|
||||
client.Ready += ReadyCallback;
|
||||
try
|
||||
{
|
||||
using (cancellationToken.Register(() => channelsAvailable.SetCanceled()))
|
||||
await channelsAvailable.Task.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.Ready -= ReadyCallback;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -172,13 +180,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
protected override async Task DisconnectImpl(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Disconnecting...");
|
||||
if (!Connected)
|
||||
{
|
||||
Logger.LogTrace("Already disconnected not doing disconnection attempt!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -254,7 +255,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
var channel = client.GetChannel(channelId) as IMessageChannel;
|
||||
await (channel?.SendMessageAsync(message, false, null, new RequestOptions
|
||||
{
|
||||
CancelToken = cancellationToken
|
||||
CancelToken = cancellationToken,
|
||||
Timeout = 10000 // prevent stupid long hold ups from this
|
||||
}) ?? Task.CompletedTask).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
sealed class IrcProvider : Provider
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of seconds used for several IRC related timeouts.
|
||||
/// </summary>
|
||||
const int TimeoutSeconds = 5;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -149,7 +152,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
await HardDisconnect().ConfigureAwait(false);
|
||||
|
||||
// DCT: None available
|
||||
await HardDisconnect(default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -239,6 +244,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Logger.LogTrace("Authenticating ({0})...", passwordType);
|
||||
switch (passwordType)
|
||||
{
|
||||
case IrcPasswordType.Server:
|
||||
@@ -259,24 +265,50 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
client.Listen(false);
|
||||
Logger.LogTrace("Processing initial messages...");
|
||||
await NonBlockingListen(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
listenTask = Task.Factory.StartNew(() =>
|
||||
var nickCheckCompleteTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => nickCheckCompleteTcs.TrySetCanceled()))
|
||||
{
|
||||
while (!disconnecting && client.IsConnected && client.Nickname != nickname)
|
||||
listenTask = Task.Factory.StartNew(
|
||||
async () =>
|
||||
{
|
||||
client.ListenOnce(true);
|
||||
if (disconnecting || !client.IsConnected)
|
||||
break;
|
||||
client.Listen(false);
|
||||
Logger.LogTrace("Entering nick check loop");
|
||||
while (!disconnecting && client.IsConnected && client.Nickname != nickname)
|
||||
{
|
||||
client.ListenOnce(true);
|
||||
if (disconnecting || !client.IsConnected)
|
||||
break;
|
||||
await NonBlockingListen(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// ensure we have the correct nick
|
||||
if (client.GetIrcUser(nickname) == null)
|
||||
client.RfcNick(nickname);
|
||||
}
|
||||
// ensure we have the correct nick
|
||||
if (client.GetIrcUser(nickname) == null)
|
||||
client.RfcNick(nickname);
|
||||
}
|
||||
|
||||
client.Listen();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
nickCheckCompleteTcs.TrySetResult(null);
|
||||
|
||||
Logger.LogTrace("Starting blocking listen...");
|
||||
try
|
||||
{
|
||||
client.Listen();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning("IRC Listen Error: {0}", ex);
|
||||
}
|
||||
|
||||
Logger.LogTrace("Exiting listening task...");
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current);
|
||||
|
||||
await nickCheckCompleteTcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Logger.LogTrace("Connection established!");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -288,6 +320,28 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a non-blocking <see cref="IrcConnection.Listen(bool)"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task NonBlockingListen(CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Listen(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning("IRC Listen Error: {0}", ex);
|
||||
}
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Current)
|
||||
.WithToken(cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run SASL authentication on <see cref="client"/>.
|
||||
/// </summary>
|
||||
@@ -297,6 +351,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Logger.LogTrace("Logging in...");
|
||||
client.Login(nickname, nickname, 0, nickname);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -312,69 +368,72 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
recievedPlus = true;
|
||||
}
|
||||
|
||||
Logger.LogTrace("Performing handshake...");
|
||||
client.OnReadLine += AuthenticationDelegate;
|
||||
|
||||
try
|
||||
{
|
||||
using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||
{
|
||||
timeoutCts.CancelAfter(TimeSpan.FromSeconds(TimeoutSeconds));
|
||||
var timeoutToken = timeoutCts.Token;
|
||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeoutCts.CancelAfter(TimeSpan.FromSeconds(TimeoutSeconds));
|
||||
var timeoutToken = timeoutCts.Token;
|
||||
|
||||
var listenTimeSpan = TimeSpan.FromMilliseconds(10);
|
||||
for (; !recievedAck;
|
||||
await asyncDelayer.Delay(listenTimeSpan, timeoutToken).ConfigureAwait(false))
|
||||
client.Listen(false);
|
||||
var listenTimeSpan = TimeSpan.FromMilliseconds(10);
|
||||
for (; !recievedAck;
|
||||
await asyncDelayer.Delay(listenTimeSpan, timeoutToken).ConfigureAwait(false))
|
||||
await NonBlockingListen(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical);
|
||||
timeoutToken.ThrowIfCancellationRequested();
|
||||
client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical);
|
||||
timeoutToken.ThrowIfCancellationRequested();
|
||||
|
||||
for (; !recievedPlus;
|
||||
await asyncDelayer.Delay(listenTimeSpan, timeoutToken).ConfigureAwait(false))
|
||||
client.Listen(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
var authString = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}{1}{0}{1}{2}",
|
||||
nickname,
|
||||
'\0',
|
||||
password);
|
||||
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString));
|
||||
var authLine = $"AUTHENTICATE {b64}";
|
||||
client.WriteLine(authLine, Priority.Critical);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
client.WriteLine("CAP END", Priority.Critical);
|
||||
for (; !recievedPlus;
|
||||
await asyncDelayer.Delay(listenTimeSpan, timeoutToken).ConfigureAwait(false))
|
||||
await NonBlockingListen(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.OnReadLine -= AuthenticationDelegate;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
Logger.LogTrace("Sending credentials...");
|
||||
var authString = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}{1}{0}{1}{2}",
|
||||
nickname,
|
||||
'\0',
|
||||
password);
|
||||
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString));
|
||||
var authLine = $"AUTHENTICATE {b64}";
|
||||
client.WriteLine(authLine, Priority.Critical);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Logger.LogTrace("Finishing authentication...");
|
||||
client.WriteLine("CAP END", Priority.Critical);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task DisconnectImpl(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Connected)
|
||||
return;
|
||||
try
|
||||
{
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning("Error quitting IRC: {0}", e);
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
await HardDisconnect().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning("Error quitting IRC: {0}", e);
|
||||
}
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Current)
|
||||
.ConfigureAwait(false);
|
||||
await HardDisconnect(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -386,16 +445,42 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
}
|
||||
|
||||
async Task HardDisconnect()
|
||||
async Task HardDisconnect(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Connected)
|
||||
{
|
||||
Logger.LogTrace("Not hard disconnecting, already offline");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogTrace("Hard disconnect");
|
||||
|
||||
disconnecting = true;
|
||||
client.Disconnect();
|
||||
|
||||
if(listenTask != null)
|
||||
await listenTask.ConfigureAwait(false);
|
||||
// This call blocks permanently randomly sometimes
|
||||
// Frankly I don't give a shit
|
||||
var disconnectTask = Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Disconnect();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogWarning("Error disconnecting IRC: {0}", e);
|
||||
}
|
||||
},
|
||||
cancellationToken,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Current);
|
||||
|
||||
await Task.WhenAny(
|
||||
Task.WhenAll(
|
||||
disconnectTask,
|
||||
listenTask ?? Task.CompletedTask),
|
||||
asyncDelayer.Delay(TimeSpan.FromSeconds(TimeoutSeconds), cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
@@ -119,8 +120,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public async Task Disconnect(CancellationToken cancellationToken)
|
||||
{
|
||||
if(Connected)
|
||||
if (Connected)
|
||||
{
|
||||
await DisconnectImpl(cancellationToken).ConfigureAwait(false);
|
||||
Logger.LogTrace("Disconnected");
|
||||
}
|
||||
|
||||
await StopReconnectionTimer().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -130,16 +135,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <inheritdoc />
|
||||
public async Task<Message> NextMessage(CancellationToken cancellationToken)
|
||||
{
|
||||
var cancelTcs = new TaskCompletionSource<object>();
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
|
||||
await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
lock (messageQueue)
|
||||
while (true)
|
||||
{
|
||||
var result = messageQueue.Dequeue();
|
||||
if (messageQueue.Count == 0)
|
||||
nextMessage = new TaskCompletionSource<object>();
|
||||
return result;
|
||||
await nextMessage.Task.WithToken(cancellationToken).ConfigureAwait(false);
|
||||
lock (messageQueue)
|
||||
if (messageQueue.Count > 0)
|
||||
{
|
||||
var result = messageQueue.Dequeue();
|
||||
if (messageQueue.Count == 0)
|
||||
nextMessage = new TaskCompletionSource<object>();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +217,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
job,
|
||||
async (core, databaseContextFactory, paramJob, progressReporter, jobCancellationToken) =>
|
||||
{
|
||||
await DisconnectImpl(jobCancellationToken).ConfigureAwait(false);
|
||||
if (Connected)
|
||||
{
|
||||
Logger.LogTrace("Disconnecting...");
|
||||
await DisconnectImpl(jobCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
Logger.LogTrace("Already disconnected not doing disconnection attempt!");
|
||||
|
||||
Logger.LogTrace("Connecting...");
|
||||
await Connect(jobCancellationToken).ConfigureAwait(false);
|
||||
Logger.LogTrace("Connected successfully");
|
||||
EnqueueMessage(null);
|
||||
},
|
||||
cancellationToken)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -202,26 +202,32 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
if (compileJob == null)
|
||||
throw new ArgumentNullException(nameof(compileJob));
|
||||
|
||||
// ensure we have the entire compile job tree
|
||||
// ensure we have the entire metadata tree
|
||||
logger.LogTrace("Loading compile job {0}...", compileJob.Id);
|
||||
await databaseContextFactory.UseContext(
|
||||
async db => compileJob = await db
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == compileJob.Id)
|
||||
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
|
||||
.Include(x => x.Job)
|
||||
.ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation)
|
||||
.ThenInclude(x => x.PrimaryTestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.Include(x => x.RevisionInformation)
|
||||
.ThenInclude(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false); // can't wait to see that query
|
||||
|
||||
if (!compileJob.Job.StoppedAt.HasValue)
|
||||
{
|
||||
// This happens if we're told to load the compile job that is currently finished up
|
||||
// It can constitute an API violation if it's returned by the DreamDaemonController so just set it here
|
||||
// Bit of a hack, but it should work out to be the same value
|
||||
logger.LogTrace("Setting missing StoppedAt for CompileJob job...");
|
||||
// This happens when we're told to load the compile job that is currently finished up
|
||||
// It constitutes an API violation if it's returned by the DreamDaemonController so just set it here
|
||||
// Bit of a hack, but it works out to be nearly if not the same value that's put in the DB
|
||||
logger.LogTrace("Setting missing StoppedAt for CompileJob.Job #{0}...", compileJob.Job.Id);
|
||||
compileJob.Job.StoppedAt = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
@@ -284,9 +290,9 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
else
|
||||
jobLockCounts[compileJob.Id] = ++value;
|
||||
|
||||
logger.LogTrace("Compile job {0} lock count now: {1}", compileJob.Id, value);
|
||||
|
||||
providerSubmitted = true;
|
||||
|
||||
logger.LogTrace("Compile job {0} lock count now: {1}", compileJob.Id, value);
|
||||
return newProvider;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Octokit;
|
||||
using System;
|
||||
@@ -633,6 +633,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
|
||||
// The difficulty with compile jobs is they have a two part commit
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogTrace("Created CompileJob {0}", compileJob.Id);
|
||||
try
|
||||
{
|
||||
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace Tgstation.Server.Host.Components
|
||||
// race condition, just quit
|
||||
if (timerTask != null)
|
||||
{
|
||||
logger.LogDebug("Aborting auto update interval change due to race condition!");
|
||||
logger.LogWarning("Aborting auto update interval change due to race condition!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -116,10 +116,11 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
/// Check if a given <paramref name="port"/> can be bound to.
|
||||
/// </summary>
|
||||
/// <param name="port">The port number to test.</param>
|
||||
static void PortBindTest(ushort port)
|
||||
void PortBindTest(ushort port)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Bind test: {0}", port);
|
||||
SocketExtensions.BindTest(port, false);
|
||||
}
|
||||
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
||||
|
||||
@@ -159,12 +159,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
protected override async Task DisposeAndNullControllersImpl()
|
||||
{
|
||||
var disposeTask = Server?.DisposeAsync();
|
||||
gracefulRebootRequired = false;
|
||||
if (!disposeTask.HasValue)
|
||||
return;
|
||||
|
||||
await disposeTask.Value.ConfigureAwait(false);
|
||||
Server = null;
|
||||
gracefulRebootRequired = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -56,11 +56,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="InstanceController"/>
|
||||
/// </summary>
|
||||
readonly IAssemblyInformationProvider assemblyInformationProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPlatformIdentifier"/> for the <see cref="InstanceController"/>
|
||||
/// </summary>
|
||||
@@ -79,7 +74,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/></param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
|
||||
@@ -89,7 +83,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
IJobManager jobManager,
|
||||
IInstanceManager instanceManager,
|
||||
IIOManager ioManager,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<InstanceController> logger)
|
||||
@@ -102,11 +95,51 @@ namespace Tgstation.Server.Host.Controllers
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
}
|
||||
|
||||
Models.Instance CreateDefaultInstance(Api.Models.Instance initialSettings)
|
||||
=> new Models.Instance
|
||||
{
|
||||
ConfigurationType = initialSettings.ConfigurationType ?? ConfigurationType.Disallowed,
|
||||
DreamDaemonSettings = new DreamDaemonSettings
|
||||
{
|
||||
AllowWebClient = false,
|
||||
AutoStart = false,
|
||||
Port = 1337,
|
||||
SecurityLevel = DreamDaemonSecurity.Safe,
|
||||
StartupTimeout = 60,
|
||||
HeartbeatSeconds = 60,
|
||||
TopicRequestTimeout = generalConfiguration.ByondTopicTimeout
|
||||
},
|
||||
DreamMakerSettings = new DreamMakerSettings
|
||||
{
|
||||
ApiValidationPort = 1339,
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Safe,
|
||||
RequireDMApiValidation = true
|
||||
},
|
||||
Name = initialSettings.Name,
|
||||
Online = false,
|
||||
Path = initialSettings.Path,
|
||||
AutoUpdateInterval = initialSettings.AutoUpdateInterval ?? 0,
|
||||
ChatBotLimit = initialSettings.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
|
||||
RepositorySettings = new RepositorySettings
|
||||
{
|
||||
CommitterEmail = "tgstation-server@users.noreply.github.com",
|
||||
CommitterName = "tgstation-server",
|
||||
PushTestMergeCommits = false,
|
||||
ShowTestMergeCommitters = false,
|
||||
AutoUpdatesKeepTestMerges = false,
|
||||
AutoUpdatesSynchronize = false,
|
||||
PostTestMergeComment = false
|
||||
},
|
||||
InstanceUsers = new List<Models.InstanceUser> // give this user full privileges on the instance
|
||||
{
|
||||
InstanceAdminUser(null)
|
||||
}
|
||||
};
|
||||
|
||||
string NormalizePath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
@@ -243,45 +276,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
else
|
||||
attached = true;
|
||||
|
||||
var newInstance = new Models.Instance
|
||||
{
|
||||
ConfigurationType = model.ConfigurationType ?? ConfigurationType.Disallowed,
|
||||
DreamDaemonSettings = new DreamDaemonSettings
|
||||
{
|
||||
AllowWebClient = false,
|
||||
AutoStart = false,
|
||||
Port = 1337,
|
||||
SecurityLevel = DreamDaemonSecurity.Safe,
|
||||
StartupTimeout = 60,
|
||||
HeartbeatSeconds = 60,
|
||||
TopicRequestTimeout = generalConfiguration.ByondTopicTimeout
|
||||
},
|
||||
DreamMakerSettings = new DreamMakerSettings
|
||||
{
|
||||
ApiValidationPort = 1339,
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Safe,
|
||||
RequireDMApiValidation = true
|
||||
},
|
||||
Name = model.Name,
|
||||
Online = false,
|
||||
Path = model.Path,
|
||||
AutoUpdateInterval = model.AutoUpdateInterval ?? 0,
|
||||
ChatBotLimit = model.ChatBotLimit ?? Models.Instance.DefaultChatBotLimit,
|
||||
RepositorySettings = new RepositorySettings
|
||||
{
|
||||
CommitterEmail = "tgstation-server@users.noreply.github.com",
|
||||
CommitterName = assemblyInformationProvider.VersionPrefix,
|
||||
PushTestMergeCommits = false,
|
||||
ShowTestMergeCommitters = false,
|
||||
AutoUpdatesKeepTestMerges = false,
|
||||
AutoUpdatesSynchronize = false,
|
||||
PostTestMergeComment = false
|
||||
},
|
||||
InstanceUsers = new List<Models.InstanceUser> // give this user full privileges on the instance
|
||||
{
|
||||
InstanceAdminUser(null)
|
||||
}
|
||||
};
|
||||
var newInstance = CreateDefaultInstance(model);
|
||||
|
||||
DatabaseContext.Instances.Add(newInstance);
|
||||
try
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
@@ -98,6 +98,10 @@ namespace Tgstation.Server.Host.Core
|
||||
// enable options which give us config reloading
|
||||
services.AddOptions();
|
||||
|
||||
// Set the timeout for IHostedService.StopAsync
|
||||
services.Configure<HostOptions>(
|
||||
opts => opts.ShutdownTimeout = TimeSpan.FromMilliseconds(postSetupServices.GeneralConfiguration.RestartTimeout));
|
||||
|
||||
static LogEventLevel? ConvertSeriLogLevel(LogLevel logLevel) =>
|
||||
logLevel switch
|
||||
{
|
||||
@@ -441,4 +445,4 @@ namespace Tgstation.Server.Host.Core
|
||||
// 404 anything that gets this far
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
@@ -55,8 +55,7 @@ namespace Tgstation.Server.Host.Database.Migrations
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ServerCommandsJson",
|
||||
table: "ReattachInformations",
|
||||
nullable: false,
|
||||
defaultValue: "server_commands.tgs.json");
|
||||
nullable: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
|
||||
await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogTrace("Starting job...");
|
||||
await operation(
|
||||
instanceCoreProvider.Value.GetInstance(oldJob.Instance),
|
||||
databaseContextFactory,
|
||||
@@ -201,7 +202,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description);
|
||||
logger.LogDebug("Registering job {0}: {1}...", job.Id, job.Description);
|
||||
var jobHandler = new JobHandler(jobCancellationToken => RunJob(job, operation, jobCancellationToken));
|
||||
try
|
||||
{
|
||||
@@ -283,15 +284,12 @@ namespace Tgstation.Server.Host.Jobs
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
user = await databaseContext.Users.GetTgsUser(cancellationToken).ConfigureAwait(false);
|
||||
databaseContext.Users.Attach(user);
|
||||
}
|
||||
|
||||
var updatedJob = new Job { Id = job.Id };
|
||||
databaseContext.Jobs.Attach(job);
|
||||
databaseContext.Jobs.Attach(updatedJob);
|
||||
var attachedUser = new User { Id = user.Id };
|
||||
databaseContext.Users.Attach(user);
|
||||
databaseContext.Users.Attach(attachedUser);
|
||||
updatedJob.CancelledBy = attachedUser;
|
||||
|
||||
// let either startup or cancellation set job.cancelled
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -290,29 +290,29 @@ namespace Tgstation.Server.Host
|
||||
}
|
||||
|
||||
if (exception == null)
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
logger.LogInformation("Restarting server...");
|
||||
var cancellationToken = cts.Token;
|
||||
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
|
||||
{
|
||||
logger.LogInformation("Restarting server...");
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(generalConfiguration.RestartTimeout));
|
||||
var cancellationToken = cts.Token;
|
||||
var eventsTask = Task.WhenAll(
|
||||
restartHandlers.Select(
|
||||
x => x.HandleRestart(newVersion, cancellationToken))
|
||||
.ToList());
|
||||
|
||||
var expiryTask = Task.Delay(TimeSpan.FromMilliseconds(generalConfiguration.RestartTimeout));
|
||||
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);
|
||||
logger.LogTrace("Joining restart handlers...");
|
||||
cts.Cancel();
|
||||
try
|
||||
{
|
||||
await eventsTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogError("Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Restart handlers error! Exception: {0}", e);
|
||||
}
|
||||
logger.LogTrace("Joining restart handlers...");
|
||||
try
|
||||
{
|
||||
await eventsTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogError("Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Restart handlers error! Exception: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogTrace("Stopping host...");
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
@@ -10,6 +10,11 @@ namespace Tgstation.Server.Host.System
|
||||
/// <inheritdoc />
|
||||
sealed class Process : IProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum time to wait in a call to <see cref="global::System.Diagnostics.Process.WaitForExit(int)"/>.
|
||||
/// </summary>
|
||||
const int MaximumWaitMilliseconds = 30000;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Id { get; }
|
||||
|
||||
@@ -31,6 +36,11 @@ namespace Tgstation.Server.Host.System
|
||||
|
||||
readonly global::System.Diagnostics.Process handle;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="TaskCompletionSource{TResult}"/> so that we can complete <see cref="Lifetime"/> if the <see cref="handle"/> becomes unresponsive.
|
||||
/// </summary>
|
||||
readonly TaskCompletionSource<object> emergencyLifetimeTcs;
|
||||
|
||||
readonly StringBuilder outputStringBuilder;
|
||||
readonly StringBuilder errorStringBuilder;
|
||||
readonly StringBuilder combinedStringBuilder;
|
||||
@@ -65,6 +75,7 @@ namespace Tgstation.Server.Host.System
|
||||
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
emergencyLifetimeTcs = new TaskCompletionSource<object>();
|
||||
Lifetime = WrapLifetimeTask(lifetime ?? throw new ArgumentNullException(nameof(lifetime)));
|
||||
|
||||
Id = handle.Id;
|
||||
@@ -92,9 +103,12 @@ namespace Tgstation.Server.Host.System
|
||||
|
||||
async Task<int> WrapLifetimeTask(Task<int> lifetimeTask)
|
||||
{
|
||||
var result = await lifetimeTask.ConfigureAwait(false);
|
||||
logger.LogTrace("PID {0} ended with code {1}", Id, result);
|
||||
return result;
|
||||
await Task.WhenAny(lifetimeTask, emergencyLifetimeTcs.Task).ConfigureAwait(false);
|
||||
if (lifetimeTask.IsCompleted)
|
||||
return await lifetimeTask.ConfigureAwait(false);
|
||||
|
||||
logger.LogTrace("Using exit code -1 for hung PID {0}.", Id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -130,7 +144,15 @@ namespace Tgstation.Server.Host.System
|
||||
{
|
||||
logger.LogTrace("Terminating PID {0}...", Id);
|
||||
handle.Kill();
|
||||
handle.WaitForExit();
|
||||
if (!handle.WaitForExit(MaximumWaitMilliseconds))
|
||||
{
|
||||
logger.LogError(
|
||||
"PID {0} hasn't exited in {1} seconds! This may cause issues with port reuse.",
|
||||
Id,
|
||||
TimeSpan.FromMilliseconds(MaximumWaitMilliseconds).TotalSeconds);
|
||||
|
||||
emergencyLifetimeTcs.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// </summary>
|
||||
/// <param name="handle">The <see cref="global::System.Diagnostics.Process"/> to attach the <see cref="Task{TResult}"/> for</param>
|
||||
/// <returns>A new <see cref="Task{TResult}"/> resulting in the exit code of <paramref name="handle"/></returns>
|
||||
static Task<int> AttachExitHandler(global::System.Diagnostics.Process handle)
|
||||
Task<int> AttachExitHandler(global::System.Diagnostics.Process handle)
|
||||
{
|
||||
handle.EnableRaisingEvents = true;
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
@@ -45,7 +45,8 @@ namespace Tgstation.Server.Host.System
|
||||
}
|
||||
|
||||
// Try because this can be invoked twice for weird reasons
|
||||
tcs.TrySetResult(exitCode);
|
||||
if (tcs.TrySetResult(exitCode))
|
||||
logger.LogTrace("Process exit event completed");
|
||||
};
|
||||
|
||||
return tcs.Task;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../../build/Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -69,7 +69,7 @@
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Octokit" Version="0.48.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
|
||||
<!-- If this is updated, be sure to update the reference in the README.md -->
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
text2file("DMAPI version [TGS_DMAPI_VERSION] does not match active API version [active_version.raw_parameter]", "test_fail_reason.txt")
|
||||
|
||||
world.log << "sleep2"
|
||||
sleep(50)
|
||||
sleep(150)
|
||||
world.log << "Terminating..."
|
||||
world.TgsEndProcess()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -36,7 +36,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
if (!new PlatformIdentifier().IsWindows)
|
||||
await dreamMakerClient.Update(new DreamMaker
|
||||
{
|
||||
ProjectName = "tests/DMAPI/ApiFree/api_free"
|
||||
ProjectName = "tests/DMAPI/ApiFree/api_free",
|
||||
ApiValidationPort = IntegrationTest.DMPort
|
||||
}, cancellationToken);
|
||||
|
||||
var updatedDD = await dreamDaemonClient.Update(new DreamDaemon
|
||||
|
||||
@@ -36,7 +36,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
var initialSettings = await instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
{
|
||||
StartupTimeout = 30,
|
||||
HeartbeatSeconds = 0
|
||||
HeartbeatSeconds = 0,
|
||||
Port = IntegrationTest.DDPort
|
||||
}, cancellationToken);
|
||||
|
||||
await ApiAssert.ThrowsException<ApiConflictException>(() => instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
@@ -87,7 +88,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
KillDD(false);
|
||||
var job = await WaitForJob(await dumpTask, 10, true, null, cancellationToken);
|
||||
Assert.IsTrue(job.ErrorCode == ErrorCode.DreamDaemonOffline || job.ErrorCode == ErrorCode.GCoreFailure);
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(20), cancellationToken);
|
||||
|
||||
var ddStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, ddStatus.Status.Value);
|
||||
@@ -145,7 +146,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
{
|
||||
blockSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
|
||||
blockSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
|
||||
blockSocket.Bind(new IPEndPoint(IPAddress.Any, 1337));
|
||||
blockSocket.Bind(new IPEndPoint(IPAddress.Any, IntegrationTest.DDPort));
|
||||
startJob = await instanceClient.DreamDaemon.Start(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await WaitForJob(startJob, 20, true, ErrorCode.DreamDaemonPortInUse, cancellationToken);
|
||||
@@ -160,7 +161,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.AreEqual(false, daemonStatus.SoftRestart);
|
||||
Assert.AreEqual(false, daemonStatus.SoftShutdown);
|
||||
|
||||
await GracefulWatchdogShutdown(30, cancellationToken);
|
||||
await GracefulWatchdogShutdown(60, cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value);
|
||||
@@ -170,7 +171,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
async Task RunHeartbeatTest(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG HEARTBEAT TEST");
|
||||
System.Console.WriteLine("TEST: WATCHDOG HEARTBEAT TEST");
|
||||
// enable heartbeats
|
||||
await instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
{
|
||||
@@ -210,7 +211,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
await Task.WhenAny(ourProcessHandler.Lifetime, Task.Delay(TimeSpan.FromSeconds(20)));
|
||||
|
||||
var timeout = 10;
|
||||
var timeout = 20;
|
||||
do
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
@@ -260,9 +261,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id);
|
||||
Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel);
|
||||
|
||||
await TellWorldToReboot(cancellationToken);
|
||||
daemonStatus = await TellWorldToReboot(cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreNotEqual(initialCompileJob.Id, daemonStatus.ActiveCompileJob.Id);
|
||||
Assert.IsNull(daemonStatus.StagedCompileJob);
|
||||
|
||||
@@ -301,9 +301,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id);
|
||||
Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, newerCompileJob.MinimumSecurityLevel);
|
||||
|
||||
await TellWorldToReboot(cancellationToken);
|
||||
daemonStatus = await TellWorldToReboot(cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreNotEqual(initialCompileJob.Id, daemonStatus.ActiveCompileJob.Id);
|
||||
Assert.IsNull(daemonStatus.StagedCompileJob);
|
||||
|
||||
@@ -354,9 +353,8 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
Assert.AreEqual(true, daemonStatus.SoftRestart);
|
||||
|
||||
await TellWorldToReboot(cancellationToken);
|
||||
daemonStatus = await TellWorldToReboot(cancellationToken);
|
||||
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(versionToInstall, daemonStatus.ActiveCompileJob.ByondVersion);
|
||||
Assert.IsNull(daemonStatus.StagedCompileJob);
|
||||
|
||||
@@ -379,6 +377,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value);
|
||||
Assert.AreEqual(IntegrationTest.DDPort, daemonStatus.CurrentPort);
|
||||
|
||||
// The measure we use to test dream daemon startup doesn't work on linux currently
|
||||
if (new PlatformIdentifier().IsWindows)
|
||||
@@ -422,28 +421,45 @@ namespace Tgstation.Server.Tests.Instance
|
||||
return ddProc != null;
|
||||
}
|
||||
|
||||
async Task TellWorldToReboot(CancellationToken cancellationToken)
|
||||
async Task<DreamDaemon> TellWorldToReboot(CancellationToken cancellationToken)
|
||||
{
|
||||
var daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
var initialCompileJob = daemonStatus.ActiveCompileJob;
|
||||
|
||||
var bts = new TopicClient(new SocketParameters
|
||||
{
|
||||
SendTimeout = TimeSpan.FromSeconds(15),
|
||||
ReceiveTimeout = TimeSpan.FromSeconds(15),
|
||||
ConnectTimeout = TimeSpan.FromSeconds(15),
|
||||
DisconnectTimeout = TimeSpan.FromSeconds(15)
|
||||
SendTimeout = TimeSpan.FromSeconds(30),
|
||||
ReceiveTimeout = TimeSpan.FromSeconds(30),
|
||||
ConnectTimeout = TimeSpan.FromSeconds(30),
|
||||
DisconnectTimeout = TimeSpan.FromSeconds(30)
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: Sending world reboot topic...");
|
||||
var result = await bts.SendTopic(IPAddress.Loopback, "tgs_integration_test_special_tactics=1", 1337, cancellationToken);
|
||||
System.Console.WriteLine("TEST: Sending world reboot topic...");
|
||||
var result = await bts.SendTopic(IPAddress.Loopback, "tgs_integration_test_special_tactics=1", IntegrationTest.DDPort, cancellationToken);
|
||||
Assert.AreEqual("ack", result.StringData);
|
||||
|
||||
await Task.Delay(20000, cancellationToken);
|
||||
using (var tempCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||
using (tempCts.Token.Register(() => System.Console.WriteLine("TEST ERROR: Timeout in TellWorldToReboot!")))
|
||||
{
|
||||
tempCts.CancelAfter(TimeSpan.FromMinutes(1));
|
||||
var tempToken = tempCts.Token;
|
||||
|
||||
do
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
daemonStatus = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
}
|
||||
while (initialCompileJob.Id == daemonStatus.ActiveCompileJob.Id && !tempToken.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
return daemonStatus;
|
||||
}
|
||||
|
||||
async Task<DreamDaemon> DeployTestDme(string dmeName, DreamDaemonSecurity deploymentSecurity, bool requireApi, CancellationToken cancellationToken)
|
||||
|
||||
@@ -199,7 +199,11 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
using var server = new TestingServer();
|
||||
using var serverCts = new CancellationTokenSource();
|
||||
|
||||
const int MaximumTestMinutes = 20;
|
||||
using var hardTimeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaximumTestMinutes));
|
||||
var hardCancellationToken = hardTimeoutCancellationTokenSource.Token;
|
||||
using var serverCts = CancellationTokenSource.CreateLinkedTokenSource(hardCancellationToken);
|
||||
var cancellationToken = serverCts.Token;
|
||||
|
||||
TerminateAllDDs();
|
||||
@@ -259,8 +263,9 @@ namespace Tgstation.Server.Tests
|
||||
Assert.IsTrue(serverTask.IsCompleted);
|
||||
|
||||
// http bind test https://github.com/tgstation/tgstation-server/issues/1065
|
||||
using (var blockingSocket = new Socket(SocketType.Stream, ProtocolType.Tcp))
|
||||
if (new PlatformIdentifier().IsWindows)
|
||||
{
|
||||
using var blockingSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true);
|
||||
blockingSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
|
||||
blockingSocket.Bind(new IPEndPoint(IPAddress.Any, server.Url.Port));
|
||||
@@ -343,12 +348,12 @@ namespace Tgstation.Server.Tests
|
||||
.ToList();
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, jobs.Count);
|
||||
|
||||
var launchJob = jobs.Single();
|
||||
Assert.IsTrue(launchJob.StartedAt.Value >= preStartupTime);
|
||||
|
||||
await new JobsRequiredTest(instanceClient.Jobs).WaitForJob(launchJob, 40, false, null, cancellationToken);
|
||||
var jrt = new JobsRequiredTest(instanceClient.Jobs);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
Assert.IsTrue(job.StartedAt.Value >= preStartupTime);
|
||||
await jrt.WaitForJob(job, 40, false, null, cancellationToken);
|
||||
}
|
||||
|
||||
var dd = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
|
||||
@@ -374,12 +379,9 @@ namespace Tgstation.Server.Tests
|
||||
finally
|
||||
{
|
||||
serverCts.Cancel();
|
||||
|
||||
// Give the test 1 minute to cleanup
|
||||
using var hardTimeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1));
|
||||
try
|
||||
{
|
||||
await serverTask.WithToken(hardTimeoutCancellationTokenSource.Token).ConfigureAwait(false);
|
||||
await serverTask.WithToken(hardCancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
|
||||
@@ -390,6 +392,33 @@ namespace Tgstation.Server.Tests
|
||||
await serverTask;
|
||||
}
|
||||
|
||||
public static ushort DDPort = FreeTcpPort();
|
||||
public static ushort DMPort = GetDMPort();
|
||||
|
||||
static ushort GetDMPort()
|
||||
{
|
||||
ushort result;
|
||||
do
|
||||
{
|
||||
result = FreeTcpPort();
|
||||
} while (result == DDPort);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ushort FreeTcpPort()
|
||||
{
|
||||
var l = new TcpListener(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
try
|
||||
{
|
||||
return (ushort)((IPEndPoint)l.LocalEndpoint).Port;
|
||||
}
|
||||
finally
|
||||
{
|
||||
l.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestScriptExecution()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user