2using System.Collections.Generic;
5using System.Threading.Tasks;
7using Microsoft.EntityFrameworkCore;
8using Microsoft.Extensions.Hosting;
9using Microsoft.Extensions.Logging;
10using Microsoft.Extensions.Options;
108 readonly ILogger<InstanceManager>
logger;
113 readonly Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>
instances;
209 IMetricFactory metricFactory,
210 ICollectorRegistry collectorRegistry,
211 IOptions<GeneralConfiguration> generalConfigurationOptions,
212 IOptions<SwarmConfiguration> swarmConfigurationOptions,
213 IOptions<InternalConfiguration> internalConfigurationOptions,
214 ILogger<InstanceManager>
logger)
226 this.console =
console ??
throw new ArgumentNullException(nameof(
console));
228 ArgumentNullException.ThrowIfNull(metricFactory);
229 ArgumentNullException.ThrowIfNull(collectorRegistry);
230 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
231 swarmConfiguration = swarmConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(swarmConfigurationOptions));
232 internalConfiguration = internalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(internalConfigurationOptions));
233 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
237 onlineInstances = metricFactory.CreateGauge(
"tgs_online_instances",
"The total number of instances online");
239 instances =
new Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>();
241 readyTcs =
new TaskCompletionSource();
246 collectorRegistry.AddBeforeCollectCallback(async cancellationToken =>
249 foreach (var container
in instances.Values)
250 container.Instance.Watchdog.RunMetricsScrape();
265 await instanceKvp.Value.Instance.DisposeAsync();
271 logger.LogInformation(
"Server shutdown");
277 ArgumentNullException.ThrowIfNull(metadata);
281 if (!
instances.TryGetValue(metadata.Require(x => x.Id), out var instance))
284 return instance.AddReference();
289 public async ValueTask
MoveInstance(Models.Instance instance,
string oldPath, CancellationToken cancellationToken)
291 ArgumentNullException.ThrowIfNull(oldPath);
295 if (instanceReferenceCheck !=
null)
296 throw new InvalidOperationException(
"Cannot move an online instance!");
297 var newPath = instance.Path!;
310 "Error moving instance {instanceId}!",
314 logger.LogDebug(
"Reverting instance {instanceId}'s path to {oldPath} in the DB...", instance.Id, oldPath);
323 db.Instances.Attach(targetInstance);
324 targetInstance.Path = oldPath;
325 return db.Save(CancellationToken.None);
332 "Error reverting database after failing to move instance {instanceId}! Attempting to detach...",
341 CancellationToken.None);
347 "Okay, what gamma radiation are you under? Failed to write instance attach file!");
349 throw new AggregateException(tripleEx, innerEx, ex);
352 throw new AggregateException(ex, innerEx);
360 public async ValueTask
OfflineInstance(Models.Instance metadata,
User user, CancellationToken cancellationToken)
362 ArgumentNullException.ThrowIfNull(metadata);
367 var instanceId = metadata.Require(x => x.Id);
370 if (!
instances.TryGetValue(instanceId, out container))
372 logger.LogDebug(
"Not offlining removed instance {instanceId}", metadata.Id);
379 logger.LogInformation(
"Offlining instance ID {instanceId}", metadata.Id);
386 ValueTask<Job?[]> groupedTask =
default;
393 .Where(x => x.Instance!.Id == metadata.Id && !x.StoppedAt.HasValue)
394 .Select(x =>
new Job(x.Id!.Value))
395 .ToListAsync(cancellationToken);
420 await container.
Instance.DisposeAsync();
427 public async ValueTask
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
429 ArgumentNullException.ThrowIfNull(metadata);
431 var instanceId = metadata.Require(x => x.Id);
436 logger.LogDebug(
"Aborting instance creation due to it seemingly already being online");
440 logger.LogInformation(
"Onlining instance ID {instanceId} ({instanceName}) at {instancePath}", metadata.Id, metadata.Name, metadata.Path);
444 await instance.StartAsync(cancellationToken);
457 logger.LogError(
"Unable to commit onlined instance {instanceId} into service, offlining!", metadata.Id);
461 await instance.StopAsync(CancellationToken.None);
465 throw new AggregateException(innerEx, ex);
473 await instance.DisposeAsync();
483 return Task.CompletedTask;
487 public async Task
StopAsync(CancellationToken cancellationToken)
496 logger.LogWarning(
"InstanceManager was never started!");
500 logger.LogDebug(
"Stopping instance manager...");
504 logger.LogTrace(
"Interrupting startup task...");
509 var instanceFactoryStopTask =
instanceFactory.StopAsync(cancellationToken);
510 await
jobService.StopAsync(cancellationToken);
512 async ValueTask OfflineInstanceImmediate(
IInstance instance, CancellationToken cancellationToken)
516 await instance.StopAsync(cancellationToken);
520 logger.LogError(ex,
"Instance shutdown exception!");
525 await instanceFactoryStopTask;
537 logger.LogCritical(ex,
"Instance manager stop exception!");
544 ArgumentNullException.ThrowIfNull(parameters);
547 if (accessIdentifier ==
null)
549 logger.LogWarning(
"Received invalid bridge request with null access identifier!");
554 var loggedDelay =
false;
555 for (var i = 0; bridgeHandler ==
null && i < 30; ++i)
559 Task delayTask = Task.CompletedTask;
561 if (!
bridgeHandlers.TryGetValue(accessIdentifier, out bridgeHandler))
565 logger.LogTrace(
"Received bridge request with unregistered access identifier \"{aid}\". Waiting up to 3 seconds for it to be registered...", accessIdentifier);
569 delayTask =
asyncDelayer.
Delay(TimeSpan.FromMilliseconds(100), cancellationToken).AsTask();
575 if (bridgeHandler ==
null)
577 if (!
bridgeHandlers.TryGetValue(accessIdentifier, out bridgeHandler))
579 logger.LogWarning(
"Received invalid bridge request with access identifier: {accessIdentifier}", accessIdentifier);
589 ArgumentNullException.ThrowIfNull(bridgeHandler);
591 var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier
592 ??
throw new InvalidOperationException(
"Attempted bridge registration with null AccessIdentifier!");
596 logger.LogTrace(
"Registered bridge handler: {accessIdentifier}", accessIdentifier);
604 logger.LogTrace(
"Unregistered bridge handler: {accessIdentifier}", accessIdentifier);
614 instances.TryGetValue(metadata.Require(x => x.Id), out var container);
615 return container?.Instance;
638 List<Models.Instance>? dbInstances =
null;
641 => dbInstances = await databaseContext
645 .Include(x => x.RepositorySettings)
646 .Include(x => x.ChatSettings)
647 .ThenInclude(x => x.Channels)
648 .Include(x => x.DreamDaemonSettings)
649 .ToListAsync(cancellationToken);
654 var jobManagerStartup =
jobService.StartAsync(cancellationToken);
656 await Task.WhenAll(instanceEnumeration.AsTask(), factoryStartup, jobManagerStartup);
658 var instanceOnliningTasks = dbInstances!.Select(
667 logger.LogError(ex,
"Failed to online instance {instanceId}!", metadata.Id);
671 await Task.WhenAll(instanceOnliningTasks);
673 logger.LogInformation(
"Server ready!");
679 catch (OperationCanceledException ex)
681 logger.LogInformation(ex,
"Cancelled instance manager initialization!");
685 logger.LogCritical(e,
"Instance manager startup error!");
693 logger.LogCritical(e2,
"Failed to kill server!");
703 logger.LogDebug(
"Running as user: {username}", Environment.UserName);
709 if (!systemIdentity.CanCreateSymlinks)
710 throw new InvalidOperationException($
"The user running {Constants.CanonicalPackageName} cannot create symlinks! Please try running as an administrative user!");
714 logger.LogWarning(
"TGS is being run as the root account. This is not recommended.");
736 throw new InvalidOperationException(
"Swarm private key does not match the swarm controller's!");
739 throw new InvalidOperationException(
"Swarm controller's TGS version does not match our own!");
string? Identifier
The server's identifier.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobService jobService, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IServerPortProvider serverPortProvider, ISwarmServiceController swarmServiceController, IConsole console, IPlatformIdentifier platformIdentifier, IMetricFactory metricFactory, ICollectorRegistry collectorRegistry, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SwarmConfiguration > swarmConfigurationOptions, IOptions< InternalConfiguration > internalConfigurationOptions, ILogger< InstanceManager > logger)
Initializes a new instance of the InstanceManager class.
Task Ready
Task that completes when the IInstanceManager finishes initializing.
readonly TaskCompletionSource readyTcs
The TaskCompletionSource for Ready.
readonly Dictionary< long, ReferenceCountingContainer< IInstance, InstanceWrapper > > instances
Map of instance EntityId.Ids to the respective ReferenceCountingContainer<TWrapped,...
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the InstanceManager.
readonly IJobService jobService
The IJobService for the InstanceManager.
async ValueTask OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance.A ValueTask representing the running operation.
readonly InternalConfiguration internalConfiguration
The InternalConfiguration for the InstanceManager.
readonly Gauge onlineInstances
The count of online instances.
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).
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .A representative IBridgeRegistration.
void PreflightChecks()
Check we have a valid system and configuration.
Task? startupTask
The Task returned by Initialize(CancellationToken).
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.
IInstanceReference? GetInstanceReference(Api.Models.Instance metadata)
Get the IInstanceReference associated with given metadata .The IInstance associated with the given me...
async ValueTask MoveInstance(Models.Instance instance, string oldPath, CancellationToken cancellationToken)
Move an IInstance.A ValueTask representing the running operation.
async ValueTask< BridgeResponse?> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .A ValueTask<TResult> resulting in the BridgeResponse for the reque...
readonly IServerPortProvider serverPortProvider
The IServerPortProvider for the InstanceManager.
readonly IConsole console
The IConsole for the InstanceManager.
readonly? string originalConsoleTitle
The original IConsole.Title of console.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager.
IInstanceCore? GetInstance(Models.Instance metadata)
Get the IInstanceCore for a given instance if it's online.The IInstanceCore if it is online,...
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager.
async ValueTask InitializeSwarm(CancellationToken cancellationToken)
Initializes the connection to the TGS swarm.
readonly CancellationTokenSource shutdownCancellationTokenSource
The CancellationTokenSource linked with the token given to StopAsync(CancellationToken).
async ValueTask OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken)
Offline an IInstance.A ValueTask representing the running operation.
readonly Dictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
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.
Unstable configuration options used internally by TGS.
bool UsingDocker
If the server is running inside of a Docker container.
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(IPlatformIdentifier platformIdentifier, ushort port, bool includeIPv6, bool udp)
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, ILogger? logger=null)
Asyncronously locks a semaphore .
For interacting with the instance services.
Provider for IInstanceCores.
Factory for creating IInstances.
IIOManager CreateGameIOManager(Models.Instance metadata)
Create an IIOManager that resolves to the "Game" directory of the Models.Instance defined by metadata...
ValueTask< IInstance > CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
Create an IInstance.
Component version of IInstanceCore.
Controller version of IInstanceCore.
ValueTask< BridgeResponse?> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
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...
ValueTask 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.
ValueTask UseContextTaskReturn(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext.
ValueTask UseContext(Func< IDatabaseContext, ValueTask > operation)
Run an operation in the scope of an IDatabaseContext.
IDatabaseCollection< Instance > Instances
The Instances in the IDatabaseContext.
Abstraction for global::System.Console.
string? Title
Gets or sets the IConsole window's title. Can return null if getting the console title is not support...
void SetTitle(string newTitle)
Sets a newTitle console window.
Interface for using filesystems.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
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 .
ValueTask< 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.
ValueTask Shutdown(CancellationToken cancellationToken)
Deregister with the swarm controller or put clients into querying state.
ValueTask< SwarmRegistrationResult > Initialize(CancellationToken cancellationToken)
Attempt to register with the swarm controller if not one, sets up the database otherwise.
For waiting asynchronously.
ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .
SwarmRegistrationResult
Result of attempting to register with a swarm controller.