From 534c65453b4b6e58fd213265ceacd1a31d5e9558 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 10:48:01 -0400 Subject: [PATCH 01/22] This does nothing --- .../Components/Chat/Providers/IrcProvider.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 34fb84c64c..1e4f39d80d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -280,9 +280,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } client.Listen(false); - if (client.Nickname != nickname) - //run this now cause it has to go up and down the pipe for us to get a proper check - client.GetIrcUser(nickname); listenTask = Task.Factory.StartNew(() => { From 58512caa525cd619ade6b5b56ce1b68c9f74473c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 10:53:08 -0400 Subject: [PATCH 02/22] Update Byond.TopicSender to --- src/Tgstation.Server.Host/Tgstation.Server.Host.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index b25b2b43de..fa8a4573ea 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -20,7 +20,7 @@ - + From 530af5f7b4216a35377aeb798baa3f3c6a5ddf17 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 11:03:35 -0400 Subject: [PATCH 03/22] Fixes byond command semantics --- .../Components/Chat/Commands/ByondCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs index e9cb16011b..e707e1f46c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/ByondCommand.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands public string Name => "byond"; /// - public string HelpText => "Displays the installed Byond version"; + public string HelpText => "Displays the active Byond version"; /// public bool AdminOnly => false; From bd889af82c0a9bc23bc3252b4dd0125fa01fc311 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 11:07:45 -0400 Subject: [PATCH 04/22] Fix PullRequestsCommand typo --- .../Components/Chat/Commands/PullRequestsCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs index f3e3233c9c..90db91a969 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs @@ -92,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands if (!results.Any()) return "None!"; - return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} as {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); + return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); } } } From a1eb9622e0e47277e733495c1f895e667a42023c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 11:07:55 -0400 Subject: [PATCH 05/22] Add revision command --- .../Chat/Commands/CommandFactory.cs | 1 + .../Chat/Commands/RevisionCommand.cs | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs index 5f8acb3c5d..4841366c5a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/CommandFactory.cs @@ -77,6 +77,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands { new VersionCommand(application), new ByondCommand(byondManager), + new RevisionCommand(watchdog, repositoryManager, instance), new PullRequestsCommand(watchdog, repositoryManager, databaseContextFactory, instance), new KekCommand() }; diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs new file mode 100644 index 0000000000..4212c0459d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/RevisionCommand.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Components.Repository; +using Tgstation.Server.Host.Components.Watchdog; + +namespace Tgstation.Server.Host.Components.Chat.Commands +{ + /// + /// For displaying + /// + sealed class RevisionCommand : ICommand + { + /// + public string Name => "revision"; + + /// + public string HelpText => "Display live commit sha. Add --repo to view repository revision"; + + /// + public bool AdminOnly => false; + + /// + /// The for the + /// + readonly IWatchdog watchdog; + + /// + /// The for the + /// + readonly IRepositoryManager repositoryManager; + + /// + /// The for the + /// + readonly Models.Instance instance; + + /// + /// Construct a + /// + /// The value of + /// The value of + /// The value of + public RevisionCommand(IWatchdog watchdog, IRepositoryManager repositoryManager, Models.Instance instance) + { + this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog)); + this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager)); + this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); + } + + /// + public async Task Invoke(string arguments, User user, CancellationToken cancellationToken) + { + string result; + if (arguments.Split(' ').Any(x => x.ToUpperInvariant() == "--REPO")) + using (var repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false)) + { + if (repo == null) + return "Repository unavailable!"; + result = repo.Head; + } + else + { + if (!watchdog.Running) + return "Server offline!"; + result = watchdog.ActiveCompileJob?.RevisionInformation.CommitSha; + } + + return String.Format(CultureInfo.InvariantCulture, "^{0}", result); + } + } +} From 27971d773253593ef183e6bbf23b2acfddd79fa5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 11:43:01 -0400 Subject: [PATCH 06/22] Limit the rev info that gets put in the interop json --- .../Components/Watchdog/SessionControllerFactory.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 92d109e28b..df4cedfbfb 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -139,7 +139,11 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatCommandsJson = JsonFile("chat_commands"), ServerCommandsJson = JsonFile("server_commands"), InstanceName = instance.Name, - Revision = dmbProvider.CompileJob.RevisionInformation + Revision = new Api.Models.Internal.RevisionInformation + { + CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, + OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha + } }; interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x))); From d26c3c6f1ef2afe6a0a6662e36879778ad698225 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 11:46:28 -0400 Subject: [PATCH 07/22] Same for test merges --- src/Tgstation.Server.Host/Components/Interop/TestMerge.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs index 9f2b4e247f..41dd1e6e26 100644 --- a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs +++ b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs @@ -24,7 +24,11 @@ namespace Tgstation.Server.Host.Components.Interop public TestMerge(Models.TestMerge testMerge) : base(testMerge) { TimeMerged = testMerge.MergedAt.Ticks; - Revision = testMerge.PrimaryRevisionInformation; + Revision = new RevisionInformation + { + CommitSha = testMerge.PrimaryRevisionInformation.CommitSha, + OriginCommitSha = testMerge.PrimaryRevisionInformation.OriginCommitSha + }; } } } From cb1aa00881a497205d72836f80efb18161da4ab9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 13:06:09 -0400 Subject: [PATCH 08/22] Fix some OnWorldNew errors --- src/DMAPI/tgs/v4/api.dm | 43 ++++++++++--------- .../Components/Compiler/DreamMaker.cs | 5 ++- .../Components/Interop/TestMerge.cs | 7 +-- .../Components/Watchdog/LaunchResult.cs | 6 +-- .../Components/Watchdog/SessionController.cs | 13 ++++-- .../Watchdog/SessionControllerFactory.cs | 16 ++++--- .../Components/Watchdog/Watchdog.cs | 29 ++++++++----- 7 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 40da59cb0f..52970f2110 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -72,7 +72,7 @@ TGS_INFO_LOG("Validating API and exiting...") Export(TGS4_COMM_VALIDATE) del(world) - + chat_channels_json_path = cached_json["chatChannelsJson"] chat_commands_json_path = cached_json["chatCommandsJson"] src.event_handler = event_handler @@ -80,31 +80,34 @@ ListCustomCommands() - . = TRUE - var/list/revisionData = cached_json["revision"] - if(!revisionData) - return - - cached_revision = new - cached_revision.commit = revisionData["commitSha"] - cached_revision.origin_commit = revisionData["originCommitSha"] + if(revisionData) + cached_revision = new + cached_revision.commit = revisionData["commitSha"] + cached_revision.origin_commit = revisionData["originCommitSha"] cached_test_merges = list() - var/json = cached_json["testMerges"] - for(var/I in json) + var/list/json = cached_json["testMerges"] + for(var/entry in json) var/datum/tgs_revision_information/test_merge/tm = new - tm.number = text2num(I) - var/list/entry = json[I] - tm.pull_request_commit = entry["pullRequestRevision"] - tm.author = entry["author"] - tm.title = entry["title"] - var/list/revInfo = entry["revision"] - tm.commit = revInfo["commitSha"] - tm.origin_commit = entry["originCommitSha"] tm.time_merged = text2num(entry["timeMerged"]) - tm.comment = entry["comment"] + + var/list/revInfo = entry["revision"] + if(revInfo) + tm.commit = revInfo["commitSha"] + tm.origin_commit = revInfo["originCommitSha"] + + tm.title = entry["titleAtMerge"] + tm.body = entry["bodyAtMerge"] tm.url = entry["url"] + tm.author = entry["author"] + tm.number = entry["number"] + tm.pull_request_commit = entry["pullRequestRevision"] + tm.comment = entry["comment"] + + cached_test_merges += tm + + return TRUE /datum/tgs_api/v4/OnInitializationComplete() Export(TGS4_COMM_SERVER_PRIMED) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 38e87fa118..17a66e4ed2 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -135,14 +135,17 @@ namespace Tgstation.Server.Host.Components.Compiler var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false)) { + var launchResult = await controller.LaunchResult.ConfigureAwait(false); + var now = DateTimeOffset.Now; - if (now < timeoutAt) + if (now < timeoutAt && launchResult.StartupTime.HasValue) { var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken); await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } + if (!controller.Lifetime.IsCompleted) { logger.LogDebug("API validation timed out!"); diff --git a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs index 41dd1e6e26..71c21a55bc 100644 --- a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs +++ b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs @@ -1,4 +1,5 @@ -using Tgstation.Server.Api.Models.Internal; +using System.Globalization; +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Interop { @@ -10,7 +11,7 @@ namespace Tgstation.Server.Host.Components.Interop /// /// The unix time of when the test merge was applied /// - public long TimeMerged { get; set; } + public string TimeMerged { get; set; } /// /// The of the @@ -23,7 +24,7 @@ namespace Tgstation.Server.Host.Components.Interop /// The to build from public TestMerge(Models.TestMerge testMerge) : base(testMerge) { - TimeMerged = testMerge.MergedAt.Ticks; + TimeMerged = testMerge.MergedAt.Ticks.ToString(CultureInfo.InvariantCulture); Revision = new RevisionInformation { CommitSha = testMerge.PrimaryRevisionInformation.CommitSha, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs index 81b2be9fb9..feefa00201 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/LaunchResult.cs @@ -9,9 +9,9 @@ namespace Tgstation.Server.Host.Components.Watchdog public sealed class LaunchResult { /// - /// The time it took for to return + /// The time it took for to return. If the startup timed out /// - public TimeSpan StartupTime { get; set; } + public TimeSpan? StartupTime { get; set; } /// /// The if it exited @@ -19,6 +19,6 @@ namespace Tgstation.Server.Host.Components.Watchdog public int? ExitCode { get; set; } /// - public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}, Time {1}ms", ExitCode, StartupTime.TotalMilliseconds); + public override string ToString() => String.Format(CultureInfo.InvariantCulture, "Exit Code: {0}, Time {1}ms", ExitCode, StartupTime?.TotalMilliseconds); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 2b11617c11..fde13f58c2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -167,7 +167,8 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger) + /// The optional time to wait before failing the + public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger logger, uint? startupTimeout) { this.chatJsonTrackingContext = chatJsonTrackingContext; //null valid this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); @@ -190,11 +191,17 @@ namespace Tgstation.Server.Host.Components.Watchdog async Task GetLaunchResult() { var startTime = DateTimeOffset.Now; - await process.Startup.ConfigureAwait(false); + Task toAwait = process.Startup; + + if (startupTimeout.HasValue) + toAwait = Task.WhenAny(process.Startup, Task.Delay(startTime.AddSeconds(startupTimeout.Value) - startTime)); + + await toAwait.ConfigureAwait(false); + var result = new LaunchResult { ExitCode = process.Lifetime.IsCompleted ? (int?)await process.Lifetime.ConfigureAwait(false) : null, - StartupTime = DateTimeOffset.Now - startTime + StartupTime = process.Startup.IsCompleted ? (TimeSpan?)(DateTimeOffset.Now - startTime) : null }; return result; }; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index df4cedfbfb..363a068bac 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -131,6 +131,7 @@ namespace Tgstation.Server.Host.Components.Watchdog //i changed this back from guids, hopefully i don't regret that string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix); + //setup interop files var interopInfo = new JsonFile { AccessIdentifier = accessIdentifier, @@ -167,15 +168,18 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { + //get the byond lock var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { - //more sanitization here cause it uses the same scheme - var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); - + //create interop context var context = new CommContext(ioManager, loggerFactory.CreateLogger(), basePath, interopInfo.ServerCommandsJson); try { + //set command line options + //more sanitization here cause it uses the same scheme + var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); + var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} {2}-close -{3} -verbose -public -params \"{4}\"", dmbProvider.DmbName, primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, @@ -183,9 +187,11 @@ namespace Tgstation.Server.Host.Components.Watchdog SecurityWord(launchParameters.SecurityLevel.Value), parameters); + //launch dd var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments); try { + //return the session controller for it return new SessionController(new ReattachInformation { AccessIdentifier = accessIdentifier, @@ -196,7 +202,7 @@ namespace Tgstation.Server.Host.Components.Watchdog ChatChannelsJson = interopInfo.ChatChannelsJson, ChatCommandsJson = interopInfo.ChatCommandsJson, ServerCommandsJson = interopInfo.ServerCommandsJson, - }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger()); + }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), launchParameters.StartupTimeout); } catch { @@ -243,7 +249,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var process = processExecutor.GetProcess(reattachInformation.ProcessId); try { - return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger()); + return new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), null); } catch { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index be572dc73d..0cfd9abcb0 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -20,9 +20,9 @@ namespace Tgstation.Server.Host.Components.Watchdog sealed class Watchdog : IWatchdog, ICustomCommandHandler { /// - /// The time in milliseconds to wait from starting to start . Does not take responsiveness into account + /// The time in seconds to wait from starting to start . Does not take responsiveness into account /// - const int AlphaBravoStartupSeperationInterval = 3000; + const int AlphaBravoStartupSeperationInterval = 3; /// public bool Running { get; private set; } @@ -655,32 +655,41 @@ namespace Tgstation.Server.Host.Components.Watchdog var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null; var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2); - Task alphaServerTask = null; try { + Task alphaServerTask; if (!doesntNeedNewDmb) alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, alphaStartCts.Token); else alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); - //do a few seconds of delay so that any backends the servers use know that alpha came first - await Task.Delay(AlphaBravoStartupSeperationInterval, cancellationToken).ConfigureAwait(false); + + //wait until this boy officially starts so as not to confuse the servers as to who came first + var startTime = DateTimeOffset.Now; + alphaServer = await alphaServerTask.ConfigureAwait(false); + + //extra delay for total ordering + var now = DateTimeOffset.Now; + var delay = now - startTime; + + if (delay.TotalSeconds < AlphaBravoStartupSeperationInterval) + await Task.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false); + Task bravoServerTask; if (!doesntNeedNewDmb) bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken); else bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); - await Task.WhenAll(alphaServerTask, bravoServerTask).ConfigureAwait(false); - - alphaServer = alphaServerTask.Result; - bravoServer = bravoServerTask.Result; - + bravoServer = await bravoServerTask.ConfigureAwait(false); + async Task CheckLaunch(ISessionController controller, string serverName) { var launch = await controller.LaunchResult.ConfigureAwait(false); if (launch.ExitCode.HasValue) //you killed us ray... throw new Exception(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); + if (!launch.StartupTime.HasValue) + throw new Exception(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value)); return launch; } From 78ffbd9655639c30e2a39962b7cf57f62545f5df Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 13:40:36 -0400 Subject: [PATCH 09/22] More test merging optimizations --- .../Controllers/RepositoryController.cs | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index beac22b255..e499498481 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -412,6 +412,7 @@ namespace Tgstation.Server.Host.Controllers var repoName = repo.GitHubRepoName; Models.RevisionInformation revInfoWereLookingFor = null; + bool needToApplyRemainingPrs = true; //optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha) { @@ -445,20 +446,65 @@ namespace Tgstation.Server.Host.Controllers if (!cantSearch) { var dbPull = await databaseContext.RevisionInformations - .Where(x => x.Instance.Id == Instance.Id - && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha - && x.ActiveTestMerges.Count == model.NewTestMerges.Count) + .Where(x => x.Instance.Id == Instance.Id + && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha + && x.ActiveTestMerges.Count <= model.NewTestMerges.Count + && x.ActiveTestMerges.Count > 0) .Include(x => x.ActiveTestMerges) .ThenInclude(x => x.TestMerge) .ToListAsync(cancellationToken).ConfigureAwait(false); + //split here cause this bit has to be done locally revInfoWereLookingFor = dbPull - .Where(x => x.ActiveTestMerges.Select(y => y.TestMerge) - .All(y => model.NewTestMerges.Any(z => - y.Number == z.Number - && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal) - && y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))) + .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count + && x.ActiveTestMerges.Select(y => y.TestMerge) + .All(y => model.NewTestMerges.Any(z => + y.Number == z.Number + && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal) + && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null)))) .FirstOrDefault(); + + if (revInfoWereLookingFor == null && model.NewTestMerges.Count > 1) + { + //okay try to add at least SOME prs we've seen before + var search = model.NewTestMerges.ToList(); + search.Reverse(); //reverse order, document the optimization in the api so clients know how to cache hit + + var appliedTestMergeIds = new List(); + + Models.RevisionInformation lastGoodRevInfo = null; + do + { + foreach (var I in search) + { + revInfoWereLookingFor = dbPull + .Where(x => model.NewTestMerges.Any(z => + x.PrimaryTestMerge.Number == z.Number + && x.PrimaryTestMerge.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal) + && (x.PrimaryTestMerge.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null)) + && x.ActiveTestMerges.Select(y => y.TestMerge).All(y => appliedTestMergeIds.Contains(y.Id))) + .FirstOrDefault(); + + if (revInfoWereLookingFor != null) + { + lastGoodRevInfo = revInfoWereLookingFor; + appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id); + search.Remove(I); + break; + } + } + } while (revInfoWereLookingFor != null && search.Count > 0); + + revInfoWereLookingFor = lastGoodRevInfo; + needToApplyRemainingPrs = search.Count != 0; + if (needToApplyRemainingPrs) + { + search.Reverse(); + model.NewTestMerges = search; + } + } + else if (revInfoWereLookingFor != null) + needToApplyRemainingPrs = false; } } @@ -468,7 +514,8 @@ namespace Tgstation.Server.Host.Controllers await repo.ResetToSha(revInfoWereLookingFor.CommitSha, cancellationToken).ConfigureAwait(false); lastRevisionInfo = revInfoWereLookingFor; } - else + + if(needToApplyRemainingPrs) { var contextUser = new Models.User { From 8a4805caa423325c4c92caa0437bb0f58645e0bc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 13:46:20 -0400 Subject: [PATCH 10/22] Reading the CompileJob from DM controller is redundant with DD controller --- src/Tgstation.Server.Api/Models/DreamMaker.cs | 6 ------ .../Controllers/DreamMakerController.cs | 6 ++---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs index f50fb7ac64..121d4b2d52 100644 --- a/src/Tgstation.Server.Api/Models/DreamMaker.cs +++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs @@ -7,12 +7,6 @@ namespace Tgstation.Server.Api.Models /// public sealed class DreamMaker : DreamMakerSettings { - /// - /// The last ran - /// - [Permissions(DenyWrite = true)] - public CompileJob LastJob { get; set; } - /// /// The of the compiler /// diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index fa46db3581..657e7fe3dc 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -49,12 +49,10 @@ namespace Tgstation.Server.Host.Controllers public override async Task Read(CancellationToken cancellationToken) { var instance = instanceManager.GetInstance(Instance); - var projectNameTask = DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).Select(x => x.ProjectName).FirstOrDefaultAsync(cancellationToken); - var job = await DatabaseContext.CompileJobs.OrderByDescending(x => x.Job.StartedAt).Include(x => x.Job).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + var projectName = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).Select(x => x.ProjectName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); return Json(new Api.Models.DreamMaker { - LastJob = job?.ToApi(), - ProjectName = await projectNameTask.ConfigureAwait(false), + ProjectName = projectName, Status = instance.DreamMaker.Status }); } From e4e8a78c33f5f20590b7705425547c8a5005defc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 13:52:47 -0400 Subject: [PATCH 11/22] Various things: Remove leading whitespace in Process output IDreamMaker.Compile now only returns successful CompileJobs Added a todo about JobExceptions --- .../Components/Compiler/DreamMaker.cs | 14 +++----------- src/Tgstation.Server.Host/Core/Process.cs | 6 +++--- v4_prototype_TODO.txt | 2 ++ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 17a66e4ed2..121d2b2871 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -176,7 +176,7 @@ namespace Tgstation.Server.Host.Components.Compiler logger.LogDebug("DreamMaker exit code: {0}", exitCode); job.Output = dm.GetCombinedOutput(); - logger.LogTrace("DreamMaker output: {0}", job.Output); + logger.LogTrace("DreamMaker output: {0}{1}", Environment.NewLine, job.Output); return exitCode; } } @@ -263,11 +263,7 @@ namespace Tgstation.Server.Host.Components.Compiler lock (this) { if (Status != CompilerStatus.Idle) - { - job.Output = "There is already a compile in progress!"; - logger.LogInformation(job.Output); - return job; - } + throw new Exception("There is already a compile in progress!"); Status = CompilerStatus.Copying; } @@ -340,11 +336,7 @@ namespace Tgstation.Server.Host.Components.Compiler logger.LogTrace("Searching for available .dmes..."); var path = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); if (path == default) - { - job.Output = "Unable to find any .dme!"; - logger.LogWarning(job.Output); - return job; - } + throw new Exception("Unable to find any .dme!"); var dmeWithExtension = ioManager.GetFileName(path); job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1); } diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index 45750daeaa..cd869ee83a 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Core { if (combinedStringBuilder == null) throw new InvalidOperationException("Output/Error reading was not enabled!"); - return combinedStringBuilder.ToString(); + return combinedStringBuilder.ToString().TrimStart(Environment.NewLine.ToCharArray()); } /// @@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Core { if (errorStringBuilder == null) throw new InvalidOperationException("Error reading was not enabled!"); - return errorStringBuilder.ToString(); + return errorStringBuilder.ToString().TrimStart(Environment.NewLine.ToCharArray()); } /// @@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Core { if (outputStringBuilder == null) throw new InvalidOperationException("Output reading was not enabled!"); - return errorStringBuilder.ToString(); + return errorStringBuilder.ToString().TrimStart(Environment.NewLine.ToCharArray()); } /// diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 572d75b123..b2ea1bbeca 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -1,3 +1,5 @@ Verify the byond cache folder location on linux Test watchdog + +Add a JobException type that the job manager will just print the message of. Replace throw new Exception()s with it From 4d1c29c1cb8cc9003691114e9e9982c036fbf5fc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 14:01:44 -0400 Subject: [PATCH 12/22] Add JobException to avoid printing needless stack traces for normal operation errors --- .../Components/Byond/WindowsByondInstaller.cs | 2 +- .../Components/Compiler/DreamMaker.cs | 6 ++-- .../Components/Repository/Repository.cs | 2 +- .../Components/Watchdog/Watchdog.cs | 6 ++-- .../Controllers/RepositoryController.cs | 14 ++++---- .../Core/JobException.cs | 34 +++++++++++++++++++ src/Tgstation.Server.Host/Core/JobManager.cs | 2 +- v4_prototype_TODO.txt | 2 -- 8 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 src/Tgstation.Server.Host/Core/JobException.cs diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index 6530a0cc4e..a17aa4eece 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -134,7 +134,7 @@ namespace Tgstation.Server.Host.Components.Byond cancellationToken.ThrowIfCancellationRequested(); if (exitCode != 0) - throw new Exception(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode)); + throw new JobException(String.Format(CultureInfo.InvariantCulture, "Failed to install included DirectX! Exit code: {0}", exitCode)); installedDirectX = true; } } diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs index 121d2b2871..c88bc67e91 100644 --- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs @@ -263,7 +263,7 @@ namespace Tgstation.Server.Host.Components.Compiler lock (this) { if (Status != CompilerStatus.Idle) - throw new Exception("There is already a compile in progress!"); + throw new JobException("There is already a compile in progress!"); Status = CompilerStatus.Copying; } @@ -336,7 +336,7 @@ namespace Tgstation.Server.Host.Components.Compiler logger.LogTrace("Searching for available .dmes..."); var path = (await ioManager.GetFilesWithExtension(dirA, DmeExtension, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); if (path == default) - throw new Exception("Unable to find any .dme!"); + throw new JobException("Unable to find any .dme!"); var dmeWithExtension = ioManager.GetFileName(path); job.DmeName = dmeWithExtension.Substring(0, dmeWithExtension.Length - DmeExtension.Length - 1); } @@ -364,7 +364,7 @@ namespace Tgstation.Server.Host.Components.Compiler { //server never validated or compile failed await eventConsumer.HandleEvent(EventType.CompileFailure, new List { resolvedGameDirectory, exitCode == 0 ? "1" : "0" }, cancellationToken).ConfigureAwait(false); - throw new Exception(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}", exitCode, job.Output)); + throw new JobException(exitCode == 0 ? "Validation of the TGS api failed!" : String.Format(CultureInfo.InvariantCulture, "DM exited with a non-zero code: {0}{1}", exitCode, job.Output)); } logger.LogTrace("Running post compile event..."); diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 2879581610..a792cfa9bc 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Repository public string GitHubRepoName { get; } /// - public bool Tracking => repository.Head.IsTracking; + public bool Tracking => Reference != null && repository.Head.IsTracking; /// public string Head => repository.Head.Tip.Sha; diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 0cfd9abcb0..70aabb3302 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -320,7 +320,7 @@ namespace Tgstation.Server.Host.Components.Watchdog if (dmbBackup == null) //NANI!? //just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has - throw new Exception("Creating backup DMB provider failed!"); + throw new JobException("Creating backup DMB provider failed!"); monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); usedMostRecentDmb = false; @@ -687,9 +687,9 @@ namespace Tgstation.Server.Host.Components.Watchdog var launch = await controller.LaunchResult.ConfigureAwait(false); if (launch.ExitCode.HasValue) //you killed us ray... - throw new Exception(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); + throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); if (!launch.StartupTime.HasValue) - throw new Exception(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value)); + throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value)); return launch; } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index e499498481..efb0ec9328 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -162,7 +162,7 @@ namespace Tgstation.Server.Host.Controllers using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, cancellationToken).ConfigureAwait(false)) { if (repos == null) - throw new Exception("Filesystem conflict while cloning repository!"); + throw new JobException("Filesystem conflict while cloning repository!"); var db = serviceProvider.GetRequiredService(); if (await PopulateApi(api, repos, db, Instance, null, null, cancellationToken).ConfigureAwait(false)) await db.Save(cancellationToken).ConfigureAwait(false); @@ -315,7 +315,7 @@ namespace Tgstation.Server.Host.Controllers using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(ct).ConfigureAwait(false)) { if (repo == null) - throw new InvalidOperationException("Repository could not be loaded!"); + throw new JobException("Repository could not be loaded!"); var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null; @@ -323,7 +323,7 @@ namespace Tgstation.Server.Host.Controllers var startSha = repo.Head; if (newTestMerges && !repo.IsGitHubRepository) - throw new InvalidOperationException("Cannot test merge on a non GitHub based repository!"); + throw new JobException("Cannot test merge on a non GitHub based repository!"); var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName; @@ -357,15 +357,15 @@ namespace Tgstation.Server.Host.Controllers //fetch/pull if (model.UpdateFromOrigin == true) { - if (!repo.Tracking && model.Reference == null) - throw new InvalidOperationException("Not on an updatable reference!"); + if (!repo.Tracking) + throw new JobException("Not on an updatable reference!"); await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, x => progressReporter(x / numFetches), ct).ConfigureAwait(false); doneFetches = 1; if (!modelHasShaOrReference) { var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, ct).ConfigureAwait(false); if (!fastForward.HasValue) - throw new InvalidOperationException("Merge conflict occurred during origin update!"); + throw new JobException("Merge conflict occurred during origin update!"); await UpdateRevInfo().ConfigureAwait(false); if (fastForward.Value) { @@ -388,7 +388,7 @@ namespace Tgstation.Server.Host.Controllers if (model.UpdateFromOrigin == true && model.Reference != null) { if (!repo.Tracking) - throw new InvalidOperationException("Checked out reference does not track a remote object!"); + throw new JobException("Checked out reference does not track a remote object!"); await repo.ResetToOrigin(ct).ConfigureAwait(false); await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, true, ct).ConfigureAwait(false); await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Core/JobException.cs b/src/Tgstation.Server.Host/Core/JobException.cs new file mode 100644 index 0000000000..88877c9548 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/JobException.cs @@ -0,0 +1,34 @@ +using System; + +namespace Tgstation.Server.Host +{ + /// + /// Operation exceptions thrown from the context of a + /// + public sealed class JobException : Exception + { + /// + /// Construct a + /// + public JobException() + { + } + + /// + /// Construct a with a + /// + /// The message for the + public JobException(string message) : base(message) + { + } + + /// + /// Construct a with a and + /// + /// The message for the + /// The inner for the nase + public JobException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index e4e13aa4d9..4a9b60419f 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Core catch (Exception e) { logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, e); - job.ExceptionDetails = e.ToString(); + job.ExceptionDetails = e is JobException ? e.Message : e.ToString(); } job.StoppedAt = DateTimeOffset.Now; await databaseContext.Save(default).ConfigureAwait(false); diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index b2ea1bbeca..572d75b123 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -1,5 +1,3 @@ Verify the byond cache folder location on linux Test watchdog - -Add a JobException type that the job manager will just print the message of. Replace throw new Exception()s with it From bef79e63a8d3dd6d2137809abd9952a84c663538 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 14:13:35 -0400 Subject: [PATCH 13/22] Fix interop TestMerge contructor issues --- .../Components/Interop/TestMerge.cs | 12 +++++------- .../Components/Watchdog/SessionControllerFactory.cs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs index 71c21a55bc..9e8b795b53 100644 --- a/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs +++ b/src/Tgstation.Server.Host/Components/Interop/TestMerge.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Interop @@ -22,14 +23,11 @@ namespace Tgstation.Server.Host.Components.Interop /// Construct a /// /// The to build from - public TestMerge(Models.TestMerge testMerge) : base(testMerge) + /// The value of + public TestMerge(Models.TestMerge testMerge, RevisionInformation revision) : base(testMerge) { TimeMerged = testMerge.MergedAt.Ticks.ToString(CultureInfo.InvariantCulture); - Revision = new RevisionInformation - { - CommitSha = testMerge.PrimaryRevisionInformation.CommitSha, - OriginCommitSha = testMerge.PrimaryRevisionInformation.OriginCommitSha - }; + Revision = revision ?? throw new ArgumentNullException(nameof(revision)); } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 363a068bac..d1de6c468a 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -147,7 +147,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } }; - interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x))); + interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x, interopInfo.Revision))); var interopJsonFile = JsonFile("interop"); From 0913e070e664518bbbd9590b31c7dc8f5a22269c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 14:26:02 -0400 Subject: [PATCH 14/22] Reversing here doesn't make sense actually --- src/Tgstation.Server.Host/Controllers/RepositoryController.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index efb0ec9328..77e7cfb95e 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -468,7 +468,6 @@ namespace Tgstation.Server.Host.Controllers { //okay try to add at least SOME prs we've seen before var search = model.NewTestMerges.ToList(); - search.Reverse(); //reverse order, document the optimization in the api so clients know how to cache hit var appliedTestMergeIds = new List(); @@ -498,10 +497,7 @@ namespace Tgstation.Server.Host.Controllers revInfoWereLookingFor = lastGoodRevInfo; needToApplyRemainingPrs = search.Count != 0; if (needToApplyRemainingPrs) - { - search.Reverse(); model.NewTestMerges = search; - } } else if (revInfoWereLookingFor != null) needToApplyRemainingPrs = false; From 1a455cc7d9e97071841ac5c704a9da2e00708fca Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 14:44:20 -0400 Subject: [PATCH 15/22] Move Running = false to DisposeAndNullControllers --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 70aabb3302..c9a8813395 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -187,6 +187,7 @@ namespace Tgstation.Server.Host.Components.Watchdog alphaServer = null; bravoServer?.Dispose(); bravoServer = null; + Running = false; } /// @@ -232,7 +233,6 @@ namespace Tgstation.Server.Host.Components.Watchdog var chatTask = announce ? chat.SendWatchdogMessage("Terminating...", cancellationToken) : Task.CompletedTask; await StopMonitor().ConfigureAwait(false); DisposeAndNullControllers(); - Running = false; await chatTask.ConfigureAwait(false); return; } @@ -411,7 +411,6 @@ namespace Tgstation.Server.Host.Components.Watchdog case Components.Watchdog.RebootState.Shutdown: await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); DisposeAndNullControllers(); - Running = false; monitorState.NextAction = MonitorAction.Exit; return; } @@ -626,9 +625,9 @@ namespace Tgstation.Server.Host.Components.Watchdog async Task LaunchNoLock(bool startMonitor, bool announce, bool doReattach, CancellationToken cancellationToken) { + logger.LogTrace("Begin LaunchNoLock"); using (var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { - logger.LogTrace("Begin LaunchNoLock"); if (Running) { logger.LogTrace("Aborted due to already running!"); From fbd44f2823072cb89b41ea072e3303384d02a7f8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 14:57:20 -0400 Subject: [PATCH 16/22] Send watchdog events in a better fashion --- src/DMAPI/tgs/v4/api.dm | 10 +++++++++- .../Components/Interop/EventNotification.cs | 20 +++++++++++++++++++ .../Components/Watchdog/Watchdog.cs | 14 +++++++------ 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Interop/EventNotification.cs diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 52970f2110..cde747ba1f 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -141,7 +141,15 @@ return result if(TGS4_TOPIC_EVENT) intercepted_message_queue = list() - event_handler.HandleEvent(text2num(params[TGS4_PARAMETER_DATA])) + var/list/event_notification = json_decode(params[TGS4_PARAMETER_DATA]) + var/list/event_parameters = event_notification["Parameters"] + + var/list/event_call = list(event_notification["Type"]) + if(event_parameters) + event_call += event_parameters + + event_handler.HandleEvent(arglist(event_call)) + . = json_encode(intercepted_message_queue) intercepted_message_queue = null return diff --git a/src/Tgstation.Server.Host/Components/Interop/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/EventNotification.cs new file mode 100644 index 0000000000..9c9811808d --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Interop/EventNotification.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Components.Interop +{ + /// + /// For notifying DD of s + /// + sealed class EventNotification + { + /// + /// The + /// + public EventType Type { get; set; } + + /// + /// The event parameters + /// + public IEnumerable Parameters { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index c9a8813395..5d35904214 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -801,12 +801,14 @@ namespace Tgstation.Server.Host.Components.Watchdog return true; var builder = new StringBuilder(Constants.DMTopicEvent); - if (parameters != null) - foreach (var I in parameters) - { - builder.Append("&"); - builder.Append(byondTopicSender.SanitizeString(I)); - } + builder.Append("&"); + var notification = new EventNotification + { + Type = eventType, + Parameters = parameters + }; + var json = JsonConvert.SerializeObject(notification); + builder.Append(byondTopicSender.SanitizeString(json)); var activeServer = AlphaIsActive ? alphaServer : bravoServer; results = await activeServer.SendCommand(builder.ToString(), cancellationToken).ConfigureAwait(false); From 5f44fa77bae42b3c84506a9a6ab20455da708ae9 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 15:09:39 -0400 Subject: [PATCH 17/22] Add JsonIgnore to MonitorState ISessionControllers --- .../Components/Watchdog/MonitorState.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs index 367c2c3e80..a4cd7172cd 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorState.cs @@ -1,16 +1,42 @@ -namespace Tgstation.Server.Host.Components.Watchdog +using Newtonsoft.Json; + +namespace Tgstation.Server.Host.Components.Watchdog { + /// + /// The (absolute) state of the + /// sealed class MonitorState { + /// + /// If the inactive server is being rebooted + /// public bool RebootingInactiveServer { get; set; } + /// + /// If the inactive server has a .dmb and needs to be swapped in + /// public bool InactiveServerHasStagedDmb { get; set; } + /// + /// If the inactive server is in an unrecoverable state + /// public bool InactiveServerCritFail { get; set; } + /// + /// The next to take in + /// public MonitorAction NextAction { get; set; } + /// + /// The active + /// + [JsonIgnore] public ISessionController ActiveServer { get; set; } + + /// + /// The inactive + /// + [JsonIgnore] public ISessionController InactiveServer { get; set; } } } From dab27eadc5f868f037e000fb1e80951e5462010d Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 15:13:57 -0400 Subject: [PATCH 18/22] Does the process priority thing for the Watchdog --- .../Components/Watchdog/SessionController.cs | 3 +++ .../Components/Watchdog/Watchdog.cs | 4 +++- src/Tgstation.Server.Host/Core/IProcessBase.cs | 5 +++++ src/Tgstation.Server.Host/Core/Process.cs | 9 +++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index fde13f58c2..6a6448678b 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -427,5 +427,8 @@ namespace Tgstation.Server.Host.Components.Watchdog CheckDisposed(); reattachInformation.RebootState = RebootState.Normal; } + + /// + public void SetHighPriority() => process.SetHighPriority(); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 5d35904214..df759f6de9 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -665,6 +665,7 @@ namespace Tgstation.Server.Host.Components.Watchdog //wait until this boy officially starts so as not to confuse the servers as to who came first var startTime = DateTimeOffset.Now; alphaServer = await alphaServerTask.ConfigureAwait(false); + alphaServer.SetHighPriority(); //extra delay for total ordering var now = DateTimeOffset.Now; @@ -680,7 +681,8 @@ namespace Tgstation.Server.Host.Components.Watchdog bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); bravoServer = await bravoServerTask.ConfigureAwait(false); - + bravoServer.SetHighPriority(); + async Task CheckLaunch(ISessionController controller, string serverName) { var launch = await controller.LaunchResult.ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Core/IProcessBase.cs b/src/Tgstation.Server.Host/Core/IProcessBase.cs index b96b93b2ae..cc287208c4 100644 --- a/src/Tgstation.Server.Host/Core/IProcessBase.cs +++ b/src/Tgstation.Server.Host/Core/IProcessBase.cs @@ -12,5 +12,10 @@ namespace Tgstation.Server.Host.Core /// The resulting in the exit code of the process /// Task Lifetime { get; } + + /// + /// Set's the owned to + /// + void SetHighPriority(); } } diff --git a/src/Tgstation.Server.Host/Core/Process.cs b/src/Tgstation.Server.Host/Core/Process.cs index cd869ee83a..15296ebb2f 100644 --- a/src/Tgstation.Server.Host/Core/Process.cs +++ b/src/Tgstation.Server.Host/Core/Process.cs @@ -79,5 +79,14 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { } } + + public void SetHighPriority() + { + try + { + handle.PriorityClass = System.Diagnostics.ProcessPriorityClass.AboveNormal; + } + catch (InvalidOperationException) { } + } } } From ebadfbd1d4ee127bc3c992b68fac1a62848deae8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 15:23:09 -0400 Subject: [PATCH 19/22] BIG OOF --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 5 +++-- v4_prototype_TODO.txt | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index df759f6de9..2d7776d7f2 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -549,9 +549,10 @@ namespace Tgstation.Server.Host.Components.Watchdog } else moreActivationsToProcess = false; + + await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); } - await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); //writeback alphaServer and bravoServer alphaServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; bravoServer = AlphaIsActive ? monitorState.ActiveServer : monitorState.InactiveServer; @@ -575,7 +576,7 @@ namespace Tgstation.Server.Host.Components.Watchdog monitorState = new MonitorState(); //clean the slate } - await chatTask.ConfigureAwait(false); + await chatTask.ConfigureAwait(false); if(!Running) { logger.LogWarning("Failed to automatically restart the watchdog! Alpha: {0}; Bravo: {1}", result.Alpha.ToString(), result.Bravo.ToString()); diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index 572d75b123..641f358e0e 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -1,3 +1,6 @@ Verify the byond cache folder location on linux Test watchdog + +Only show user name and ID when serializing to API +In fact remove IApiConvertable<> altogether, it's not required by anything \ No newline at end of file From 80b4e6790c4c904a3b9845314dd3e38821cf4c1a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 15:26:21 -0400 Subject: [PATCH 20/22] Another postman update --- tools/TGS.postman_collection.json | 51 ++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json index 4c6ae4699f..c7f7374670 100644 --- a/tools/TGS.postman_collection.json +++ b/tools/TGS.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "6defe202-821a-488f-b8fe-e4017992655f", + "_postman_id": "b7e61e38-6891-473e-8ab4-869e5cf41a96", "name": "TGS", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -1254,7 +1254,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": 4294967294,\n\t\"dreamDaemonRights\": 4294967294,\n\t\"dreamMakerRights\": 4294967294,\n\t\"repositoryRights\": 4294967294\n\t\"chatBotRights\": 4294967294,\n\t\"configurationRights\": 4294967294\n}" + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": 4294967295,\n\t\"dreamDaemonRights\": 4294967295,\n\t\"dreamMakerRights\": 4294967295,\n\t\"repositoryRights\": 4294967295\n\t\"chatBotRights\": 4294967295,\n\t\"configurationRights\": 4294967295\n}" }, "url": { "raw": "localhost:5000/InstanceUser", @@ -1297,7 +1297,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": 4294967294,\n\t\"dreamDaemonRights\": 4294967294,\n\t\"dreamMakerRights\": 4294967294,\n\t\"repositoryRights\": 4294967294,\n\t\"chatBotRights\": 4294967294,\n\t\"configurationRights\": 4294967294\n}" + "raw": "{\n\t\"userId\": 1,\n\t\"byondRights\": 4294967295,\n\t\"dreamDaemonRights\": 4294967295,\n\t\"dreamMakerRights\": 4294967295,\n\t\"repositoryRights\": 4294967295,\n\t\"chatBotRights\": 4294967295,\n\t\"configurationRights\": 4294967295\n}" }, "url": { "raw": "localhost:5000/InstanceUser", @@ -1901,7 +1901,7 @@ ], "body": { "mode": "raw", - "raw": "{\n\t\"updateFromOrigin\": true,\n\t\"reference\": \"master\",\n\t\"newTestMerges\": [\n\t\t{\n\t\t\t\"number\": 39147,\n\t\t\t\"comment\": \"electric boogaloo\"\n\t\t}\n\t\t]\n}" + "raw": "{\n\t\"updateFromOrigin\": true,\n\t\"reference\": \"master\",\n\t\"newTestMerges\": [\n\t\t{\n\t\t\t\"number\": 39771,\n\t\t\t\"comment\": \"needful for debugging\"\n\t\t},\n\t\t{\n\t\t\t\"number\": 39777,\n\t\t\t\"comment\": \"also needful\"\n\t\t},\n\t\t{\n\t\t\t\"number\": 39778\n\t\t}\n\t\t]\n}" }, "url": { "raw": "localhost:5000/Repository", @@ -2717,6 +2717,49 @@ }, "response": [] }, + { + "name": "Read", + "request": { + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "User-Agent", + "value": "Postman/1.0" + }, + { + "key": "Api", + "value": "Tgstation.Server.Api/4.0.0.0" + }, + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Instance", + "value": "1" + } + ], + "body": { + "mode": "raw", + "raw": "{}" + }, + "url": { + "raw": "localhost:5000/DreamDaemon", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "DreamDaemon" + ] + } + }, + "response": [] + }, { "name": "Stop", "request": { From 7d02c43c6042dee6d9d02712eb753103a4cfc802 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 16:05:53 -0400 Subject: [PATCH 21/22] Add missing conditional --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 2d7776d7f2..5618cb91e7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -549,8 +549,9 @@ namespace Tgstation.Server.Host.Components.Watchdog } else moreActivationsToProcess = false; - - await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); + + if(moreActivationsToProcess) + await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false); } //writeback alphaServer and bravoServer From 19112750d9fe3faeab33a67411a8f41f8eda8d5f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sat, 18 Aug 2018 18:41:34 -0400 Subject: [PATCH 22/22] Fix MonitorActivationReason.InactiveServerRebooted always triggering --- src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index 5618cb91e7..b43720659f 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -437,8 +437,6 @@ namespace Tgstation.Server.Host.Components.Watchdog monitorState.NextAction = MonitorAction.Break; break; case MonitorActivationReason.InactiveServerRebooted: - //should never happen but okay - logger.LogWarning("Inactive server rebooted, this is a bug in DM code!"); monitorState.RebootingInactiveServer = true; monitorState.InactiveServer.ResetRebootState(); //the DMAPI has already done this internally monitorState.ActiveServer.ClosePortOnReboot = false; @@ -491,7 +489,7 @@ namespace Tgstation.Server.Host.Components.Watchdog var inactiveServerLifetime = monitorState.InactiveServer.Lifetime; var activeServerReboot = monitorState.ActiveServer.OnReboot; var inactiveServerReboot = monitorState.InactiveServer.OnReboot; - var inactiveServerStartup = monitorState.InactiveServer.LaunchResult; + var inactiveServerStartup = monitorState.RebootingInactiveServer ? monitorState.InactiveServer.LaunchResult : null; var activeLaunchParametersChanged = activeParametersUpdated.Task; var newDmbAvailable = dmbFactory.OnNewerDmb;