diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs
index 05a0c2fa91..b7e64db9b2 100644
--- a/src/Tgstation.Server.Host/Components/IInstance.cs
+++ b/src/Tgstation.Server.Host/Components/IInstance.cs
@@ -1,55 +1,12 @@
using Microsoft.Extensions.Hosting;
using System;
-using System.Threading.Tasks;
-using Tgstation.Server.Host.Components.Byond;
-using Tgstation.Server.Host.Components.Chat;
-using Tgstation.Server.Host.Components.Deployment;
-using Tgstation.Server.Host.Components.Repository;
-using Tgstation.Server.Host.Components.StaticFiles;
-using Tgstation.Server.Host.Components.Watchdog;
namespace Tgstation.Server.Host.Components
{
///
- /// For interacting with the instance services
+ /// Component version of .
///
- public interface IInstance : ILatestCompileJobProvider, IHostedService, IRenameNotifyee, IAsyncDisposable
+ interface IInstance : IInstanceCore, IHostedService, IAsyncDisposable
{
- ///
- /// The for the
- ///
- IRepositoryManager RepositoryManager { get; }
-
- ///
- /// The for the
- ///
- IByondManager ByondManager { get; }
-
- ///
- /// The for the .
- ///
- IDreamMaker DreamMaker { get; }
-
- ///
- /// The for the
- ///
- IWatchdog Watchdog { get; }
-
- ///
- /// The for the
- ///
- IChatManager Chat { get; }
-
- ///
- /// The for the
- ///
- IConfiguration Configuration { get; }
-
- ///
- /// Change the for the
- ///
- /// The new auto update inteval
- /// A representing the running operation
- Task SetAutoUpdateInterval(uint newInterval);
}
-}
\ No newline at end of file
+}
diff --git a/src/Tgstation.Server.Host/Components/IInstanceCore.cs b/src/Tgstation.Server.Host/Components/IInstanceCore.cs
new file mode 100644
index 0000000000..27552e5bc6
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/IInstanceCore.cs
@@ -0,0 +1,53 @@
+using System.Threading.Tasks;
+using Tgstation.Server.Host.Components.Byond;
+using Tgstation.Server.Host.Components.Chat;
+using Tgstation.Server.Host.Components.Deployment;
+using Tgstation.Server.Host.Components.Repository;
+using Tgstation.Server.Host.Components.StaticFiles;
+using Tgstation.Server.Host.Components.Watchdog;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// For interacting with the instance services
+ ///
+ public interface IInstanceCore : ILatestCompileJobProvider, IRenameNotifyee
+ {
+ ///
+ /// The for the
+ ///
+ IRepositoryManager RepositoryManager { get; }
+
+ ///
+ /// The for the
+ ///
+ IByondManager ByondManager { get; }
+
+ ///
+ /// The for the .
+ ///
+ IDreamMaker DreamMaker { get; }
+
+ ///
+ /// The for the
+ ///
+ IWatchdog Watchdog { get; }
+
+ ///
+ /// The for the
+ ///
+ IChatManager Chat { get; }
+
+ ///
+ /// The for the
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Change the for the
+ ///
+ /// The new auto update inteval
+ /// A representing the running operation
+ Task SetAutoUpdateInterval(uint newInterval);
+ }
+}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs
new file mode 100644
index 0000000000..494aaf130f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/IInstanceCoreProvider.cs
@@ -0,0 +1,15 @@
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Provider for s
+ ///
+ interface IInstanceCoreProvider
+ {
+ ///
+ /// Get the for a given if it's online.
+ ///
+ /// The to get the for.
+ /// The if it is online, otherwise.
+ IInstanceCore GetInstance(Models.Instance instance);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
index a4ff54c906..1731be40cf 100644
--- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs
@@ -16,11 +16,11 @@ namespace Tgstation.Server.Host.Components
Task Ready { get; }
///
- /// Get the associated with given
+ /// Get the associated with given
///
/// The of the desired
/// The associated with the given if it is online, otherwise.
- IInstance GetInstance(Models.Instance metadata);
+ IInstanceReference GetInstanceReference(Models.Instance metadata);
///
/// Online an
diff --git a/src/Tgstation.Server.Host/Components/IInstanceReference.cs b/src/Tgstation.Server.Host/Components/IInstanceReference.cs
new file mode 100644
index 0000000000..a6453747f8
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/IInstanceReference.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Controller version of .
+ ///
+ public interface IInstanceReference : IInstanceCore, IDisposable
+ {
+ ///
+ /// A unique ID for the .
+ ///
+ public Guid Uid { get; }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index 1617658bf5..8eba501262 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -23,6 +23,11 @@ namespace Tgstation.Server.Host.Components
#pragma warning disable CA1506 // TODO: Decomplexify
sealed class Instance : IInstance
{
+ ///
+ /// Message for the if ever a job starts on a different than the one that queued it.
+ ///
+ public const string DifferentCoreExceptionMessage = "Job started on different instance core!";
+
///
public IRepositoryManager RepositoryManager { get; }
@@ -186,8 +191,11 @@ namespace Tgstation.Server.Host.Components
};
string deploySha = null;
- await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContextFactory, progressReporter, jobCancellationToken) =>
+ await jobManager.RegisterOperation(repositoryUpdateJob, async (core, databaseContextFactory, paramJob, progressReporter, jobCancellationToken) =>
{
+ if (core != this)
+ throw new InvalidOperationException(DifferentCoreExceptionMessage);
+
// assume 5 steps with synchronize
const int ProgressSections = 7;
const int ProgressStep = 100 / ProgressSections;
@@ -373,7 +381,16 @@ namespace Tgstation.Server.Host.Components
await jobManager.RegisterOperation(
compileProcessJob,
- DreamMaker.DeploymentProcess,
+ (core, databaseContextFactory, job, progressReporter, jobCancellationToken) =>
+ {
+ if (core != this)
+ throw new InvalidOperationException(DifferentCoreExceptionMessage);
+ return DreamMaker.DeploymentProcess(
+ job,
+ databaseContextFactory,
+ progressReporter,
+ jobCancellationToken);
+ },
cancellationToken).ConfigureAwait(false);
await jobManager.WaitForJobCompletion(compileProcessJob, systemUser, default, cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/InstanceContainer.cs b/src/Tgstation.Server.Host/Components/InstanceContainer.cs
new file mode 100644
index 0000000000..3a1e64f92a
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/InstanceContainer.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Components
+{
+ ///
+ /// Wrapper for managing s
+ ///
+ sealed class InstanceContainer
+ {
+ ///
+ /// The .
+ ///
+ public IInstance Instance { get; }
+
+ ///
+ /// A that completes when there are no s active for the .
+ ///
+ public Task OnZeroReferences
+ {
+ get
+ {
+ lock (referenceCountLock)
+ {
+ if (referenceCount == 0)
+ return Task.CompletedTask;
+ return onZeroReferencesTcs.Task;
+ }
+ }
+ }
+
+ ///
+ /// for .
+ ///
+ readonly object referenceCountLock;
+
+ ///
+ /// Backing for .
+ ///
+ TaskCompletionSource
/// The being run
- /// The operation for the
+ /// The for the
/// The for the operation
/// A representing the running operation
- async Task RunJob(Job job, Func operation, CancellationToken cancellationToken)
+ async Task RunJob(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
{
using (LogContext.PushProperty("Job", job.Id))
try
@@ -87,7 +95,20 @@ namespace Tgstation.Server.Host.Jobs
var oldJob = job;
job = new Job { Id = oldJob.Id };
- await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false);
+ void UpdateProgress(int progress)
+ {
+ lock (synchronizationLock)
+ if (jobs.TryGetValue(oldJob.Id, out var handler))
+ handler.Progress = progress;
+ }
+
+ await operation(
+ instanceCoreProvider.Value.GetInstance(oldJob.Instance),
+ databaseContextFactory,
+ job,
+ UpdateProgress,
+ cancellationToken)
+ .ConfigureAwait(false);
logger.LogDebug("Job {0} completed!", job.Id);
}
@@ -144,43 +165,50 @@ namespace Tgstation.Server.Host.Jobs
}
///
- public Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
- {
- if (job == null)
- throw new ArgumentNullException(nameof(job));
- if (operation == null)
- throw new ArgumentNullException(nameof(operation));
+ public Task RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
+ => databaseContextFactory.UseContext(
+ async databaseContext =>
+ {
+ if (job == null)
+ throw new ArgumentNullException(nameof(job));
+ if (operation == null)
+ throw new ArgumentNullException(nameof(operation));
- job.StartedAt = DateTimeOffset.Now;
- job.Cancelled = false;
+ job.StartedAt = DateTimeOffset.Now;
+ job.Cancelled = false;
- job.Instance = new Instance
- {
- Id = job.Instance.Id
- };
- databaseContext.Instances.Attach(job.Instance);
+ job.Instance = new Models.Instance
+ {
+ Id = job.Instance.Id
+ };
+ databaseContext.Instances.Attach(job.Instance);
- job.StartedBy = new User
- {
- Id = job.StartedBy.Id
- };
- databaseContext.Users.Attach(job.StartedBy);
+ job.StartedBy = new User
+ {
+ Id = job.StartedBy.Id
+ };
+ databaseContext.Users.Attach(job.StartedBy);
- databaseContext.Jobs.Add(job);
+ databaseContext.Jobs.Add(job);
- await databaseContext.Save(cancellationToken).ConfigureAwait(false);
- logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description);
- var jobHandler = new JobHandler(x => RunJob(job, (jobParam, serviceProvider, ct) =>
- operation(jobParam, serviceProvider, y =>
- {
- lock (synchronizationLock)
- if (jobs.TryGetValue(job.Id, out var handler))
- handler.Progress = y;
- }, ct),
- x));
- lock (synchronizationLock)
- jobs.Add(job.Id, jobHandler);
- });
+ await databaseContext.Save(cancellationToken).ConfigureAwait(false);
+
+ logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description);
+ var jobHandler = new JobHandler(jobCancellationToken => RunJob(job, operation, jobCancellationToken));
+ try
+ {
+ lock (synchronizationLock)
+ {
+ jobs.Add(job.Id, jobHandler);
+ jobHandler.Start();
+ }
+ }
+ catch
+ {
+ jobHandler.Dispose();
+ throw;
+ }
+ });
///
public async Task StartAsync(CancellationToken cancellationToken)