From 38575a4c399ddb21482f19d88ffc93cfa2cd997b Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 10:47:24 -0400 Subject: [PATCH 01/25] Remove request counting. It's already done in file logging --- .../Components/Watchdog/WatchdogBase.cs | 20 +++++++------- src/Tgstation.Server.Host/Core/Application.cs | 2 -- .../ApplicationBuilderExtensions.cs | 27 ------------------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d6eafdef32..05656382c3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -621,9 +621,9 @@ namespace Tgstation.Server.Host.Components.Watchdog // wait for something to happen await toWaitOn.ConfigureAwait(false); - cancellationToken.ThrowIfCancellationRequested(); } + cancellationToken.ThrowIfCancellationRequested(); Logger.LogTrace("Monitor activated"); using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) @@ -711,17 +711,17 @@ namespace Tgstation.Server.Host.Components.Watchdog await chatTask.ConfigureAwait(false); } - } - catch (OperationCanceledException) - { - Logger.LogDebug("Monitor cancelled"); + } + catch (OperationCanceledException) + { + Logger.LogDebug("Monitor cancelled"); - if (releaseServers) - { - Logger.LogTrace("Detaching servers..."); - releasedReattachInformation = CreateReattachInformation(); - } + if (releaseServers) + { + Logger.LogTrace("Detaching servers..."); + releasedReattachInformation = CreateReattachInformation(); } + } DisposeAndNullControllers(); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index c7ed253c9c..7f486eeb43 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -357,8 +357,6 @@ namespace Tgstation.Server.Host.Core // Final point where we wrap exceptions in a 500 (ErrorMessage) response applicationBuilder.UseServerErrorHandling(); - applicationBuilder.UseRequestCounting(); - // 503 requests made while the application is starting applicationBuilder.UseAsyncInitialization(async (cancellationToken) => { diff --git a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs index 2fe00098db..f155aff190 100644 --- a/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ApplicationBuilderExtensions.cs @@ -107,32 +107,5 @@ namespace Tgstation.Server.Host.Extensions } }); } - - /// - /// Add middleware for logging the request number. - /// - /// The to configure. - public static void UseRequestCounting(this IApplicationBuilder applicationBuilder) - { - if (applicationBuilder == null) - throw new ArgumentNullException(nameof(applicationBuilder)); - - ulong requestCounter = 0; - - applicationBuilder.Use(async (context, next) => - { - var logger = GetLogger(context); - var requestNumber = ++requestCounter; - logger.LogTrace("Starting request #{0}...", requestNumber); - try - { - await next().ConfigureAwait(false); - } - finally - { - logger.LogTrace("Finished request #{0}", requestNumber); - } - }); - } } } From b8e766ba0748bf22e14955aea9742d5cc4e9abab Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 12:05:26 -0400 Subject: [PATCH 02/25] Fix active launch parameters update always triggering --- src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 05656382c3..b5087c1c1c 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -747,7 +747,10 @@ namespace Tgstation.Server.Host.Components.Watchdog return; ActiveLaunchParameters = launchParameters; if (Running) + { ActiveParametersUpdated.TrySetResult(null); // queue an update + ActiveParametersUpdated = new TaskCompletionSource(); + } } } From b8047a5585b8c6e737d02080c7bfddf4c9ed802c Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 12:09:13 -0400 Subject: [PATCH 03/25] Don't consider startup timeout as a launch parameter --- build/Version.props | 4 ++-- .../Models/Internal/DreamDaemonLaunchParameters.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build/Version.props b/build/Version.props index bda24b2315..a59e37402d 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,8 +3,8 @@ 4.3.0 - 6.5.0 - 7.1.0 + 6.5.1 + 7.1.1 5.2.2 0.4.0 1.1.0 diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index f5bd309d61..647c22c6db 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -48,7 +48,7 @@ namespace Tgstation.Server.Api.Models.Internal public uint? HeartbeatSeconds { get; set; } /// - /// Check if we match a given set of + /// Check if we match a given set of . is excluded. /// /// The to compare against /// if they match, otherwise @@ -57,7 +57,6 @@ namespace Tgstation.Server.Api.Models.Internal && SecurityLevel == otherParameters.SecurityLevel && PrimaryPort == otherParameters.PrimaryPort && SecondaryPort == otherParameters.SecondaryPort - && StartupTimeout == otherParameters.StartupTimeout - && HeartbeatSeconds == otherParameters.HeartbeatSeconds; + && HeartbeatSeconds == otherParameters.HeartbeatSeconds; // We intentionally don't check StartupTimeout as it doesn't matter } } \ No newline at end of file From 304421e7b2bb605816676f893e8f0f9d51b26695 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 12:25:46 -0400 Subject: [PATCH 04/25] This check will fail on reattach. Remove it --- .../Components/Watchdog/WindowsWatchdog.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index 730ced06b9..473b3f36cc 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -117,8 +117,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected override MonitorAction HandleNormalReboot() { - if (activeSwappable == null) - throw new InvalidOperationException("Expected activeSwappable to not be null!"); if (pendingSwappable != null) { Logger.LogTrace("Replacing activeSwappable with pendingSwappable..."); From 6985d63bfa3c64ccaa1c2949fe1cfefd0c7f2bbc Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 13:38:41 -0400 Subject: [PATCH 05/25] DmbFactory commit fix --- .../Components/Deployment/DmbFactory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs index 978586af70..113dbaa3ad 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DmbFactory.cs @@ -141,8 +141,11 @@ namespace Tgstation.Server.Host.Components.Deployment { nextDmbProvider?.Dispose(); nextDmbProvider = newProvider; - newerDmbTcs.SetResult(nextDmbProvider); + + // Oh god dammit + var temp = newerDmbTcs; newerDmbTcs = new TaskCompletionSource(); + temp.SetResult(nextDmbProvider); } } From 670bd81023e9538267c2ac6b4d52df4e2f208611 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 13:43:34 -0400 Subject: [PATCH 06/25] Add logging at start of auto update loop --- src/Tgstation.Server.Host/Components/Instance.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 2782ee9934..1de8db3fed 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -150,6 +150,7 @@ namespace Tgstation.Server.Host.Components #pragma warning disable CA1502 // TODO: Decomplexify async Task TimerLoop(uint minutes, CancellationToken cancellationToken) { + logger.LogTrace("Entering auto-update loop"); while (true) try { From 6b7b8932801420090f39491655adb52b4dd2ca34 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 14:48:16 -0400 Subject: [PATCH 07/25] Fix a bug when auto updating without preserving test merges --- src/Tgstation.Server.Host/Components/Instance.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 1de8db3fed..131438aa9c 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -301,11 +301,12 @@ namespace Tgstation.Server.Host.Components var currentHead = repo.Head; currentRevInfo = await databaseContext.RevisionInformations - .AsQueryable() - .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) - .FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false); + .AsQueryable() + .Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id) + .FirstOrDefaultAsync(jobCancellationToken) + .ConfigureAwait(false); - if (currentHead != startSha && currentRevInfo != default) + if (currentHead != startSha && currentRevInfo == default) await UpdateRevInfo(currentHead, true).ConfigureAwait(false); shouldSyncTracked = true; From 92b63db42e97196841a00aa173224d5fb047b388 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 15:12:24 -0400 Subject: [PATCH 08/25] Fix some cancellation token issues with auto update --- src/Tgstation.Server.Host/Components/Instance.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 131438aa9c..68d5fdcd77 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -234,7 +234,7 @@ namespace Tgstation.Server.Host.Components .AsQueryable() .Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id) .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) - .FirstOrDefaultAsync(cancellationToken); + .FirstOrDefaultAsync(jobCancellationToken); async Task UpdateRevInfo(string currentHead, bool onOrigin) { @@ -326,7 +326,7 @@ namespace Tgstation.Server.Host.Components if (hasDbChanges) try { - await databaseContext.Save(cancellationToken).ConfigureAwait(false); + await databaseContext.Save(jobCancellationToken).ConfigureAwait(false); } catch { @@ -369,7 +369,7 @@ namespace Tgstation.Server.Host.Components DreamMaker.DeploymentProcess, cancellationToken).ConfigureAwait(false); - await jobManager.WaitForJobCompletion(compileProcessJob, user, cancellationToken, default).ConfigureAwait(false); + await jobManager.WaitForJobCompletion(compileProcessJob, user, default, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { From 3ca2a8d24883290bd453587dd7d1ed7d73700915 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 15:46:19 -0400 Subject: [PATCH 09/25] Allow heartbeat time to be changed without a reboot --- .../Models/Internal/DreamDaemonLaunchParameters.cs | 5 ++--- .../Components/Watchdog/WatchdogBase.cs | 13 ++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs index 647c22c6db..56c4c758b3 100644 --- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs +++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs @@ -52,11 +52,10 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The to compare against /// if they match, otherwise - public bool Match(DreamDaemonLaunchParameters otherParameters) => + public bool CanApplyWithoutReboot(DreamDaemonLaunchParameters otherParameters) => AllowWebClient == (otherParameters?.AllowWebClient ?? throw new ArgumentNullException(nameof(otherParameters))) && SecurityLevel == otherParameters.SecurityLevel && PrimaryPort == otherParameters.PrimaryPort - && SecondaryPort == otherParameters.SecondaryPort - && HeartbeatSeconds == otherParameters.HeartbeatSeconds; // We intentionally don't check StartupTimeout as it doesn't matter + && SecondaryPort == otherParameters.SecondaryPort; // We intentionally don't check StartupTimeout or heartbeat seconds as it doesn't matter in terms of the watchdog } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index b5087c1c1c..4d2db18c2d 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -743,14 +743,13 @@ namespace Tgstation.Server.Host.Components.Watchdog { using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) { - if (launchParameters.Match(ActiveLaunchParameters)) - return; + bool match = launchParameters.CanApplyWithoutReboot(ActiveLaunchParameters); ActiveLaunchParameters = launchParameters; - if (Running) - { - ActiveParametersUpdated.TrySetResult(null); // queue an update - ActiveParametersUpdated = new TaskCompletionSource(); - } + if (match || !Running) + return; + + ActiveParametersUpdated.TrySetResult(null); // queue an update + ActiveParametersUpdated = new TaskCompletionSource(); } } From e24b45bbb19051f99d226edd86a42e1ccdf81c55 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 16:08:09 -0400 Subject: [PATCH 10/25] Version bump to 4.3.1 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index a59e37402d..264b6860ac 100644 --- a/build/Version.props +++ b/build/Version.props @@ -2,7 +2,7 @@ - 4.3.0 + 4.3.1 6.5.1 7.1.1 5.2.2 From 1d561dc542311c5aa2be53b0472230ded1c87f3d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 16:28:00 -0400 Subject: [PATCH 11/25] Fix watchdog detach event not being sent to DMAPI --- .../Components/Watchdog/WatchdogBase.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 4d2db18c2d..d3067f29b7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -904,14 +904,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task StopAsync(CancellationToken cancellationToken) { - if (releaseServers) - { - await StopMonitor().ConfigureAwait(false); - if (releasedReattachInformation != null) - await reattachInfoHandler.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false); - } - await TerminateNoLock(false, !releaseServers, cancellationToken).ConfigureAwait(false); + if (releasedReattachInformation != null) + await reattachInfoHandler.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false); } /// From a391144abdca91c841ae05762540a604753886cd Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 16:44:22 -0400 Subject: [PATCH 12/25] Fix potential concurrency issue when reattaching --- .../Components/Session/SessionController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 471cbad030..f8740a0034 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -241,8 +241,9 @@ namespace Tgstation.Server.Host.Components.Session _ = process.Lifetime.ContinueWith( x => { - if (!disposed) - reattachTopicCts.Cancel(); + lock (synchronizationLock) + if (!disposed) + reattachTopicCts.Cancel(); chatTrackingContext.Active = false; }, TaskScheduler.Current); From cdb5bae33a4e4c699122d34a0c56ddf6ac889eba Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 17:48:15 -0400 Subject: [PATCH 13/25] Prevent double disposes of session controllers. --- .../Components/Session/SessionController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index f8740a0034..b3658ce9d2 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -281,6 +281,8 @@ namespace Tgstation.Server.Host.Components.Session { if (disposed) return; + disposed = true; + logger.LogTrace("Disposing..."); if (disposing) { if (!released) @@ -294,7 +296,6 @@ namespace Tgstation.Server.Host.Components.Session Dmb?.Dispose(); // will be null when released chatTrackingContext.Dispose(); reattachTopicCts.Dispose(); - disposed = true; } else { From fdb02d3009851a1f8400d620d74f57ea8d17638d Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sun, 31 May 2020 19:21:08 -0400 Subject: [PATCH 14/25] Fix ObjectDisposedException --- .../Components/Session/SessionController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index b3658ce9d2..3d1590cc4e 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -293,7 +293,7 @@ namespace Tgstation.Server.Host.Components.Session process.Dispose(); bridgeRegistration.Dispose(); - Dmb?.Dispose(); // will be null when released + reattachInformation.Dmb?.Dispose(); // will be null when released chatTrackingContext.Dispose(); reattachTopicCts.Dispose(); } From b68cacdad9bce3a177f4d856030bf6c226901169 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 11:48:00 -0400 Subject: [PATCH 15/25] Modernize issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 36 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000000..ba351945b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,36 @@ +--- +name: Bug Report +about: Report a problem with the server +title: '' +labels: Bug, Reproduction Required, Ready +assignees: Cyberboss + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Please note that client issues (i.e. Control panel crashes) do not belong in this repo and should be report in their own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Logs** +Please include full server logs to help diagnose your problem + +**Server State: (please complete the following information):** + - OS: + - Version: + - BYOND Version Used: + - git Repository Used: + - Origin Commit hash Used: + - Active Test Merges: + - Client Version: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..b6058b642b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for the server +title: '' +labels: Feature Request, Backlog +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 552c5974178d1a4dd61ce9ec024477e5a539270f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 11:49:10 -0400 Subject: [PATCH 16/25] Delete old issue template --- .github/ISSUE_TEMPLATE.md | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b88655f26e..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,19 +0,0 @@ -Note that this repository does not contain any client code (With the exception of the web control panel which can be found in [its own repository](https://github.com/tgstation/tgstation-server-control-panel)). Any client issues should be reported to their respective codebases. - -Please include: - -- The version of tgstation-server you were using. - -- The operating system you are running it from. - -- A description of the issue. - -- A link to your codebase git (if public) - - Include active SHA/test merges if applicable - -- The client you we're using (Desktop control panel, web control panel, etc) - - Include a version if applicable - -- Reproduction steps for the issue if possible from your client. - -- The server log of when the event happened (The full file is much more useful than snippets). From 4a9d66b78e52da32dade3a5e1bdba763ce2a7bd5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 11:59:58 -0400 Subject: [PATCH 17/25] Minor doc update --- src/Tgstation.Server.Host/Components/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/README.md b/src/Tgstation.Server.Host/Components/README.md index 6d48a22aae..a59d17ec22 100644 --- a/src/Tgstation.Server.Host/Components/README.md +++ b/src/Tgstation.Server.Host/Components/README.md @@ -19,4 +19,8 @@ While the database represents stored instance data, in component code an instanc `IInstance`s are created via the [IInstanceFactory](./IInstanceFactory.cs) ([implementation](./InstanceFactory.cs)) and are generally controlled via the [IInstanceManager](./IInstanceManager.cs) ([implementation](./InstanceManager.cs)). -Many classes in here implement [IHostedService](), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. +Many classes in here implement [IHostedService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. + +`IInstanceManager` is the sole point where the controllers talk to component code. It also dispatches bridge requests to their relevant instances. + +There are various [EventType](./EventType.cs)s that can be dispatched to the DMAPI and the script system. From 0c0e7e25904fc0baa12e3083a05ad4baf75471d1 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 12:34:47 -0400 Subject: [PATCH 18/25] Events refactor - Add Components.Events namespace, move all event stuff there. - Add README. - Add EventScriptAttribute, all EventTypes must now have an associated script name. - Added unit tests. --- .../Components/Byond/ByondManager.cs | 1 + .../Components/Deployment/DreamMaker.cs | 1 + .../Components/{ => Events}/EventConsumer.cs | 2 +- .../Components/Events/EventScriptAttribute.cs | 25 +++++++++++++++++ .../Components/{ => Events}/EventType.cs | 19 ++++++++++++- .../Components/{ => Events}/IEventConsumer.cs | 2 +- .../Components/Events/README.md | 8 ++++++ .../Components/Instance.cs | 1 + .../Components/InstanceFactory.cs | 1 + .../Interop/Topic/EventNotification.cs | 1 + .../Components/README.md | 3 +- .../Components/Repository/Repository.cs | 1 + .../Repository/RepositoryManager.cs | 1 + .../Components/StaticFiles/Configuration.cs | 19 +++++++++---- .../Components/StaticFiles/IConfiguration.cs | 1 + .../Components/Watchdog/IWatchdog.cs | 1 + .../Components/Watchdog/WatchdogBase.cs | 1 + .../Events/TestEventScriptAttribute.cs | 21 ++++++++++++++ .../Components/Events/TestEventType.cs | 28 +++++++++++++++++++ 19 files changed, 126 insertions(+), 11 deletions(-) rename src/Tgstation.Server.Host/Components/{ => Events}/EventConsumer.cs (97%) create mode 100644 src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs rename src/Tgstation.Server.Host/Components/{ => Events}/EventType.cs (73%) rename src/Tgstation.Server.Host/Components/{ => Events}/IEventConsumer.cs (93%) create mode 100644 src/Tgstation.Server.Host/Components/Events/README.md create mode 100644 tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs create mode 100644 tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 7896369b02..d58ad67880 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index e35cfd5b15..c73f5ad8d8 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; diff --git a/src/Tgstation.Server.Host/Components/EventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs similarity index 97% rename from src/Tgstation.Server.Host/Components/EventConsumer.cs rename to src/Tgstation.Server.Host/Components/Events/EventConsumer.cs index 1cebdd1d2b..45cb18f0a0 100644 --- a/src/Tgstation.Server.Host/Components/EventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventConsumer.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Components.StaticFiles; using Tgstation.Server.Host.Components.Watchdog; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// sealed class EventConsumer : IEventConsumer diff --git a/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs new file mode 100644 index 0000000000..c42a4b2a6b --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Events/EventScriptAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace Tgstation.Server.Host.Components.Events +{ + /// + /// Attribute for indicating the script that a given runs. + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + sealed class EventScriptAttribute : Attribute + { + /// + /// The name of the script the event script the runs. + /// + public string ScriptName { get; } + + /// + /// Initializes a new instance of the . + /// + /// The value of . + public EventScriptAttribute(string scriptName) + { + ScriptName = scriptName ?? throw new ArgumentNullException(nameof(scriptName)); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs similarity index 73% rename from src/Tgstation.Server.Host/Components/EventType.cs rename to src/Tgstation.Server.Host/Components/Events/EventType.cs index 2f57373b32..419fb9ec3b 100644 --- a/src/Tgstation.Server.Host/Components/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// /// Types of events. Mirror in tgs.dm @@ -8,86 +8,103 @@ /// /// Parameters: Reference name, commit sha /// + [EventScript("RepoResetOrigin")] RepoResetOrigin, /// /// Parameters: Checkout target /// + [EventScript("RepoCheckout")] RepoCheckout, /// /// No parameters /// + [EventScript("RepoFetch")] RepoFetch, /// /// Parameters: Pull request number, pull request sha, merger message /// + [EventScript("RepoMergePullRequest")] RepoMergePullRequest, /// /// Parameters: Absolute path to repository root /// + [EventScript("PreSynchronize")] RepoPreSynchronize, /// /// Parameters: Version being installed /// + [EventScript("ByondInstallStart")] ByondInstallStart, /// /// Parameters: Error string /// + [EventScript("ByondInstallFail")] ByondInstallFail, /// /// Parameters: Old active version, new active version /// + [EventScript("ByondActiveVersionChange")] ByondActiveVersionChange, /// /// Parameters: Game directory path, origin commit sha /// + [EventScript("PreCompile")] CompileStart, /// /// No parameters /// + [EventScript("CompileCancelled")] CompileCancelled, /// /// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise /// + [EventScript("CompileFailure")] CompileFailure, /// /// Parameters: Game directory path /// + [EventScript("PostCompile")] CompileComplete, /// /// No parameters /// + [EventScript("InstanceAutoUpdateStart")] InstanceAutoUpdateStart, /// /// Parameters: Base sha, target sha, base reference, target reference /// + [EventScript("RepoMergeConflict")] RepoMergeConflict, /// /// No parameters /// + [EventScript("DeploymentComplete")] DeploymentComplete, /// /// Before the watchdog shutsdown. Not sent for graceful shutdowns. No parameters. /// + [EventScript("WatchdogShutdown")] WatchdogShutdown, /// /// Before the watchdog detaches. No parameters. /// + [EventScript("WatchdogDetach")] WatchdogDetach, } } diff --git a/src/Tgstation.Server.Host/Components/IEventConsumer.cs b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs similarity index 93% rename from src/Tgstation.Server.Host/Components/IEventConsumer.cs rename to src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs index f4dd616bd8..6dbf73fe87 100644 --- a/src/Tgstation.Server.Host/Components/IEventConsumer.cs +++ b/src/Tgstation.Server.Host/Components/Events/IEventConsumer.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Tgstation.Server.Host.Components +namespace Tgstation.Server.Host.Components.Events { /// /// Consumes s and takes the appropriate actions diff --git a/src/Tgstation.Server.Host/Components/Events/README.md b/src/Tgstation.Server.Host/Components/Events/README.md new file mode 100644 index 0000000000..12f8343ddd --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Events/README.md @@ -0,0 +1,8 @@ +# Event System + +Many actions in TGS generate events. They are dispatched via the [IEventConsumer](./IEventConsumer.cs) ([main implementation](./EventConsumer.cs)). From there, it is further sent to the two actual consumers. + +1. The static files system will attempt to run a script with the given [EventScriptAttribute](./EventScriptAttribute)'s `ScriptName`. +1. The watchdog will send a mirror of the event via the DMAPI to be handled by game code. + +With these two endpoints, the behaviour of TGS is highly customizable as server operators can customize the entire process. diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 68d5fdcd77..76d67ce595 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -9,6 +9,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Watchdog; using Tgstation.Server.Host.Database; diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 23aa1da429..844aa5c264 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -7,6 +7,7 @@ using Tgstation.Server.Host.Components.Byond; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Interop.Bridge; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Components.Session; diff --git a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs index e9183f5787..b6024c58a3 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Topic/EventNotification.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Tgstation.Server.Host.Components.Events; namespace Tgstation.Server.Host.Components.Interop.Topic { diff --git a/src/Tgstation.Server.Host/Components/README.md b/src/Tgstation.Server.Host/Components/README.md index a59d17ec22..ff311c3f00 100644 --- a/src/Tgstation.Server.Host/Components/README.md +++ b/src/Tgstation.Server.Host/Components/README.md @@ -10,6 +10,7 @@ Component code is where the magic and tears of TGS are made. There are six main There exist two more namespaces in here that don't directly fit in these 6 components. +- [Events](./Events) deals with the TGS event system. - [Interop](./Interop) deals with the bulk of DMAPI communication (Though it's not all contained here). - [Session](./Session) contains the classes used for actually executing DreamDaemon, sending topic requests, receiving bridge requests, among other things. @@ -22,5 +23,3 @@ While the database represents stored instance data, in component code an instanc Many classes in here implement [IHostedService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio), `InstanceManager` being the only one that is called by the ASP.NET runtime. In the case of instances `StartAsync()` is called when an `Instance` is being brought online (from server startup or user request). The `Instance` handles calling `StartAsync()` on its various subcomponents that need it. When an `Instance` is being brought offline (from server shutdown/restart/update or user request) the same pattern is followed calling `StopAsync()`. `IInstanceManager` is the sole point where the controllers talk to component code. It also dispatches bridge requests to their relevant instances. - -There are various [EventType](./EventType.cs)s that can be dispatched to the DMAPI and the script system. diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 8afac9fe04..35a8928d4c 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index 6b6c1b51b8..4c5fefc314 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index bcc44ee87f..a61ba8cea7 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; @@ -32,12 +33,18 @@ namespace Tgstation.Server.Host.Components.StaticFiles const string CodeModificationsHeadFile = "HeadInclude.dm"; const string CodeModificationsTailFile = "TailInclude.dm"; - static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary - { - { EventType.CompileStart, "PreCompile" }, - { EventType.CompileComplete, "PostCompile" }, - { EventType.RepoPreSynchronize, "PreSynchronize" } - }; + static readonly IReadOnlyDictionary EventTypeScriptFileNameMap = new Dictionary( + Enum.GetValues(typeof(EventType)) + .OfType() + .Select( + eventType => new KeyValuePair( + eventType, + typeof(EventType) + .GetField(eventType.ToString()) + .GetCustomAttributes(false) + .OfType() + .First() + .ScriptName))); /// /// The for diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index ad88be4917..0cd29c0c5c 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components.StaticFiles diff --git a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs index 0f86a510f5..536096c499 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/IWatchdog.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Session; namespace Tgstation.Server.Host.Components.Watchdog diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index d3067f29b7..9874ce67fa 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -11,6 +11,7 @@ using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components.Chat; using Tgstation.Server.Host.Components.Deployment; +using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.Components.Interop.Topic; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Core; diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs new file mode 100644 index 0000000000..6652345fab --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventScriptAttribute.cs @@ -0,0 +1,21 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + + +namespace Tgstation.Server.Host.Components.Events.Tests +{ + /// + /// Tests for the . + /// + [TestClass] + public sealed class TestEventScriptAttribute + { + [TestMethod] + public void TestConstruction() + { + Assert.ThrowsException(() => new EventScriptAttribute(null)); + var test = new EventScriptAttribute("test"); + Assert.AreEqual("test", test.ScriptName); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs new file mode 100644 index 0000000000..3e9ed9b8c0 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Components/Events/TestEventType.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tgstation.Server.Host.Components.Events.Tests +{ + [TestClass] + public sealed class TestEventType + { + [TestMethod] + public void TestAllEventTypesHaveUniqueEventScriptAttributes() + { + var allScripts = new HashSet(); + foreach (var eventType in Enum.GetValues(typeof(EventType))) { + var list = typeof(EventType) + .GetField(eventType.ToString()) + .GetCustomAttributes(false) + .OfType() + .ToList(); + Assert.AreEqual(1, list.Count, $"EventType: {eventType}"); + + var attribute = list.First(); + Assert.IsTrue(allScripts.Add(attribute.ScriptName), $"Non-unique script Name: {attribute.ScriptName}"); + } + } + } +} From b0fb5689b2b9064bed292a584ddde57ea5937b4a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 1 Jun 2020 16:38:18 -0400 Subject: [PATCH 19/25] Session docs --- .../Components/Session/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/Tgstation.Server.Host/Components/Session/README.md diff --git a/src/Tgstation.Server.Host/Components/Session/README.md b/src/Tgstation.Server.Host/Components/Session/README.md new file mode 100644 index 0000000000..c8d6756420 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Session/README.md @@ -0,0 +1,14 @@ +# Session Management + +"Session" refers to a single invocation of DreamDaemon. The code in here is meant for creating and managing sessions. This is different from the watchdog as it is more low level. + +[ISessionController](./ISessionController.cs) ([implementation](SessionController.cs)) is the representation of DreamDaemon. It handles most of the DMAPI interop, such as DMAPI validation, and raising events for things such as the world rebooting, or the process ending. These are created by the [ISessionControllerFactory](./ISessionControllerFactory.cs) ([implementation](./SessionControllerFactory.cs)) which handles actually launching DreamDaemon and linking together all the components it needs. Sessions give out a [LaunchResult](./LaunchResult.cs) which indicates how long it took for DreamDaemon to become responsive or if it exited before it did. + +Sessions can be detached and reattached. When a session is detached, it generates [ReattachInformation](./ReattachInformation.cs). This is stored in and the database as [DualReattachInformation](./DualReattachInformation.cs) by passing it into and out of [IReattachInfoHandler](./IReattachInfoHandler.cs) ([implementation](./ReattachInfoHandler.cs)). This data can be passed back into the `ISessionControllerFactory` to reattach the session. + +Other classes: + +- [ApiValidationStatus](./ApiValidationStatus.cs) is an indicator of DMAPI validation. +- [CombinedTopicResponse](./CombinedTopicResponse.cs) is a wrapper class around the external [BYOND.TopicSender] library's raw topic response (i.e. string or float + raw bytes) and our internal interop response. +- [DeadSessionController](./DeadSessionController.cs) dummy implementation of `ISessionController`. +- [RebootState](./RebootState.cs) indicates the action that should be taken when the server's world reboots. Just an indicator though, action is take elsewhere. From 44e4cea531ec84ce6c55f74ae81e4f5bb045f6c8 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 2 Jun 2020 12:01:21 -0400 Subject: [PATCH 20/25] Note about branches --- .github/CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index be80cf9929..a7b2e34978 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -48,6 +48,9 @@ The following environment variables aren't required but enable more tests. ### Know your Code +- All feature work should be submitted to the `dev` branch for the next minor release. +- All patch work should be submitted to the `master` branch for the next patch. Changes will be automatically integrated into `dev` + The `/src` folder at the root of this repository contains a series of `README.md` files useful for helping find your way around the codebase. ## Specifications From 3932f0047eee8cf20763ce69e774b9a8e642c6fb Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Tue, 2 Jun 2020 13:05:00 -0400 Subject: [PATCH 21/25] Watchdog docs - Cleanup code names/docs - Error code 80 returned when attempting to restart a stopped watchdog --- build/Version.props | 6 +- src/Tgstation.Server.Api/Models/ErrorCode.cs | 6 + .../Components/Session/README.md | 2 +- .../Components/Watchdog/BasicWatchdog.cs | 18 +++ .../Watchdog/ExperimentalWatchdog.cs | 2 +- .../Components/Watchdog/README.md | 52 ++++++++ .../Components/Watchdog/WatchdogBase.cs | 114 ++++++++---------- 7 files changed, 133 insertions(+), 67 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Watchdog/README.md diff --git a/build/Version.props b/build/Version.props index 264b6860ac..2a24e56613 100644 --- a/build/Version.props +++ b/build/Version.props @@ -2,9 +2,9 @@ - 4.3.1 - 6.5.1 - 7.1.1 + 4.3.2 + 6.6.0 + 7.2.0 5.2.2 0.4.0 1.1.0 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 1f8141301d..f54147b304 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -489,5 +489,11 @@ namespace Tgstation.Server.Api.Models /// [Description("The deployment succeeded but one or more notification events failed!")] PostDeployFailure, + + /// + /// Attempted to restart a stopped watchdog. + /// + [Description("Cannot restart the watchdog as it is not running!")] + WatchdogNotRunning, } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Components/Session/README.md b/src/Tgstation.Server.Host/Components/Session/README.md index c8d6756420..cf6b8f3516 100644 --- a/src/Tgstation.Server.Host/Components/Session/README.md +++ b/src/Tgstation.Server.Host/Components/Session/README.md @@ -2,7 +2,7 @@ "Session" refers to a single invocation of DreamDaemon. The code in here is meant for creating and managing sessions. This is different from the watchdog as it is more low level. -[ISessionController](./ISessionController.cs) ([implementation](SessionController.cs)) is the representation of DreamDaemon. It handles most of the DMAPI interop, such as DMAPI validation, and raising events for things such as the world rebooting, or the process ending. These are created by the [ISessionControllerFactory](./ISessionControllerFactory.cs) ([implementation](./SessionControllerFactory.cs)) which handles actually launching DreamDaemon and linking together all the components it needs. Sessions give out a [LaunchResult](./LaunchResult.cs) which indicates how long it took for DreamDaemon to become responsive or if it exited before it did. +[ISessionController](./ISessionController.cs) ([implementation](SessionController.cs)) is the representation of DreamDaemon. It handles most of the DMAPI interop, such as DMAPI validation, and raising events for things such as the world rebooting, or the process ending. These are created by the [ISessionControllerFactory](./ISessionControllerFactory.cs) ([implementation](./SessionControllerFactory.cs)) which handles actually launching DreamDaemon and linking together all the components it needs. It also runs some sanity checks, such as if the port requested is avaiable or if the BYOND pager is running (which can cause issues). Sessions give out a [LaunchResult](./LaunchResult.cs) which indicates how long it took for DreamDaemon to become responsive or if it exited before it did. Sessions can be detached and reattached. When a session is detached, it generates [ReattachInformation](./ReattachInformation.cs). This is stored in and the database as [DualReattachInformation](./DualReattachInformation.cs) by passing it into and out of [IReattachInfoHandler](./IReattachInfoHandler.cs) ([implementation](./ReattachInfoHandler.cs)). This data can be passed back into the `ISessionControllerFactory` to reattach the session. diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index e3e8968fd8..1722376728 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -328,5 +328,23 @@ namespace Tgstation.Server.Host.Components.Watchdog return Restart(true, cancellationToken); } + + /// + /// Send a chat message and log about a failed reattach operation and attempts another call to . + /// + /// If the inactive server was reattached successfully. + /// The for the operation/ + /// A representing the running operation. + async Task NotifyOfFailedReattach(bool inactiveReattachSuccess, CancellationToken cancellationToken) + { + // we lost the server, just restart entirely + DisposeAndNullControllers(); + const string FailReattachMessage = "Unable to properly reattach to server! Restarting..."; + Logger.LogWarning(FailReattachMessage); + Logger.LogDebug(inactiveReattachSuccess ? "Also could not reattach to inactive server!" : "Inactive server was reattached successfully!"); + Task chatTask = Chat.SendWatchdogMessage(FailReattachMessage, false, cancellationToken); + await LaunchNoLock(true, false, null, cancellationToken).ConfigureAwait(false); + await chatTask.ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs index 6547ef6d62..07c73f7376 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/ExperimentalWatchdog.cs @@ -512,7 +512,7 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogDebug(bothServersDead ? "Also could not reattach to inactive server!" : "Inactive server was reattached successfully!"); chatTask = Chat.SendWatchdogMessage(FailReattachMessage, false, cancellationToken); callBeforeRecurse(); - await LaunchImplNoLock(true, false, null, cancellationToken).ConfigureAwait(false); + await LaunchNoLock(true, false, null, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); return; } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/README.md b/src/Tgstation.Server.Host/Components/Watchdog/README.md new file mode 100644 index 0000000000..8927a46a83 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Watchdog/README.md @@ -0,0 +1,52 @@ +# The Watchdog + +The watchdog is what keeps DreamDaemon alive and well. It's responsible for starting, stopping, rebooting it, sending TGS events to the DMAPI, running heartbeats, and applying patches. + +There are three main implementations of the interface [IWatchdog](./IWatchdog.cs) that deal with how it handles the last of those jobs. + +- The [BasicWatchdog](./BasicWatchdog.cs) kills DreamDaemon when the world reboots and immediately restarts it with the newly compiled .dmb. +- The [WindowsWatchdog](./WindowsWatchdog.cs) uses a quirk of the interaction of DD with the Windows file system. A symlink to the game folder is created. From here, we launch the game. When a new .dmb is available, this symlink is deleted and then immediately recreated pointing to the new game directory. When DreamDaemon reboots it will load the new game. +- The [ExperimentalWatchdog](./ExperimentalWatchdog) starts new servers when the new .dmb comes in. But it uses some, rather invasive, port trickery to reroute traffic from the old game server to the new one. The idea for this is to always have two servers running so one can act as a fallback for if the main one fails. This is currently a big work-in-progress as it has issues with BYOND bugs and static file sharing. + +[WatchdogBase](./WatchdogBase.cs) is the parent of all these implementations and contains the bulk of the monitoring code. More on that later. + +`IWatchdog`s are created via the [IWatchdogFactory](./IWatchdogFactory.cs) interface. The two implementations, [WatchdogFactory](./WatchdogFactory.cs) and [WindowsWatchdogFactory](./WindowsWatchdogFactory.cs), differ in that the latter creates `WindowsWatchdog`s as opposed to `BasicWatchdog`s. + +## Startup + +From the perspective of `WatchdogBase` + +- When an instance is onlined, `StartAsync()` is called on `WatchdogBase`. If the instance is configured to autostart the server, or if reattach information is available from the `IReattachInfoHandler` the watchdog will be launched here. Otherwise, it waits to be activated from the controller. +- We first do some sanity checks such as if the watchdog is already running and if the `IDmbFactory` has a IDmbProvider for us. +- We send out the annoucement of launch/reattach to the chat system. +- The `ActiveLaunchParameters` are made the `LastLaunchParameters` for reference. +- `InitControllers()` is called, which abstractly creates the `ISessionController`(s) for the watchdog. + - For the `BasicWatchdog`, the controller is created normally. + - For the `WindowsWatchdog`, we replace the `IDmbProvider` with a `WindowsSwappableDmbProvider` and setup the symlinking before deferring to the `BasicWatchdog` + - For the `ExperimentalWatchdog` we launch two servers in tandem. + - For reattaching, all three work similarly in that they use `ISessionControllerFactory` to reattach their sessions. The `ExperimentalWatchdog` will insert a `DeadSessionController` in place of one if only one server could not reattach. +- `MonitorLifetimes()` is called which is the "main loop" of the watchdog. + +## Monitoring + +Watchdog monitoring takes place in the `MonitorLifetimes()` functiona and is entirely event based. It responds to a set of [MonitorActivationReason](./MonitorActivationReason.cs)s and takes the apporprate action. This includes: +- When a server crashes. +- When a server calls `/world/prod/Reboot()`. +- When a new .dmb is deployed. +- When DreamDaemon settings are changed via the API. +- When it is time to make a heartbeat. + +Here's how this works. + +- A [MonitorState](./MonitorState.cs) object is created +- `Task`s are created for all possible activation reasons and an asynchrounous wait is made on them. +- Each activation reason is processed in a particular order via a call to `HandleMonitorWakeup()`. This function is allowed to mutate `MonitorState`. + - How each activation reason is handled depends on the watchdog type. For example, the `BasicWatchdog` will queue a graceful reboot when a new .dmb becomes available, wheras the `WindowsWatchdog` will immediately swap the symlink to it. + - Some actions are universal. i.e. if the server crashes, a reboot will occur. Or the server will shutdown on reboot if a graceful stop was queued. + - If the state's [MonitorAction](./MonitorAction.cs) changes, what happens after each activation may change + +This continues until `StopMonitor` is called, which triggers the `CancellationToken` in `MonitorLifetimes()` and terminates all `ISessionController`s. + +## Detaching + +`WatchdogBase` maintains a `IRestartRegistration` with TGS, meaning that, when TGS is rebooted, `HandleRestart()` will be called before `StopAsync()`. This has the effect of adjusting what happens when the monitor exits. Instead of terminating the server(s) it will instead call `ISessionController.Release()` on them. This generates the `ReattachInformation` which will be saved to the database in `StopAsync()`. diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 9874ce67fa..d2400a8170 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -119,7 +119,7 @@ namespace Tgstation.Server.Host.Components.Watchdog readonly object controllerDisposeLock; /// - /// If the should in + /// If the should in /// readonly bool autoStart; @@ -332,14 +332,14 @@ namespace Tgstation.Server.Host.Components.Watchdog /// to use, if any /// The for the operation /// A representing the running operation - protected async Task LaunchImplNoLock(bool startMonitor, bool announce, DualReattachInformation reattachInfo, CancellationToken cancellationToken) + protected async Task LaunchNoLock(bool startMonitor, bool announce, DualReattachInformation reattachInfo, CancellationToken cancellationToken) { Logger.LogTrace("Begin LaunchImplNoLock"); if (Running) throw new JobException(ErrorCode.WatchdogRunning); - if (!DmbFactory.DmbAvailable) + if (reattachInfo == null && !DmbFactory.DmbAvailable) throw new JobException(ErrorCode.WatchdogCompileJobCorrupted); // this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers @@ -356,10 +356,13 @@ namespace Tgstation.Server.Host.Components.Watchdog heartbeatsMissed = 0; // for when we call ourself and want to not catch thrown exceptions - var ignoreNestedException = false; + var recursiveCallToHappen = false; try { - await InitControllers(() => ignoreNestedException = true, chatTask, reattachInfo, cancellationToken).ConfigureAwait(false); + await InitControllers(() => recursiveCallToHappen = true, chatTask, reattachInfo, cancellationToken).ConfigureAwait(false); + if (recursiveCallToHappen) + return; + await chatTask.ConfigureAwait(false); Logger.LogInformation("Launched servers successfully"); @@ -367,13 +370,14 @@ namespace Tgstation.Server.Host.Components.Watchdog if (startMonitor) { - StartMonitor(); + monitorCts = new CancellationTokenSource(); + monitorTask = MonitorLifetimes(monitorCts.Token); } } catch (Exception e) { // don't try to send chat tasks or warning logs if were suppressing exceptions or cancelled - if (!ignoreNestedException && !cancellationToken.IsCancellationRequested) + if (!recursiveCallToHappen && !cancellationToken.IsCancellationRequested) { var originalChatTask = chatTask; async Task ChainChatTaskWithErrorMessage() @@ -399,15 +403,6 @@ namespace Tgstation.Server.Host.Components.Watchdog } } - /// - /// Call and setup and . - /// - protected void StartMonitor() - { - monitorCts = new CancellationTokenSource(); - monitorTask = MonitorLifetimes(monitorCts.Token); - } - /// /// Stops . Doesn't kill the servers /// @@ -427,24 +422,6 @@ namespace Tgstation.Server.Host.Components.Watchdog return wasRunning; } - /// - /// Send a chat message and log about a failed reattach operation and attempts another call to . - /// - /// If the inactive server was reattached successfully. - /// The for the operation/ - /// A representing the running operation. - protected async Task NotifyOfFailedReattach(bool inactiveReattachSuccess, CancellationToken cancellationToken) - { - // we lost the server, just restart entirely - DisposeAndNullControllers(); - const string FailReattachMessage = "Unable to properly reattach to server! Restarting..."; - Logger.LogWarning(FailReattachMessage); - Logger.LogDebug(inactiveReattachSuccess ? "Also could not reattach to inactive server!" : "Inactive server was reattached successfully!"); - Task chatTask = Chat.SendWatchdogMessage(FailReattachMessage, false, cancellationToken); - await LaunchImplNoLock(true, false, null, cancellationToken).ConfigureAwait(false); - await chatTask.ConfigureAwait(false); - } - /// /// Check the of a given for errors and throw a if any are detected. /// @@ -477,6 +454,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected void DisposeAndNullControllers() { + Logger.LogTrace("DisposeAndNullControllers"); lock (controllerDisposeLock) DisposeAndNullControllersImpl(); } @@ -513,6 +491,11 @@ namespace Tgstation.Server.Host.Components.Watchdog MonitorState monitorState, CancellationToken cancellationToken); + /// + /// Attempt to restart the monitor from scratch. + /// + /// The for the operation. + /// A resulting in the new . private async Task MonitorRestart(CancellationToken cancellationToken) { Logger.LogTrace("Monitor restart!"); @@ -526,7 +509,7 @@ namespace Tgstation.Server.Host.Components.Watchdog try { // use LaunchImplNoLock without announcements or restarting the monitor - await LaunchImplNoLock(false, false, null, cancellationToken).ConfigureAwait(false); + await LaunchNoLock(false, false, null, cancellationToken).ConfigureAwait(false); if (Running) { Logger.LogDebug("Relaunch successful, resetting monitor state..."); @@ -568,7 +551,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } /// - /// The loop that watches the watchdog. + /// The main loop of the watchdog. Ayschronously waits for events to occur and then responds to them. /// /// The for the operation. /// A representing the running operation. @@ -607,35 +590,32 @@ namespace Tgstation.Server.Host.Components.Watchdog // cancel waiting if requested var cancelTcs = new TaskCompletionSource(); - using (cancellationToken.Register(() => cancelTcs.SetCanceled())) - { - var toWaitOn = Task.WhenAny( - activeServerLifetime, - activeServerReboot, - inactiveServerLifetime, - inactiveServerReboot, - inactiveStartupComplete, - heartbeat, - newDmbAvailable, - cancelTcs.Task, - activeLaunchParametersChanged); + var toWaitOn = Task.WhenAny( + activeServerLifetime, + activeServerReboot, + inactiveServerLifetime, + inactiveServerReboot, + inactiveStartupComplete, + heartbeat, + newDmbAvailable, + cancelTcs.Task, + activeLaunchParametersChanged); - // wait for something to happen + // wait for something to happen + using (cancellationToken.Register(() => cancelTcs.SetCanceled())) await toWaitOn.ConfigureAwait(false); - } cancellationToken.ThrowIfCancellationRequested(); Logger.LogTrace("Monitor activated"); + // always run HandleMonitorWakeup from the context of the semaphore lock using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) { - // always run HandleMonitorWakeup from the context of the semaphore lock // multiple things may have happened, handle them one at a time for (var moreActivationsToProcess = true; moreActivationsToProcess && (monitorState.NextAction == MonitorAction.Continue || monitorState.NextAction == MonitorAction.Skip);) { MonitorActivationReason activationReason = default; // this will always be assigned before being used - // process the tasks in this order and call HandlerMonitorWakup for each bool CheckActivationReason(ref Task task, MonitorActivationReason testActivationReason) { var taskCompleted = task?.IsCompleted == true; @@ -651,6 +631,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return false; } + // process the tasks in this order and call HandlerMonitorWakup for each depending on the new monitorState var anyActivation = CheckActivationReason(ref activeServerLifetime, MonitorActivationReason.ActiveServerCrashed) || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) @@ -681,11 +662,14 @@ namespace Tgstation.Server.Host.Components.Watchdog } Logger.LogTrace("Next monitor action is to {0}", monitorState.NextAction); + + // Restart if requested if (monitorState.NextAction == MonitorAction.Restart) monitorState = await MonitorRestart(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { + // let this bubble, other exceptions caught below throw; } catch (Exception e) @@ -715,6 +699,7 @@ namespace Tgstation.Server.Host.Components.Watchdog } catch (OperationCanceledException) { + // stop signal Logger.LogDebug("Monitor cancelled"); if (releaseServers) @@ -732,7 +717,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Starts all s. /// - /// An that must be run before making a recursive call to . + /// An that must be run before making a recursive call to . /// A, possibly active, for an outgoing chat message. /// to use, if any /// The for the operation @@ -818,7 +803,7 @@ namespace Tgstation.Server.Host.Components.Watchdog public async Task Launch(CancellationToken cancellationToken) { using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) - await LaunchImplNoLock(true, true, null, cancellationToken).ConfigureAwait(false); + await LaunchNoLock(true, true, null, cancellationToken).ConfigureAwait(false); } /// @@ -837,10 +822,13 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public async Task Restart(bool graceful, CancellationToken cancellationToken) { + if (!Running) + throw new JobException(ErrorCode.WatchdogRunning); + Logger.LogTrace("Begin Restart. Graceful: {0}", graceful); using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false)) { - if (!graceful || !Running) + if (!graceful) { Task chatTask; if (Running) @@ -850,16 +838,14 @@ namespace Tgstation.Server.Host.Components.Watchdog } else chatTask = Task.CompletedTask; - await LaunchImplNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false); + await LaunchNoLock(true, !Running, null, cancellationToken).ConfigureAwait(false); await chatTask.ConfigureAwait(false); } var toReboot = GetActiveController(); - if (toReboot != null) - { - if (!await toReboot.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false)) - Logger.LogWarning("Unable to send reboot state change event!"); - } + if (toReboot != null + && !await toReboot.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false)) + Logger.LogWarning("Unable to send reboot state change event!"); } } @@ -898,7 +884,7 @@ namespace Tgstation.Server.Host.Components.Watchdog await jobManager.RegisterOperation(job, async (j, databaseContextFactory, progressFunction, ct) => { using (await SemaphoreSlimContext.Lock(Semaphore, ct).ConfigureAwait(false)) - await LaunchImplNoLock(true, true, reattachInfo, ct).ConfigureAwait(false); + await LaunchNoLock(true, true, reattachInfo, ct).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false); } @@ -907,7 +893,11 @@ namespace Tgstation.Server.Host.Components.Watchdog { await TerminateNoLock(false, !releaseServers, cancellationToken).ConfigureAwait(false); if (releasedReattachInformation != null) + { await reattachInfoHandler.Save(releasedReattachInformation, cancellationToken).ConfigureAwait(false); + releasedReattachInformation = null; + releaseServers = false; + } } /// From cf7e17bf68e1f96dff58eb2379ca99c32ed9414b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 05:52:49 -0400 Subject: [PATCH 22/25] Fix documentation link --- src/Tgstation.Server.Host/Jobs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Jobs/README.md b/src/Tgstation.Server.Host/Jobs/README.md index 48ec91bddd..53bf47f8fc 100644 --- a/src/Tgstation.Server.Host/Jobs/README.md +++ b/src/Tgstation.Server.Host/Jobs/README.md @@ -1,5 +1,5 @@ # Jobs Subsystem - [IJobManager](./IJobManager.cs) and [implementation](./JobManager.cs) is where the bulk of the magic happens. The `RegisterOperation()` call is what takes a work unit and sets it to run asynchronously while being tracked through the API. -- [JobException] is a special .NET Exception implementation that is able to carry API `ErrorCode`s and other additional data. +- [JobException](./JobException.cs) is a special .NET Exception implementation that is able to carry API `ErrorCode`s and other additional data. - [JobHandler](./JobHandler.cs) carries the [CancellationTokenSource](https://stackoverflow.com/questions/20638952/cancellationtoken-and-cancellationtokensource-how-to-use-it) for a given job in a disposable context. From 2dd36389a53dd2c86b8880912aca28a0c21b909f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 12:42:07 -0400 Subject: [PATCH 23/25] Fix a NullReferenceException when deploying with no repo --- src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index c73f5ad8d8..3a2eb928a0 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -586,7 +586,7 @@ namespace Tgstation.Server.Host.Components.Deployment } catch { - repo.Dispose(); + repo?.Dispose(); throw; } }) From d6fa60a3b8c48d7a41afe178e32ae7e4e9d64668 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 14:20:11 -0400 Subject: [PATCH 24/25] Add DreamDaemon logging to Windows --- .../Session/SessionControllerFactory.cs | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index e5d61c60d1..9be2d437be 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api; @@ -233,12 +234,18 @@ namespace Tgstation.Server.Host.Components.Session var visibility = apiValidate ? "invisible" : "public"; // important to run on all ports to allow port changing - var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4} -public -params \"{5}\"", + Guid? logFileGuid = null; + var arguments = String.Format( + CultureInfo.InvariantCulture, + "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"", dmbProvider.DmbName, portToUse, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(launchParameters.SecurityLevel.Value), visibility, + platformIdentifier.IsWindows + ? $" -log {logFileGuid = Guid.NewGuid()}" + : String.Empty, // Just use stdout on linux parameters); // See https://github.com/tgstation/tgstation-server/issues/719 @@ -253,16 +260,52 @@ namespace Tgstation.Server.Host.Components.Session noShellExecute, noShellExecute: noShellExecute); - if (noShellExecute) + async Task GetDDOutput() { - // Log DD output - _ = process.Lifetime.ContinueWith( - x => logger.LogTrace( - "DreamDaemon Output:{0}{1}", - Environment.NewLine, process.GetCombinedOutput()), - TaskScheduler.Current); + if (!platformIdentifier.IsWindows) + return process.GetCombinedOutput(); + + var logFilePath = ioManager.ConcatPath(basePath, logFileGuid.ToString()); + try + { + var dreamDaemonLogBytes = await ioManager.ReadAllBytes( + logFilePath, + default) + .ConfigureAwait(false); + + return Encoding.UTF8.GetString(dreamDaemonLogBytes); + } + finally + { + try + { + await ioManager.DeleteFile(logFilePath, default).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogWarning("Failed to delete DreamDaemon log file {0}: {1}", logFilePath, ex); + } + } } + // Log DD output + _ = process.Lifetime.ContinueWith( + async x => + { + try + { + var ddOutput = await GetDDOutput().ConfigureAwait(false); + logger.LogTrace( + "DreamDaemon Output:{0}{1}", + Environment.NewLine, ddOutput); + } + catch (Exception ex) + { + logger.LogWarning("Error reading DreamDaemon output: {0}", ex); + } + }, + TaskScheduler.Current); + try { networkPromptReaper.RegisterProcess(process); From 568f3bd764f64684fbf26e65c54d57e29aa67fe7 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Thu, 4 Jun 2020 15:11:22 -0400 Subject: [PATCH 25/25] Fix bad DMAPI override --- src/DMAPI/tgs/core/datum.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DMAPI/tgs/core/datum.dm b/src/DMAPI/tgs/core/datum.dm index ef6d3a4fe5..4d37ed662d 100644 --- a/src/DMAPI/tgs/core/datum.dm +++ b/src/DMAPI/tgs/core/datum.dm @@ -50,7 +50,7 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) return TGS_UNIMPLEMENTED -/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) +/datum/tgs_api/proc/ChatPrivateMessage(message, datum/tgs_chat_user/user) return TGS_UNIMPLEMENTED /datum/tgs_api/proc/SecurityLevel()