/// <inheritdoc />
        public void ReplaceDmbProvider(IDmbProvider dmbProvider)
        {
            var oldDmb = reattachInformation.Dmb;

            reattachInformation.Dmb = dmbProvider ?? throw new ArgumentNullException(nameof(dmbProvider));
            oldDmb.Dispose();
        }
        /// <inheritdoc />
        protected sealed override async Task <IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
        {
            if (ActiveSwappable != null)
            {
                throw new InvalidOperationException("Expected activeSwappable to be null!");
            }
            if (startupDmbProvider != null)
            {
                throw new InvalidOperationException("Expected startupDmbProvider to be null!");
            }

            Logger.LogTrace("Prep for server launch. pendingSwappable is {0}available", pendingSwappable == null ? "not " : String.Empty);

            // Add another lock to the startup DMB because it'll be used throughout the lifetime of the watchdog
            startupDmbProvider = await DmbFactory.FromCompileJob(dmbToUse.CompileJob, cancellationToken).ConfigureAwait(false);

            pendingSwappable ??= new SwappableDmbProvider(dmbToUse, GameIOManager, symlinkFactory);
            ActiveSwappable  = pendingSwappable;
            pendingSwappable = null;

            try
            {
                await InitialLink(cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // We won't worry about disposing activeSwappable here as we can't dispose dmbToUse here.
                ActiveSwappable = null;
                throw;
            }

            return(ActiveSwappable);
        }
Example #3
0
        /// <inheritdoc />
        public async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

            CompileJob finalCompileJob = null;
            //now load the entire compile job tree
            await databaseContextFactory.UseContext(async db => finalCompileJob = await db.CompileJobs.Where(x => x.Id == job.Id)
                                                    .Include(x => x.Job).ThenInclude(x => x.StartedBy)
                                                    .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
                                                    .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
                                                    .FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); //can't wait to see that query

            if (finalCompileJob == null)
            {
                //lol git f****d
                return;
            }

            var newProvider = await FromCompileJob(finalCompileJob, cancellationToken).ConfigureAwait(false);

            if (newProvider == null)
            {
                return;
            }
            lock (this)
            {
                nextDmbProvider?.Dispose();
                nextDmbProvider = newProvider;
                newerDmbTcs.SetResult(nextDmbProvider);
                newerDmbTcs = new TaskCompletionSource <object>();
            }
        }
Example #4
0
        /// <inheritdoc />
        public async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

            var newProvider = await FromCompileJob(job, cancellationToken).ConfigureAwait(false);

            if (newProvider == null)
            {
                return;
            }

            // Do this first, because it's entirely possible when we set the tcs it will immediately need to be applied
            if (started)
            {
                await gitHubDeploymentManager.StageDeployment(
                    newProvider.CompileJob,
                    cancellationToken)
                .ConfigureAwait(false);
            }

            lock (jobLockCounts)
            {
                nextDmbProvider?.Dispose();
                nextDmbProvider = newProvider;

                // Oh god dammit
                var temp = newerDmbTcs;
                newerDmbTcs = new TaskCompletionSource <object>();
                temp.SetResult(nextDmbProvider);
            }
        }
Example #5
0
        /// <inheritdoc />
        protected override async Task HandleNewDmbAvailable(CancellationToken cancellationToken)
        {
            IDmbProvider compileJobProvider             = DmbFactory.LockNextDmb(1);
            WindowsSwappableDmbProvider windowsProvider = null;

            try
            {
                windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory);

                Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id);
                Server.Suspend();
                await windowsProvider.MakeActive(cancellationToken).ConfigureAwait(false);

                Server.Resume();
            }
            catch
            {
                IDmbProvider providerToDispose = windowsProvider ?? compileJobProvider;
                providerToDispose.Dispose();
                throw;
            }

            pendingSwappable?.Dispose();
            pendingSwappable = windowsProvider;
        }
Example #6
0
        /// <summary>
        /// Construct a <see cref="ReattachInformation"/> from a given <paramref name="copy"/> and <paramref name="dmb"/>
        /// </summary>
        /// <param name="copy">The <see cref="Models.ReattachInformation"/> to copy values from</param>
        /// <param name="dmb">The value of <see cref="Dmb"/></param>
        /// <param name="topicRequestTimeout">The value of <see cref="TopicRequestTimeout"/>.</param>
        public ReattachInformation(
            Models.ReattachInformation copy,
            IDmbProvider dmb,
            TimeSpan topicRequestTimeout) : base(copy)
        {
            Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb));
            TopicRequestTimeout = topicRequestTimeout;

            runtimeInformationLock = new object();
        }
        /// <inheritdoc />
        protected override async Task HandleNewDmbAvailable(CancellationToken cancellationToken)
        {
            IDmbProvider compileJobProvider = DmbFactory.LockNextDmb(1);

            if (compileJobProvider.CompileJob.ByondVersion != ActiveCompileJob.ByondVersion)
            {
                // have to do a graceful restart
                Logger.LogDebug(
                    "Not swapping to new compile job {0} as it uses a different BYOND version ({1}) than what is currently active {2}. Queueing graceful restart instead...",
                    compileJobProvider.CompileJob.Id,
                    compileJobProvider.CompileJob.ByondVersion,
                    ActiveCompileJob.ByondVersion);
                compileJobProvider.Dispose();
                await base.HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false);

                return;
            }

            WindowsSwappableDmbProvider windowsProvider = null;
            bool suspended = false;

            try
            {
                windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory);

                Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id);
                try
                {
                    Server.Suspend();
                    suspended = true;
                }
                catch (Exception ex)
                {
                    Logger.LogWarning("Exception while suspending server: {0}", ex);
                }

                await windowsProvider.MakeActive(cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Logger.LogError("Exception while swapping: {0}", ex);
                IDmbProvider providerToDispose = windowsProvider ?? compileJobProvider;
                providerToDispose.Dispose();
                throw;
            }

            // Let this throw hard if it fails
            if (suspended)
            {
                Server.Resume();
            }

            pendingSwappable?.Dispose();
            pendingSwappable = windowsProvider;
        }
Example #8
0
 /// <summary>
 /// Construct a <see cref="WatchdogReattachInformation"/> from a given <paramref name="copy"/> with a given <paramref name="dmbAlpha"/> and <paramref name="dmbBravo"/>
 /// </summary>
 /// <param name="copy">The <see cref="WatchdogReattachInformationBase"/> to copy information from</param>
 /// <param name="dmbAlpha">The <see cref="IDmbProvider"/> used to build <see cref="Alpha"/></param>
 /// <param name="dmbBravo">The <see cref="IDmbProvider"/> used to build <see cref="Bravo"/></param>
 public WatchdogReattachInformation(Models.WatchdogReattachInformation copy, IDmbProvider dmbAlpha, IDmbProvider dmbBravo) : base(copy)
 {
     if (copy.Alpha != null)
     {
         Alpha = new ReattachInformation(copy.Alpha, dmbAlpha);
     }
     if (copy.Bravo != null)
     {
         Bravo = new ReattachInformation(copy.Bravo, dmbBravo);
     }
 }
        /// <inheritdoc />
        protected override async Task DisposeAndNullControllersImpl()
        {
            await base.DisposeAndNullControllersImpl().ConfigureAwait(false);

            // If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again.
            ActiveSwappable = null;
            pendingSwappable?.Dispose();
            pendingSwappable = null;

            startupDmbProvider?.Dispose();
            startupDmbProvider = null;
        }
Example #10
0
        /// <inheritdoc />
        public void ReplaceDmbProvider(IDmbProvider dmbProvider)
        {
#pragma warning disable IDE0016 // Use 'throw' expression
            if (dmbProvider == null)
            {
                throw new ArgumentNullException(nameof(dmbProvider));
            }
#pragma warning restore IDE0016 // Use 'throw' expression

            reattachInformation.Dmb.Dispose();
            reattachInformation.Dmb = dmbProvider;
        }
        /// <inheritdoc />
        protected override void DisposeAndNullControllersImpl()
        {
            base.DisposeAndNullControllersImpl();

            // If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again.
            activeSwappable = null;
            pendingSwappable?.Dispose();
            pendingSwappable = null;

            startupDmbProvider?.Dispose();
            startupDmbProvider = null;
        }
Example #12
0
        /// <inheritdoc />
        protected override async Task InitControllers(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
        {
            try
            {
                await base.InitControllers(chatTask, reattachInfo, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                // Then we move it back and apply the symlink
                if (hardLinkedDmb != null)
                {
                    try
                    {
                        Logger.LogTrace("Unhardlinking compile job...");
                        Server?.Suspend();
                        var hardLink         = hardLinkedDmb.Directory;
                        var originalPosition = hardLinkedDmb.CompileJob.DirectoryName.ToString();
                        await GameIOManager.MoveDirectory(
                            hardLink,
                            originalPosition,
                            default)
                        .ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(
                            ex,
                            "Failed to un-hard link compile job #{0} ({1})",
                            hardLinkedDmb.CompileJob.Id,
                            hardLinkedDmb.CompileJob.DirectoryName);
                    }

                    hardLinkedDmb = null;
                }
            }

            if (reattachInfo != null)
            {
                Logger.LogTrace("Skipping symlink due to reattach");
                return;
            }

            Logger.LogTrace("Symlinking compile job...");
            await ActiveSwappable.MakeActive(cancellationToken).ConfigureAwait(false);

            Server.Resume();
        }
Example #13
0
        /// <inheritdoc />
        public async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

            var newProvider = await FromCompileJob(job, cancellationToken).ConfigureAwait(false);

            if (newProvider == null)
            {
                return;
            }
            lock (this)
            {
                nextDmbProvider?.Dispose();
                nextDmbProvider = newProvider;
                newerDmbTcs.SetResult(nextDmbProvider);
                newerDmbTcs = new TaskCompletionSource <object>();
            }
        }
Example #14
0
        /// <inheritdoc />
        protected override async Task <IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
        {
            Debug.Assert(activeSwappable == null, "Expected swappableDmbProvider to be null!");

            Logger.LogTrace("Prep for server launch. pendingSwappable is {0}avaiable", pendingSwappable == null ? "not " : String.Empty);

            activeSwappable  = pendingSwappable ?? new WindowsSwappableDmbProvider(dmbToUse, ioManager, symlinkFactory);
            pendingSwappable = null;

            try
            {
                await activeSwappable.MakeActive(cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // We won't worry about disposing activeSwappable here as we can't dispose dmbToUse here.
                activeSwappable = null;
                throw;
            }

            return(activeSwappable);
        }
Example #15
0
        /// <summary>
        /// Initializes a new isntance of the <see cref="ReattachInformation"/> <see langword="class"/>.
        /// </summary>
        /// <param name="dmb">The value of <see cref="Dmb"/>.</param>
        /// <param name="process">The <see cref="IProcess"/> used to get the <see cref="ReattachInformationBase.ProcessId"/>.</param>
        /// <param name="runtimeInformation">The value of <see cref="RuntimeInformation"/>.</param>
        /// <param name="accessIdentifier">The value of <see cref="Interop.DMApiParameters.AccessIdentifier"/>.</param>
        /// <param name="port">The value of <see cref="ReattachInformationBase.Port"/>.</param>
        internal ReattachInformation(
            IDmbProvider dmb,
            IProcess process,
            RuntimeInformation runtimeInformation,
            string accessIdentifier,
            ushort port)
        {
            Dmb                = dmb ?? throw new ArgumentNullException(nameof(dmb));
            ProcessId          = process?.Id ?? throw new ArgumentNullException(nameof(process));
            RuntimeInformation = runtimeInformation ?? throw new ArgumentNullException(nameof(runtimeInformation));
            if (!runtimeInformation.SecurityLevel.HasValue)
            {
                throw new ArgumentException("runtimeInformation must have a valid SecurityLevel!", nameof(runtimeInformation));
            }

            AccessIdentifier = accessIdentifier ?? throw new ArgumentNullException(nameof(accessIdentifier));

            LaunchSecurityLevel = runtimeInformation.SecurityLevel.Value;
            Port = port;

            runtimeInformationLock = new object();
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="RuntimeInformation"/> <see langword="class"/>.
        /// </summary>
        /// <param name="chatTrackingContext">The <see cref="IChatTrackingContext"/> to use.</param>
        /// <param name="dmbProvider">The <see cref="IDmbProvider"/> to get revision information from.</param>
        /// <param name="serverVersion">The value of <see cref="ServerVersion"/>.</param>
        /// <param name="instanceName">The value of <see cref="InstanceName"/>.</param>
        /// <param name="securityLevel">The value of <see cref="SecurityLevel"/>.</param>
        /// <param name="serverPort">The value of <see cref="ServerPort"/>.</param>
        /// <param name="apiValidateOnly">The value of <see cref="ApiValidateOnly"/>.</param>
        public RuntimeInformation(
            IChatTrackingContext chatTrackingContext,
            IDmbProvider dmbProvider,
            Version serverVersion,
            string instanceName,
            DreamDaemonSecurity?securityLevel,
            ushort serverPort,
            bool apiValidateOnly)
            : base(chatTrackingContext?.Channels ?? throw new ArgumentNullException(nameof(chatTrackingContext)))
        {
            if (dmbProvider == null)
            {
                throw new ArgumentNullException(nameof(dmbProvider));
            }

            ServerVersion = serverVersion ?? throw new ArgumentNullException(nameof(serverVersion));

            Revision = new Api.Models.Internal.RevisionInformation
            {
                CommitSha       = dmbProvider.CompileJob.RevisionInformation.CommitSha,
                Timestamp       = dmbProvider.CompileJob.RevisionInformation.Timestamp,
                OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha
            };

            TestMerges = (IReadOnlyCollection <TestMergeInformation>)dmbProvider
                         .CompileJob
                         .RevisionInformation
                         .ActiveTestMerges?
                         .Select(x => x.TestMerge)
                         .Select(x => new TestMergeInformation(x, Revision))
                         .ToList()
                         ?? Array.Empty <TestMergeInformation>();

            InstanceName    = instanceName ?? throw new ArgumentNullException(nameof(instanceName));
            SecurityLevel   = securityLevel;
            ServerPort      = serverPort;
            ApiValidateOnly = apiValidateOnly;
        }
Example #17
0
        /// <inheritdoc />
        protected override async Task InitialLink(CancellationToken cancellationToken)
        {
            // The logic to check for an active live directory is in SwappableDmbProvider, so we just do it again here for safety
            Logger.LogTrace("Hard linking compile job...");

            // Symlinks are counted as a file on linux??
            if (await GameIOManager.DirectoryExists(ActiveSwappable.Directory, cancellationToken).ConfigureAwait(false))
            {
                await GameIOManager.DeleteDirectory(ActiveSwappable.Directory, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                await GameIOManager.DeleteFile(ActiveSwappable.Directory, cancellationToken).ConfigureAwait(false);
            }

            // Instead of symlinking to begin with we actually rename the directory
            await GameIOManager.MoveDirectory(
                ActiveSwappable.CompileJob.DirectoryName.ToString(),
                ActiveSwappable.Directory,
                cancellationToken)
            .ConfigureAwait(false);

            hardLinkedDmb = ActiveSwappable;
        }
 /// <summary>
 /// Prepare the server to launch a new instance with the <see cref="WatchdogBase.ActiveLaunchParameters"/> and a given <paramref name="dmbToUse"/>.
 /// </summary>
 /// <param name="dmbToUse">The <see cref="IDmbProvider"/> to be launched. Will not be disposed by this function.</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
 /// <returns>A <see cref="Task{TResult}"/> resulting in the modified <see cref="IDmbProvider"/> to be used.</returns>
 protected virtual Task <IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse);
Example #19
0
        /// <summary>
        /// Construct a <see cref="ReattachInformation"/> from a given <paramref name="copy"/> and <paramref name="dmb"/>
        /// </summary>
        /// <param name="copy">The <see cref="Models.ReattachInformation"/> to copy values from</param>
        /// <param name="dmb">The value of <see cref="Dmb"/></param>
        public ReattachInformation(Models.ReattachInformation copy, IDmbProvider dmb) : base(copy)
        {
            Dmb = dmb ?? throw new ArgumentNullException(nameof(dmb));

            runtimeInformationLock = new object();
        }
Example #20
0
 /// <summary>
 /// Initializes a new instance of the <see cref="WindowsSwappableDmbProvider"/> <see langword="class"/>.
 /// </summary>
 /// <param name="baseProvider">The value of <see cref="baseProvider"/>.</param>
 /// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
 /// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param>
 public WindowsSwappableDmbProvider(IDmbProvider baseProvider, IIOManager ioManager, ISymlinkFactory symlinkFactory)
 {
     this.baseProvider   = baseProvider ?? throw new ArgumentNullException(nameof(baseProvider));
     this.ioManager      = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
     this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
 }
 /// <inheritdoc />
 public ISessionController CreateDeadSession(IDmbProvider dmbProvider) => new DeadSessionController(dmbProvider);
        /// <inheritdoc />
                #pragma warning disable CA1506 // TODO: Decomplexify
        public async Task <ISessionController> LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken)
        {
            var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort;

            if (!portToUse.HasValue)
            {
                throw new InvalidOperationException("Given port is null!");
            }
            var accessIdentifier = cryptographySuite.GetSecureString();

            const string JsonPostfix = "tgs.json";

            var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory;

            // delete all previous tgs json files
            var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false);

            await Task.WhenAll(files.Select(x => ioManager.DeleteFile(x, cancellationToken))).ConfigureAwait(false);

            // i changed this back from guids, hopefully i don't regret that
            string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix);

            var securityLevelToUse = launchParameters.SecurityLevel.Value;

            switch (dmbProvider.CompileJob.MinimumSecurityLevel)
            {
            case DreamDaemonSecurity.Ultrasafe:
                break;

            case DreamDaemonSecurity.Safe:
                if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe)
                {
                    securityLevelToUse = DreamDaemonSecurity.Safe;
                }
                break;

            case DreamDaemonSecurity.Trusted:
                securityLevelToUse = DreamDaemonSecurity.Trusted;
                break;

            default:
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel));
            }

            // setup interop files
            var interopInfo = new JsonFile
            {
                AccessIdentifier   = accessIdentifier,
                ApiValidateOnly    = apiValidate,
                ChatChannelsJson   = JsonFile("chat_channels"),
                ChatCommandsJson   = JsonFile("chat_commands"),
                ServerCommandsJson = JsonFile("server_commands"),
                InstanceName       = instance.Name,
                SecurityLevel      = securityLevelToUse,
                Revision           = new Api.Models.Internal.RevisionInformation
                {
                    CommitSha       = dmbProvider.CompileJob.RevisionInformation.CommitSha,
                    OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha
                }
            };

            interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x, interopInfo.Revision)));

            var interopJsonFile = JsonFile("interop");

            var interopJson = JsonConvert.SerializeObject(interopInfo, new JsonSerializerSettings
            {
                ContractResolver      = new CamelCasePropertyNamesContractResolver(),
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            });

            var chatJsonTrackingTask = chat.TrackJsons(basePath, interopInfo.ChatChannelsJson, interopInfo.ChatCommandsJson, cancellationToken);

            await ioManager.WriteAllBytes(ioManager.ConcatPath(basePath, interopJsonFile), Encoding.UTF8.GetBytes(interopJson), cancellationToken).ConfigureAwait(false);

            var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false);

            try
            {
                // get the byond lock
                var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);

                try
                {
                    // create interop context
                    var context = new CommContext(ioManager, loggerFactory.CreateLogger <CommContext>(), basePath, interopInfo.ServerCommandsJson);
                    try
                    {
                        // set command line options
                        // more sanitization here cause it uses the same scheme
                        var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson));

                        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} -{5} -public -params \"{4}\"",
                                                      dmbProvider.DmbName,
                                                      primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort,
                                                      launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty,
                                                      SecurityWord(securityLevelToUse),
                                                      parameters,
                                                      visibility);

                        // See https://github.com/tgstation/tgstation-server/issues/719
                        var noShellExecute = !platformIdentifier.IsWindows;

                        // launch dd
                        var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute);
                        try
                        {
                            networkPromptReaper.RegisterProcess(process);

                            // return the session controller for it
                            var result = new SessionController(new ReattachInformation
                            {
                                AccessIdentifier = accessIdentifier,
                                Dmb                = dmbProvider,
                                IsPrimary          = primaryDirectory,
                                Port               = portToUse.Value,
                                ProcessId          = process.Id,
                                ChatChannelsJson   = interopInfo.ChatChannelsJson,
                                ChatCommandsJson   = interopInfo.ChatCommandsJson,
                                ServerCommandsJson = interopInfo.ServerCommandsJson,
                            }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger <SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout);

                            // writeback launch parameter's fixed security level
                            launchParameters.SecurityLevel = securityLevelToUse;

                            return(result);
                        }
                        catch
                        {
                            process.Dispose();
                            throw;
                        }
                    }
                    catch
                    {
                        context.Dispose();
                        throw;
                    }
                }
                catch
                {
                    if (currentByondLock == null)
                    {
                        byondLock.Dispose();
                    }
                    throw;
                }
            }
            catch
            {
                chatJsonTrackingContext.Dispose();
                throw;
            }
        }