File: Process\RunningProcess.cs
Web Access
Project: src\src\sdk\src\Dotnet.Watch\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.

namespace Microsoft.DotNet.Watch;

internal sealed class RunningProcess(
    int id,
    Task<int> task,
    CancellationTokenSource exitedSource,
    CancellationTokenSource terminationSource) : IAsyncDisposable
{
    private CancellationTokenSource? _terminationSource = terminationSource;

    /// <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 readonly CancellationToken ExitedCancellationToken = exitedSource.Token;

    public Task<int> Task => task;
    public int Id => id;

    ValueTask IAsyncDisposable.DisposeAsync()
        => DisposeAsync(isExiting: false);

    public async ValueTask DisposeAsync(bool isExiting)
    {
        var terminationSource = Interlocked.Exchange(ref _terminationSource, null);
        ObjectDisposedException.ThrowIf(terminationSource == null, this);

        // do not await process termination since it's already in progress:
        if (!isExiting)
        {
            terminationSource.Cancel();
            await task;
        }

        terminationSource.Dispose();

        exitedSource.Cancel();
        exitedSource.Dispose();
    }

    /// <summary>
    /// Terminates the process if it hasn't terminated yet.
    /// Awating the task triggers OnExit handlers, which in turn call <see cref="DisposeAsync"/>.
    /// </summary>
    public Task TerminateAsync()
    {
        _terminationSource?.Cancel();
        return task;
    }
}