/// <inheritdoc /> public Task Save(ReattachInformation reattachInformation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async(db) => { if (reattachInformation == null) { throw new ArgumentNullException(nameof(reattachInformation)); } logger.LogDebug("Saving reattach information: {0}...", reattachInformation); await db .ReattachInformations .AsQueryable() .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id) .DeleteAsync(cancellationToken) .ConfigureAwait(false); var dbReattachInfo = new Models.ReattachInformation { AccessIdentifier = reattachInformation.AccessIdentifier, CompileJobId = reattachInformation.Dmb.CompileJob.Id, Port = reattachInformation.Port, ProcessId = reattachInformation.ProcessId, RebootState = reattachInformation.RebootState, LaunchSecurityLevel = reattachInformation.LaunchSecurityLevel }; db.ReattachInformations.Add(dbReattachInfo); await db.Save(cancellationToken).ConfigureAwait(false); });
/// <summary> /// Construct a <see cref="SessionController"/> /// </summary> /// <param name="reattachInformation">The value of <see cref="reattachInformation"/></param> /// <param name="process">The value of <see cref="process"/></param> /// <param name="byondLock">The value of <see cref="byondLock"/></param> /// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param> /// <param name="bridgeRegistrar">The <see cref="IBridgeRegistrar"/> used to populate <see cref="bridgeRegistration"/>.</param> /// <param name="chat">The value of <see cref="chat"/></param> /// <param name="chatTrackingContext">The value of <see cref="chatTrackingContext"/></param> /// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="SessionController"/>.</param> /// <param name="logger">The value of <see cref="logger"/></param> /// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param> /// <param name="reattached">If this is a reattached session.</param> public SessionController( ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, ITopicClient byondTopicSender, IChatTrackingContext chatTrackingContext, IBridgeRegistrar bridgeRegistrar, IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, ILogger <SessionController> logger, uint?startupTimeout, bool reattached) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.process = process ?? throw new ArgumentNullException(nameof(process)); this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.chatTrackingContext = chatTrackingContext ?? throw new ArgumentNullException(nameof(chatTrackingContext)); bridgeRegistration = bridgeRegistrar?.RegisterHandler(this) ?? throw new ArgumentNullException(nameof(bridgeRegistrar)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.chatTrackingContext.SetChannelSink(this); portClosedForReboot = false; disposed = false; apiValidationStatus = ApiValidationStatus.NeverValidated; released = false; rebootTcs = new TaskCompletionSource <object>(); primeTcs = new TaskCompletionSource <object>(); reattachTopicCts = new CancellationTokenSource(); synchronizationLock = new object(); _ = process.Lifetime.ContinueWith( x => { if (!disposed) { reattachTopicCts.Cancel(); } chatTrackingContext.Active = false; }, TaskScheduler.Current); LaunchResult = GetLaunchResult( assemblyInformationProvider, startupTimeout, reattached); logger.LogDebug("Created session controller. Primary: {0}, CommsKey: {1}, Port: {2}", IsPrimary, reattachInformation.AccessIdentifier, Port); }
/// <summary> /// Construct a <see cref="SessionController"/> /// </summary> /// <param name="reattachInformation">The value of <see cref="reattachInformation"/></param> /// <param name="metadata">The owning <see cref="Instance"/>.</param> /// <param name="process">The value of <see cref="process"/></param> /// <param name="byondLock">The value of <see cref="byondLock"/></param> /// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param> /// <param name="bridgeRegistrar">The <see cref="IBridgeRegistrar"/> used to populate <see cref="bridgeRegistration"/>.</param> /// <param name="chat">The value of <see cref="chat"/></param> /// <param name="chatTrackingContext">The value of <see cref="chatTrackingContext"/></param> /// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="SessionController"/>.</param> /// <param name="logger">The value of <see cref="logger"/></param> /// <param name="postLifetimeCallback">The <see cref="Func{TResult}"/> returning a <see cref="Task"/> to be run after the <paramref name="process"/> ends.</param> /// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param> /// <param name="reattached">If this is a reattached session.</param> /// <param name="apiValidate">If this is a DMAPI validation session.</param> public SessionController( ReattachInformation reattachInformation, Api.Models.Instance metadata, IProcess process, IByondExecutableLock byondLock, ITopicClient byondTopicSender, IChatTrackingContext chatTrackingContext, IBridgeRegistrar bridgeRegistrar, IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, ILogger <SessionController> logger, Func <Task> postLifetimeCallback, uint?startupTimeout, bool reattached, bool apiValidate) { this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation)); this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); this.process = process ?? throw new ArgumentNullException(nameof(process)); this.byondLock = byondLock ?? throw new ArgumentNullException(nameof(byondLock)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.chatTrackingContext = chatTrackingContext ?? throw new ArgumentNullException(nameof(chatTrackingContext)); if (bridgeRegistrar == null) { throw new ArgumentNullException(nameof(bridgeRegistrar)); } this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); portClosedForReboot = false; disposed = false; apiValidationStatus = ApiValidationStatus.NeverValidated; released = false; rebootTcs = new TaskCompletionSource <object>(); primeTcs = new TaskCompletionSource <object>(); initialBridgeRequestTcs = new TaskCompletionSource <object>(); reattachTopicCts = new CancellationTokenSource(); synchronizationLock = new object(); if (apiValidate || DMApiAvailable) { bridgeRegistration = bridgeRegistrar.RegisterHandler(this); this.chatTrackingContext.SetChannelSink(this); } else { logger.LogTrace( "Not registering session with {0} DMAPI version for interop!", reattachInformation.Dmb.CompileJob.DMApiVersion == null ? "no" : $"incompatible ({reattachInformation.Dmb.CompileJob.DMApiVersion})"); } async Task <int> WrapLifetime() { var exitCode = await process.Lifetime.ConfigureAwait(false); await postLifetimeCallback().ConfigureAwait(false); return(exitCode); } Lifetime = WrapLifetime(); LaunchResult = GetLaunchResult( assemblyInformationProvider, startupTimeout, reattached, apiValidate); logger.LogDebug( "Created session controller. CommsKey: {0}, Port: {1}", reattachInformation.AccessIdentifier, reattachInformation.Port); }
/// <inheritdoc /> public async Task <ReattachInformation> Load(CancellationToken cancellationToken) { Models.ReattachInformation result = null; TimeSpan?topicTimeout = null; await databaseContextFactory.UseContext(async (db) => { var dbReattachInfos = await db .ReattachInformations .AsQueryable() .Where(x => x.CompileJob.Job.Instance.Id == metadata.Id) .Include(x => x.CompileJob) .ToListAsync(cancellationToken).ConfigureAwait(false); result = dbReattachInfos.FirstOrDefault(); if (result == default) { return; } var timeoutMilliseconds = await db .Instances .AsQueryable() .Where(x => x.Id == metadata.Id) .Select(x => x.DreamDaemonSettings.TopicRequestTimeout) .FirstOrDefaultAsync(cancellationToken) .ConfigureAwait(false); if (timeoutMilliseconds == default) { logger.LogCritical("Missing TopicRequestTimeout!"); return; } topicTimeout = TimeSpan.FromMilliseconds(timeoutMilliseconds.Value); bool first = true; foreach (var reattachInfo in dbReattachInfos) { if (!first) { logger.LogWarning("Killing PID {0} associated with extra reattach information...", reattachInfo.ProcessId); try { using var process = processExecutor.GetProcess(reattachInfo.ProcessId); process.Terminate(); await process.Lifetime.ConfigureAwait(false); } catch (Exception ex) { logger.LogWarning(ex, "Failed to kill process!"); } } db.ReattachInformations.Remove(reattachInfo); first = false; } await db.Save(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); if (!topicTimeout.HasValue) { logger.LogDebug("Reattach information not found!"); return(null); } var dmb = await dmbFactory.FromCompileJob(result.CompileJob, cancellationToken).ConfigureAwait(false); if (dmb == null) { logger.LogError("Unable to reattach! Could not load .dmb!"); return(null); } var info = new ReattachInformation( result, dmb, topicTimeout.Value); logger.LogDebug("Reattach information loaded: {0}", info); return(info); }