File: Process\RunningProject.cs
Web Access
Project: ..\..\..\src\BuiltInTools\Watch\Microsoft.DotNet.HotReload.Watch.csproj (Microsoft.DotNet.HotReload.Watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
 
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Build.Graph;
using Microsoft.DotNet.HotReload;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.DotNet.Watch
{
    internal delegate ValueTask<RunningProject> RestartOperation(CancellationToken cancellationToken);
 
    internal sealed class RunningProject(
        ProjectGraphNode projectNode,
        ProjectOptions options,
        HotReloadClients clients,
        Task<int> runningProcess,
        int processId,
        CancellationTokenSource processExitedSource,
        CancellationTokenSource processTerminationSource,
        RestartOperation restartOperation,
        ImmutableArray<string> capabilities) : IDisposable
    {
        public readonly ProjectGraphNode ProjectNode = projectNode;
        public readonly ProjectOptions Options = options;
        public readonly HotReloadClients Clients = clients;
        public readonly ImmutableArray<string> Capabilities = capabilities;
        public readonly Task<int> RunningProcess = runningProcess;
        public readonly int ProcessId = processId;
        public readonly RestartOperation RestartOperation = restartOperation;
 
        /// <summary>
        /// Cancellation token triggered when the process exits.
        /// Stores the token to allow callers to use the token even after the source has been disposed.
        /// </summary>
        public CancellationToken ProcessExitedCancellationToken = processExitedSource.Token;
 
        /// <summary>
        /// Set to true when the process termination is being requested so that it can be restarted within
        /// the Hot Reload session (i.e. without restarting the root project).
        /// </summary>
        public bool IsRestarting => _isRestarting != 0;
 
        private volatile int _isRestarting;
        private volatile bool _isDisposed;
 
        /// <summary>
        /// Disposes the project. Can occur unexpectedly whenever the process exits.
        /// Must only be called once per project.
        /// </summary>
        public void Dispose()
        {
            ObjectDisposedException.ThrowIf(_isDisposed, this);
 
            _isDisposed = true;
            processExitedSource.Cancel();
 
            Clients.Dispose();
            processTerminationSource.Dispose();
            processExitedSource.Dispose();
        }
 
        /// <summary>
        /// Waits for the application process to start.
        /// Ensures that the build has been complete and the build outputs are available.
        /// Returns false if the process has exited before the connection was established.
        /// </summary>
        public async ValueTask<bool> WaitForProcessRunningAsync(CancellationToken cancellationToken)
        {
            using var processCommunicationCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ProcessExitedCancellationToken);
 
            try
            {
                await Clients.WaitForConnectionEstablishedAsync(processCommunicationCancellationSource.Token);
                return true;
            }
            catch (OperationCanceledException) when (ProcessExitedCancellationToken.IsCancellationRequested)
            {
                return false;
            }
        }
 
        /// <summary>
        /// Terminates the process if it hasn't terminated yet.
        /// </summary>
        public Task TerminateAsync()
        {
            if (!_isDisposed)
            {
                processTerminationSource.Cancel();
            }
 
            return RunningProcess;
        }
 
        /// <summary>
        /// Marks the <see cref="RunningProject"/> as restarting.
        /// Subsequent process termination will be treated as a restart.
        /// </summary>
        /// <returns>True if the project hasn't been int restarting state prior the call.</returns>
        public bool InitiateRestart()
            => Interlocked.Exchange(ref _isRestarting, 1) == 0;
 
        /// <summary>
        /// Terminates the process in preparation for a restart.
        /// </summary>
        public Task TerminateForRestartAsync()
        {
            InitiateRestart();
            return TerminateAsync();
        }
    }
}