2using System.Collections.Generic;
5using System.Threading.Tasks;
7using Microsoft.EntityFrameworkCore;
8using Microsoft.Extensions.Hosting;
9using Microsoft.Extensions.Logging;
10using Microsoft.Extensions.Options;
93 readonly ILogger<InstanceManager>
logger;
98 readonly Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>
instances;
172 IOptions<GeneralConfiguration> generalConfigurationOptions,
173 IOptions<SwarmConfiguration> swarmConfigurationOptions,
174 ILogger<InstanceManager>
logger)
186 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
187 swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
188 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
190 instances =
new Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>();
192 readyTcs =
new TaskCompletionSource();
209 await instanceKvp.Value.Instance.DisposeAsync();
215 logger.LogInformation(
"Server shutdown");
221 ArgumentNullException.ThrowIfNull(metadata);
225 if (!
instances.TryGetValue(metadata.Id.Value, out var instance))
228 return instance.AddReference();
233 public async Task
MoveInstance(Models.Instance instance,
string oldPath, CancellationToken cancellationToken)
235 ArgumentNullException.ThrowIfNull(oldPath);
239 if (instanceReferenceCheck !=
null)
240 throw new InvalidOperationException(
"Cannot move an online instance!");
241 var newPath = instance.Path;
254 "Error moving instance {instanceId}!",
258 logger.LogDebug(
"Reverting instance {instanceId}'s path to {oldPath} in the DB...", instance.Id, oldPath);
267 db.Instances.Attach(targetInstance);
268 targetInstance.Path = oldPath;
269 return db.Save(CancellationToken.None);
272 catch (Exception innerEx)
276 "Error reverting database after failing to move instance {instanceId}! Attempting to detach...",
285 CancellationToken.None);
287 catch (Exception tripleEx)
291 "Okay, what gamma radiation are you under? Failed to write instance attach file!");
293 throw new AggregateException(tripleEx, innerEx, ex);
296 throw new AggregateException(ex, innerEx);
304 public async Task
OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
306 ArgumentNullException.ThrowIfNull(metadata);
313 if (!
instances.TryGetValue(metadata.Id.Value, out container))
315 logger.LogDebug(
"Not offlining removed instance {instanceId}", metadata.Id);
322 logger.LogInformation(
"Offlining instance ID {instanceId}", metadata.Id);
329 var tasks =
new List<Task>();
336 .Where(x => x.Instance.Id == metadata.Id && !x.StoppedAt.HasValue)
337 .Select(x =>
new Models.Job
341 .ToListAsync(cancellationToken);
342 foreach (var job in jobs)
346 await Task.WhenAll(tasks);
352 instances.Add(metadata.Id.Value, container);
364 await container.
Instance.DisposeAsync();
370 public async Task
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
372 ArgumentNullException.ThrowIfNull(metadata);
376 if (
instances.ContainsKey(metadata.Id.Value))
378 logger.LogDebug(
"Aborting instance creation due to it seemingly already being online");
382 logger.LogInformation(
"Onlining instance ID {instanceId} ({instanceName}) at {instancePath}", metadata.Id, metadata.Name, metadata.Path);
386 await instance.StartAsync(cancellationToken);
397 logger.LogError(
"Unable to commit onlined instance {instanceId} into service, offlining!", metadata.Id);
401 await instance.StopAsync(CancellationToken.None);
403 catch (Exception innerEx)
405 throw new AggregateException(innerEx, ex);
413 await instance.DisposeAsync();
423 return Task.CompletedTask;
427 public async Task
StopAsync(CancellationToken cancellationToken)
432 logger.LogDebug(
"Stopping instance manager...");
436 logger.LogTrace(
"Interrupting startup task...");
441 var instanceFactoryStopTask =
instanceFactory.StopAsync(cancellationToken);
442 await
jobService.StopAsync(cancellationToken);
444 async Task OfflineInstanceImmediate(
IInstance instance, CancellationToken cancellationToken)
448 await instance.StopAsync(cancellationToken);
452 logger.LogError(ex,
"Instance shutdown exception!");
456 await Task.WhenAll(
instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
457 await instanceFactoryStopTask;
463 logger.LogCritical(ex,
"Instance manager stop exception!");
470 ArgumentNullException.ThrowIfNull(parameters);
473 for (var i = 0; bridgeHandler ==
null && i < 30; ++i)
477 Task delayTask = Task.CompletedTask;
480 delayTask =
asyncDelayer.
Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
485 if (bridgeHandler ==
null)
489 logger.LogWarning(
"Recieved invalid bridge request with access identifier: {accessIdentifier}", parameters.
AccessIdentifier);
499 ArgumentNullException.ThrowIfNull(bridgeHandler);
505 logger.LogTrace(
"Registered bridge handler: {accessIdentifier}", accessIdentifier);
513 logger.LogTrace(
"Unregistered bridge handler: {accessIdentifier}", accessIdentifier);
523 instances.TryGetValue(metadata.Id.Value, out var container);
524 return container?.Instance;
546 List<Models.Instance> dbInstances =
null;
548 async databaseContext => dbInstances = await databaseContext
552 .Include(x => x.RepositorySettings)
553 .Include(x => x.ChatSettings)
554 .ThenInclude(x => x.Channels)
555 .Include(x => x.DreamDaemonSettings)
556 .ToListAsync(cancellationToken));
559 var jobManagerStartup =
jobService.StartAsync(cancellationToken);
561 await Task.WhenAll(instanceEnumeration, factoryStartup, jobManagerStartup);
563 var instanceOnliningTasks = dbInstances.Select(
572 logger.LogError(ex,
"Failed to online instance {instanceId}!", metadata.Id);
576 await Task.WhenAll(instanceOnliningTasks);
580 logger.LogInformation(
"Server ready!");
583 catch (OperationCanceledException ex)
585 logger.LogInformation(ex,
"Cancelled instance manager initialization!");
589 logger.LogCritical(e,
"Instance manager startup error!");
597 logger.LogCritical(e2,
"Failed to kill server!");
611 if (!systemIdentity.CanCreateSymlinks)
612 throw new InvalidOperationException(
"The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
633 throw new InvalidOperationException(
"Swarm private key does not match the swarm controller's!");
636 throw new InvalidOperationException(
"Swarm controller's TGS version does not match our own!");
virtual ? long Id
The ID of the entity.
string? Identifier
The server's identifier.
async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance. A Task representing the running operation.
Task Ready
Task that completes when the IInstanceManager finishes initializing.
async Task InitializeSwarm(CancellationToken cancellationToken)
Initializes the connection to the TGS swarm.
readonly TaskCompletionSource readyTcs
The TaskCompletionSource for Ready.
async Task MoveInstance(Models.Instance instance, string oldPath, CancellationToken cancellationToken)
Move an IInstance. A Task representing the running operation.
readonly Dictionary< long, ReferenceCountingContainer< IInstance, InstanceWrapper > > instances
Map of instance EntityId.Ids to the respective ReferenceCountingContainer<TWrapped,...
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobService jobService, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IServerPortProvider serverPortProvider, ISwarmServiceController swarmServiceController, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, ILogger< InstanceManager > logger)
Initializes a new instance of the InstanceManager class.
IInstanceCore GetInstance(Models.Instance metadata)
Get the IInstanceCore for a given instance if it's online. The IInstanceCore if it is online,...
void CheckSystemCompatibility()
Check we have a valid system and configuration.
readonly IJobService jobService
The IJobService for the InstanceManager.
IInstanceReference GetInstanceReference(Api.Models.Instance metadata)
Get the IInstanceReference associated with given metadata . The IInstance associated with the given m...
readonly IIOManager ioManager
The IIOManager for the InstanceManager.
bool disposed
If the InstanceManager has been DisposeAsync'd.
readonly ISwarmServiceController swarmServiceController
The ISwarmServiceController for the InstanceManager.
async ValueTask DisposeAsync()
readonly CancellationTokenSource startupCancellationTokenSource
The CancellationTokenSource for Initialize(CancellationToken).
async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler . A representative IBridgeRegistration.
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager.
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager.
readonly SwarmConfiguration swarmConfiguration
The SwarmConfiguration for the InstanceManager.
Task StartAsync(CancellationToken cancellationToken)
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager.
async Task Initialize(CancellationToken cancellationToken)
Initializes the InstanceManager.
async Task StopAsync(CancellationToken cancellationToken)
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceManager.
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager.
readonly SemaphoreSlim instanceStateChangeSemaphore
SemaphoreSlim used to guard calls to OnlineInstance(Models.Instance, CancellationToken) and OfflineIn...
readonly IServerControl serverControl
The IServerControl for the InstanceManager.
async Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters . A Task<TResult> resulting in the BridgeResponse for the request o...
readonly IServerPortProvider serverPortProvider
The IServerPortProvider for the InstanceManager.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager.
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager.
readonly CancellationTokenSource shutdownCancellationTokenSource
The CancellationTokenSource linked with the token given to StopAsync(CancellationToken).
readonly Dictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
Task startupTask
The Task returned by Initialize(CancellationToken).
Parameters for a bridge request.
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance.
General configuration options.
void CheckCompatibility(ILogger logger)
Validates the current ConfigVersion's compatibility and provides migration instructions.
Configuration for the server swarm system.
ApiController for managing Components.Instances.
const string InstanceAttachFileName
File name to allow attaching instances.
Extension methods for the Socket class.
static void BindTest(ushort port, bool includeIPv6)
Attempt to exclusively bind to a given port .
IIOManager that resolves paths to Environment.CurrentDirectory.
const string CurrentDirectory
Path to the current working directory for the IIOManager.
Represents an Api.Models.Instance in the database.
Wrapper for managing some TWrapped .
TWrapped Instance
The TWrapped .
Task OnZeroReferences
A Task that completes when there are no TReference s active for the Instance.
Async lock context helper.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken)
Asyncronously locks a semaphore .
For interacting with the instance services.
Provider for IInstanceCores.
Factory for creating IInstances.
Task< IInstance > CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
Create an IInstance.
IIOManager CreateGameIOManager(Models.Instance metadata)
Create an IIOManager that resolves to the "Game" directory of the Models.Instance defined by metadata...
Component version of IInstanceCore.
Controller version of IInstanceCore.
Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
DMApiParameters DMApiParameters
The DMApiParameters for the IBridgeHandler.
Registers IBridgeHandlers.
Represents a registration of an interop session.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...
Task Die(Exception exception)
Kill the server with a fatal exception.
Provides access to the server's HttpApiPort.
ushort HttpApiPort
The port the server listens on.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
Task UseContext(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext.
Interface for using filesystems.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task MoveDirectory(string source, string destination, CancellationToken cancellationToken)
Moves a directory at source to destination .
Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task< Job > CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken)
Cancels a give job .
The service that manages everything to do with jobs.
void Activate(IInstanceCoreProvider instanceCoreProvider)
Activate the IJobManager.
Factory for ISystemIdentitys.
ISystemIdentity GetCurrent()
Retrieves a ISystemIdentity representing the user executing tgstation-server.
Start and stop controllers for a swarm service.
Task< SwarmRegistrationResult > Initialize(CancellationToken cancellationToken)
Attempt to register with the swarm controller if not one, sets up the database otherwise.
Task Shutdown(CancellationToken cancellationToken)
Deregister with the swarm controller or put clients into querying state.
For waiting asynchronously.
Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .
SwarmRegistrationResult
Result of attempting to register with a swarm controller.