Skip to content

Commit b46e1c0

Browse files
authored
Process termination (#49335)
2 parents abb61b8 + 50cacbd commit b46e1c0

19 files changed

+188
-149
lines changed

src/BuiltInTools/dotnet-watch/DotNetWatchContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ internal sealed class DotNetWatchContext
99
public required GlobalOptions Options { get; init; }
1010
public required EnvironmentOptions EnvironmentOptions { get; init; }
1111
public required IReporter Reporter { get; init; }
12+
public required ProcessRunner ProcessRunner { get; init; }
1213

1314
public required ProjectOptions RootProjectOptions { get; init; }
1415
}

src/BuiltInTools/dotnet-watch/DotNetWatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
8181

8282
fileSetWatcher.WatchContainingDirectories(evaluationResult.Files.Keys, includeSubdirectories: true);
8383

84-
var processTask = ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: true, launchResult: null, combinedCancellationSource.Token);
84+
var processTask = Context.ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: true, launchResult: null, combinedCancellationSource.Token);
8585

8686
Task<ChangedFile?> fileSetTask;
8787
Task finishedTask;

src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal sealed class CompilationHandler : IDisposable
1717
public readonly EnvironmentOptions EnvironmentOptions;
1818
private readonly IReporter _reporter;
1919
private readonly WatchHotReloadService _hotReloadService;
20+
private readonly ProcessRunner _processRunner;
2021

2122
/// <summary>
2223
/// Lock to synchronize:
@@ -36,17 +37,15 @@ internal sealed class CompilationHandler : IDisposable
3637
/// </summary>
3738
private ImmutableList<WatchHotReloadService.Update> _previousUpdates = [];
3839

39-
private readonly CancellationToken _shutdownCancellationToken;
40-
4140
private bool _isDisposed;
4241

43-
public CompilationHandler(IReporter reporter, EnvironmentOptions environmentOptions, CancellationToken shutdownCancellationToken)
42+
public CompilationHandler(IReporter reporter, ProcessRunner processRunner, EnvironmentOptions environmentOptions)
4443
{
4544
_reporter = reporter;
45+
_processRunner = processRunner;
4646
EnvironmentOptions = environmentOptions;
4747
Workspace = new IncrementalMSBuildWorkspace(reporter);
4848
_hotReloadService = new WatchHotReloadService(Workspace.CurrentSolution.Services, () => ValueTask.FromResult(GetAggregateCapabilities()));
49-
_shutdownCancellationToken = shutdownCancellationToken;
5049
}
5150

5251
public void Dispose()
@@ -88,7 +87,6 @@ public void DiscardProjectBaselines(ImmutableDictionary<ProjectId, string> proje
8887
public void UpdateProjectBaselines(ImmutableDictionary<ProjectId, string> projectsToBeRebuilt, CancellationToken cancellationToken)
8988
{
9089
_hotReloadService.UpdateBaselines(Workspace.CurrentSolution, projectsToBeRebuilt.Keys.ToImmutableArray());
91-
_reporter.Report(MessageDescriptor.ProjectBaselinesUpdated);
9290
}
9391

9492
public async ValueTask StartSessionAsync(CancellationToken cancellationToken)
@@ -138,7 +136,7 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken)
138136
};
139137

140138
var launchResult = new ProcessLaunchResult();
141-
var runningProcess = ProcessRunner.RunAsync(processSpec, processReporter, isUserApplication: true, launchResult, processTerminationSource.Token);
139+
var runningProcess = _processRunner.RunAsync(processSpec, processReporter, isUserApplication: true, launchResult, processTerminationSource.Token);
142140
if (launchResult.ProcessId == null)
143141
{
144142
// error already reported
@@ -152,7 +150,6 @@ public async ValueTask StartSessionAsync(CancellationToken cancellationToken)
152150
var runningProject = new RunningProject(
153151
projectNode,
154152
projectOptions,
155-
EnvironmentOptions,
156153
deltaApplier,
157154
processReporter,
158155
browserRefreshServer,
@@ -659,7 +656,7 @@ public bool TryGetRunningProject(string projectPath, out ImmutableArray<RunningP
659656
private async ValueTask<IReadOnlyList<int>> TerminateRunningProjects(IEnumerable<RunningProject> projects, CancellationToken cancellationToken)
660657
{
661658
// wait for all tasks to complete:
662-
return await Task.WhenAll(projects.Select(p => p.TerminateAsync(_shutdownCancellationToken).AsTask())).WaitAsync(cancellationToken);
659+
return await Task.WhenAll(projects.Select(p => p.TerminateAsync().AsTask())).WaitAsync(cancellationToken);
663660
}
664661

665662
private static Task ForEachProjectAsync(ImmutableDictionary<string, ImmutableArray<RunningProject>> projects, Func<RunningProject, CancellationToken, Task> action, CancellationToken cancellationToken)

src/BuiltInTools/dotnet-watch/HotReload/RunningProject.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace Microsoft.DotNet.Watch
1212
internal sealed class RunningProject(
1313
ProjectGraphNode projectNode,
1414
ProjectOptions options,
15-
EnvironmentOptions environmentOptions,
1615
DeltaApplier deltaApplier,
1716
IReporter reporter,
1817
BrowserRefreshServer? browserRefreshServer,
@@ -70,21 +69,8 @@ public async ValueTask WaitForProcessRunningAsync(CancellationToken cancellation
7069
await DeltaApplier.WaitForProcessRunningAsync(cancellationToken);
7170
}
7271

73-
public async ValueTask<int> TerminateAsync(CancellationToken shutdownCancellationToken)
72+
public async ValueTask<int> TerminateAsync()
7473
{
75-
if (shutdownCancellationToken.IsCancellationRequested)
76-
{
77-
// Ctrl+C sent, wait for the process to exit
78-
try
79-
{
80-
_ = await RunningProcess.WaitAsync(environmentOptions.ProcessCleanupTimeout, CancellationToken.None);
81-
}
82-
catch (TimeoutException)
83-
{
84-
// nop
85-
}
86-
}
87-
8874
ProcessTerminationSource.Cancel();
8975
return await RunningProcess;
9076
}

src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
100100
}
101101

102102
var projectMap = new ProjectNodeMap(evaluationResult.ProjectGraph, Context.Reporter);
103-
compilationHandler = new CompilationHandler(Context.Reporter, Context.EnvironmentOptions, shutdownCancellationToken);
103+
compilationHandler = new CompilationHandler(Context.Reporter, Context.ProcessRunner, Context.EnvironmentOptions);
104104
var scopedCssFileHandler = new ScopedCssFileHandler(Context.Reporter, projectMap, browserConnector);
105105
var projectLauncher = new ProjectLauncher(Context, projectMap, browserConnector, compilationHandler, iteration);
106106
var outputDirectories = GetProjectOutputDirectories(evaluationResult.ProjectGraph);
@@ -369,6 +369,8 @@ void FileChangedCallback(ChangedPath change)
369369

370370
// Update project baselines to reflect changes to the restarted projects.
371371
compilationHandler.UpdateProjectBaselines(projectsToRebuild, iterationCancellationToken);
372+
373+
Context.Reporter.Report(MessageDescriptor.ProjectsRebuilt, projectsToRebuild.Count);
372374
}
373375

374376
if (projectsToRestart is not [])
@@ -392,6 +394,8 @@ await Task.WhenAll(
392394
}
393395
}))
394396
.WaitAsync(shutdownCancellationToken);
397+
398+
Context.Reporter.Report(MessageDescriptor.ProjectsRestarted, projectsToRestart.Length);
395399
}
396400

397401
async Task<ImmutableList<ChangedFile>> CaptureChangedFilesSnapshot(ImmutableDictionary<ProjectId, string>? rebuiltProjects)
@@ -455,6 +459,8 @@ async Task<ImmutableList<ChangedFile>> CaptureChangedFilesSnapshot(ImmutableDict
455459
changedFiles = changedFiles
456460
.Select(f => evaluationResult.Files.TryGetValue(f.Item.FilePath, out var evaluatedFile) ? f with { Item = evaluatedFile } : f)
457461
.ToImmutableList();
462+
463+
Context.Reporter.Report(MessageDescriptor.ReEvaluationCompleted);
458464
}
459465

460466
if (rebuiltProjects != null)
@@ -527,7 +533,7 @@ async Task<ImmutableList<ChangedFile>> CaptureChangedFilesSnapshot(ImmutableDict
527533

528534
if (rootRunningProject != null)
529535
{
530-
await rootRunningProject.TerminateAsync(shutdownCancellationToken);
536+
await rootRunningProject.TerminateAsync();
531537
}
532538

533539
if (runtimeProcessLauncher != null)
@@ -831,7 +837,7 @@ await FileWatcher.WaitForFileChangeAsync(
831837

832838
Context.Reporter.Output($"Building {projectPath} ...");
833839

834-
var exitCode = await ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: false, launchResult: null, cancellationToken);
840+
var exitCode = await Context.ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: false, launchResult: null, cancellationToken);
835841
return (exitCode == 0, buildOutput.ToImmutableArray(), projectPath);
836842
}
837843

src/BuiltInTools/dotnet-watch/Internal/IReporter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ public MessageDescriptor ToErrorWhen(bool condition)
6363
// predefined messages used for testing:
6464
public static readonly MessageDescriptor HotReloadSessionStarting = new(Format: null, Emoji: null, MessageSeverity.None, s_id++);
6565
public static readonly MessageDescriptor HotReloadSessionStarted = new("Hot reload session started.", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
66-
public static readonly MessageDescriptor ProjectBaselinesUpdated = new("Project baselines updated.", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
66+
public static readonly MessageDescriptor ProjectsRebuilt = new("Projects rebuilt ({0})", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
67+
public static readonly MessageDescriptor ProjectsRestarted = new("Projects restarted ({0})", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
6768
public static readonly MessageDescriptor FixBuildError = new("Fix the error to continue or press Ctrl+C to exit.", WatchEmoji, MessageSeverity.Warning, s_id++);
6869
public static readonly MessageDescriptor WaitingForChanges = new("Waiting for changes", WatchEmoji, MessageSeverity.Verbose, s_id++);
6970
public static readonly MessageDescriptor LaunchedProcess = new("Launched '{0}' with arguments '{1}': process id {2}", LaunchEmoji, MessageSeverity.Verbose, s_id++);
70-
public static readonly MessageDescriptor KillingProcess = new("Killing process {0}", WatchEmoji, MessageSeverity.Verbose, s_id++);
7171
public static readonly MessageDescriptor HotReloadChangeHandled = new("Hot reload change handled in {0}ms.", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
7272
public static readonly MessageDescriptor HotReloadSucceeded = new("Hot reload succeeded.", HotReloadEmoji, MessageSeverity.Output, s_id++);
7373
public static readonly MessageDescriptor UpdatesApplied = new("Updates applied: {0} out of {1}.", HotReloadEmoji, MessageSeverity.Verbose, s_id++);
@@ -86,6 +86,7 @@ public MessageDescriptor ToErrorWhen(bool condition)
8686
public static readonly MessageDescriptor IgnoringChangeInHiddenDirectory = new("Ignoring change in hidden directory '{0}': {1} '{2}'", WatchEmoji, MessageSeverity.Verbose, s_id++);
8787
public static readonly MessageDescriptor IgnoringChangeInOutputDirectory = new("Ignoring change in output directory: {0} '{1}'", WatchEmoji, MessageSeverity.Verbose, s_id++);
8888
public static readonly MessageDescriptor FileAdditionTriggeredReEvaluation = new("File addition triggered re-evaluation.", WatchEmoji, MessageSeverity.Verbose, s_id++);
89+
public static readonly MessageDescriptor ReEvaluationCompleted = new("Re-evaluation completed.", WatchEmoji, MessageSeverity.Verbose, s_id++);
8990
public static readonly MessageDescriptor NoCSharpChangesToApply = new("No C# changes to apply.", WatchEmoji, MessageSeverity.Output, s_id++);
9091
public static readonly MessageDescriptor Exited = new("Exited", WatchEmoji, MessageSeverity.Output, s_id++);
9192
public static readonly MessageDescriptor ExitedWithUnknownErrorCode = new("Exited with unknown error code", ErrorEmoji, MessageSeverity.Error, s_id++);

src/BuiltInTools/dotnet-watch/Internal/MsBuildFileSetFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal class MSBuildFileSetFactory(
2020
string rootProjectFile,
2121
IEnumerable<string> buildArguments,
2222
EnvironmentOptions environmentOptions,
23+
ProcessRunner processRunner,
2324
IReporter reporter)
2425
{
2526
private const string TargetName = "GenerateWatchList";
@@ -53,7 +54,7 @@ internal class MSBuildFileSetFactory(
5354

5455
reporter.Verbose($"Running MSBuild target '{TargetName}' on '{rootProjectFile}'");
5556

56-
var exitCode = await ProcessRunner.RunAsync(processSpec, reporter, isUserApplication: false, launchResult: null, cancellationToken);
57+
var exitCode = await processRunner.RunAsync(processSpec, reporter, isUserApplication: false, launchResult: null, cancellationToken);
5758

5859
var success = exitCode == 0 && File.Exists(watchList);
5960

0 commit comments

Comments
 (0)