-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add Process.StartAndForget APIs for fire-and-forget process launching #126078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
ad79bd9
Add Process.StartAndForget APIs for fire-and-forget scenarios
Copilot 0f8be81
Address review feedback on StartAndForget tests and ref file
Copilot 9be6c1c
Fix StartAndForget_WithNullArguments_StartsProcess to actually pass null
Copilot bff4ded
Fix StartAndForget_WithNullArguments test: use hostname on Windows, l…
Copilot c6a0adb
Sync with main (PR #126192) and use SafeProcessHandle.Start in StartA…
Copilot 0f411da
Revert "Sync with main (PR #126192) and use SafeProcessHandle.Start i…
adamsitnik 295397e
Merge remote-tracking branch 'origin/main' into copilot/add-start-and…
adamsitnik ea70470
address my own feedback
adamsitnik c093f04
fix a bug discovered by code review
adamsitnik 1a36d81
Address review feedback: fix typo, remove duplicate SerializationGuar…
Copilot 3506a11
address code review feedback: don't call SerializationGuard.ThrowIfDe…
adamsitnik fd129ae
Apply suggestion from @adamsitnik
adamsitnik 016c107
Merge remote-tracking branch 'origin/main' into copilot/add-start-and…
adamsitnik a4ca369
Merge remote-tracking branch 'origin/main' into copilot/add-start-and…
adamsitnik 14d1235
use the new APIs to limit handle inheritance by default
adamsitnik 7d9bbf0
Throw InvalidOperationException for UseShellExecute=true in StartAndF…
Copilot 44d8f6a
Update docs and remove unused using in StartAndForget
Copilot 8d3bbfd
Merge remote-tracking branch 'origin/main' into copilot/add-start-and…
adamsitnik d4a941b
update the implementation based on experience from StartDetached and …
adamsitnik a2b586a
Fix unused usings in Process.Scenarios.cs and improve redirect error …
Copilot 1380afd
Address review feedback: rename fallbackToNull, simplify summaries, r…
Copilot 398d6ac
Apply suggestion: add 'using' to Process template declaration in Star…
Copilot e3fff40
Add StartAndForget_WithStandardOutputHandle_CapturesOutput test
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Scenarios.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
|
danmoseley marked this conversation as resolved.
adamsitnik marked this conversation as resolved.
|
||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Runtime.Versioning; | ||
| using Microsoft.Win32.SafeHandles; | ||
|
|
||
| namespace System.Diagnostics | ||
| { | ||
| public partial class Process | ||
| { | ||
| /// <summary> | ||
| /// Starts the process described by <paramref name="startInfo"/>, releases all associated resources, | ||
| /// and returns the process ID. | ||
| /// </summary> | ||
| /// <param name="startInfo">The <see cref="ProcessStartInfo"/> that contains the information used to start the process.</param> | ||
| /// <returns>The process ID of the started process.</returns> | ||
| /// <exception cref="ArgumentNullException"><paramref name="startInfo"/> is <see langword="null"/>.</exception> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <para>One or more of <see cref="ProcessStartInfo.RedirectStandardInput"/>, | ||
| /// <see cref="ProcessStartInfo.RedirectStandardOutput"/>, or | ||
| /// <see cref="ProcessStartInfo.RedirectStandardError"/> is set to <see langword="true"/>. | ||
| /// Stream redirection is not supported in fire-and-forget scenarios because redirected streams | ||
| /// must be drained to avoid deadlocks.</para> | ||
| /// <para>-or-</para> | ||
| /// <para><see cref="ProcessStartInfo.UseShellExecute"/> is set to <see langword="true"/>. | ||
| /// Shell execution is not supported in fire-and-forget scenarios because on Windows it may not | ||
| /// create a new process, making it impossible to return a valid process ID.</para> | ||
| /// </exception> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// When a standard handle (<see cref="ProcessStartInfo.StandardInputHandle"/>, | ||
| /// <see cref="ProcessStartInfo.StandardOutputHandle"/>, or <see cref="ProcessStartInfo.StandardErrorHandle"/>) | ||
| /// is not provided, it is redirected to the null file by default. | ||
|
adamsitnik marked this conversation as resolved.
|
||
| /// </para> | ||
|
adamsitnik marked this conversation as resolved.
|
||
| /// <para> | ||
| /// This method is designed for fire-and-forget scenarios where the caller wants to launch a process | ||
| /// and does not need to interact with it further. It starts the process, releases all associated | ||
| /// resources, and returns the process ID. The started process continues to run independently. | ||
| /// </para> | ||
| /// </remarks> | ||
| [UnsupportedOSPlatform("ios")] | ||
| [UnsupportedOSPlatform("tvos")] | ||
| [SupportedOSPlatform("maccatalyst")] | ||
| public static int StartAndForget(ProcessStartInfo startInfo) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(startInfo); | ||
|
adamsitnik marked this conversation as resolved.
|
||
|
|
||
| if (startInfo.UseShellExecute) | ||
| { | ||
| throw new InvalidOperationException(SR.StartAndForget_UseShellExecuteNotSupported); | ||
| } | ||
|
|
||
| using SafeProcessHandle processHandle = SafeProcessHandle.Start(startInfo, fallbackToNull: true); | ||
| return processHandle.ProcessId; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Starts a process with the specified file name and optional arguments, releases all associated resources, | ||
| /// and returns the process ID. | ||
| /// </summary> | ||
| /// <param name="fileName">The name of the application or document to start.</param> | ||
| /// <param name="arguments"> | ||
| /// The command-line arguments to pass to the process. Pass <see langword="null"/> or an empty list | ||
| /// to start the process without additional arguments. | ||
| /// </param> | ||
| /// <returns>The process ID of the started process.</returns> | ||
| /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <see langword="null"/>.</exception> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// This method is designed for fire-and-forget scenarios where the caller wants to launch a process | ||
| /// and does not need to interact with it further. It starts the process, captures its process ID, | ||
| /// releases all associated resources, and returns the process ID. The started process continues to | ||
| /// run independently. | ||
| /// </para> | ||
| /// <para> | ||
| /// Standard handles are redirected to the null file by default. | ||
| /// </para> | ||
|
adamsitnik marked this conversation as resolved.
|
||
| /// </remarks> | ||
| [UnsupportedOSPlatform("ios")] | ||
| [UnsupportedOSPlatform("tvos")] | ||
| [SupportedOSPlatform("maccatalyst")] | ||
| public static int StartAndForget(string fileName, IList<string>? arguments = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(fileName); | ||
|
|
||
| ProcessStartInfo startInfo = new(fileName); | ||
| if (arguments is not null) | ||
| { | ||
| foreach (string argument in arguments) | ||
| { | ||
| startInfo.ArgumentList.Add(argument); | ||
| } | ||
|
adamsitnik marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return StartAndForget(startInfo); | ||
| } | ||
| } | ||
| } | ||
114 changes: 114 additions & 0 deletions
114
src/libraries/System.Diagnostics.Process/tests/StartAndForget.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.IO; | ||
| using Microsoft.DotNet.RemoteExecutor; | ||
| using Microsoft.Win32.SafeHandles; | ||
| using Xunit; | ||
|
|
||
| namespace System.Diagnostics.Tests | ||
| { | ||
| public class StartAndForgetTests : ProcessTestBase | ||
| { | ||
| [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
| [InlineData(true)] | ||
| [InlineData(false)] | ||
| public void StartAndForget_StartsProcessAndReturnsValidPid(bool useProcessStartInfo) | ||
| { | ||
| using Process template = CreateSleepProcess((int)TimeSpan.FromHours(1).TotalMilliseconds); | ||
| int pid = useProcessStartInfo | ||
| ? Process.StartAndForget(template.StartInfo) | ||
| : Process.StartAndForget(template.StartInfo.FileName, template.StartInfo.ArgumentList); | ||
|
adamsitnik marked this conversation as resolved.
|
||
|
|
||
| Assert.True(pid > 0); | ||
|
|
||
| using Process launched = Process.GetProcessById(pid); | ||
| try | ||
| { | ||
| Assert.False(launched.HasExited); | ||
| } | ||
| finally | ||
| { | ||
| launched.Kill(); | ||
| launched.WaitForExit(); | ||
|
adamsitnik marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
|
adamsitnik marked this conversation as resolved.
|
||
| [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
| public void StartAndForget_WithStandardOutputHandle_CapturesOutput() | ||
| { | ||
| using Process template = CreateProcess(static () => | ||
| { | ||
| Console.Write("hello"); | ||
| return RemoteExecutor.SuccessExitCode; | ||
| }); | ||
|
|
||
| SafeFileHandle.CreateAnonymousPipe(out SafeFileHandle outputReadPipe, out SafeFileHandle outputWritePipe); | ||
|
|
||
| using (outputReadPipe) | ||
| using (outputWritePipe) | ||
| { | ||
| template.StartInfo.StandardOutputHandle = outputWritePipe; | ||
|
|
||
| int pid = Process.StartAndForget(template.StartInfo); | ||
| Assert.True(pid > 0); | ||
|
|
||
| outputWritePipe.Close(); // close the parent copy of child handle | ||
|
|
||
| using StreamReader streamReader = new(new FileStream(outputReadPipe, FileAccess.Read, bufferSize: 1, outputReadPipe.IsAsync)); | ||
| Assert.Equal("hello", streamReader.ReadToEnd()); | ||
| } | ||
| } | ||
|
|
||
| // This test does not use RemoteExecutor, but it's a simple way to filter to OSes that support Process.Start. | ||
| [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
| public void StartAndForget_WithNullArguments_StartsProcess() | ||
| { | ||
| // cmd is available on every Windows, including Nano. When run with no parameters, it displays the Windows version/copyright banner. | ||
| // true is available on every Unix. When invoked with no arguments, it does nothing and exits successfully. | ||
| int pid = Process.StartAndForget(OperatingSystem.IsWindows() ? "cmd.exe" : "true", null); | ||
|
adamsitnik marked this conversation as resolved.
|
||
|
|
||
| Assert.True(pid > 0); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void StartAndForget_WithStartInfo_NullStartInfo_ThrowsArgumentNullException() | ||
| { | ||
| AssertExtensions.Throws<ArgumentNullException>("startInfo", () => Process.StartAndForget((ProcessStartInfo)null!)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void StartAndForget_WithFileName_NullFileName_ThrowsArgumentNullException() | ||
| { | ||
| AssertExtensions.Throws<ArgumentNullException>("fileName", () => Process.StartAndForget((string)null!)); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData(true, false, false)] | ||
| [InlineData(false, true, false)] | ||
| [InlineData(false, false, true)] | ||
| public void StartAndForget_WithRedirectedStreams_ThrowsInvalidOperationException( | ||
| bool redirectInput, bool redirectOutput, bool redirectError) | ||
| { | ||
| ProcessStartInfo startInfo = new("someprocess") | ||
| { | ||
| RedirectStandardInput = redirectInput, | ||
| RedirectStandardOutput = redirectOutput, | ||
| RedirectStandardError = redirectError, | ||
| }; | ||
|
|
||
| Assert.Throws<InvalidOperationException>(() => Process.StartAndForget(startInfo)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void StartAndForget_WithUseShellExecute_ThrowsInvalidOperationException() | ||
| { | ||
| ProcessStartInfo startInfo = new("someprocess") | ||
| { | ||
| UseShellExecute = true, | ||
| }; | ||
|
|
||
| Assert.Throws<InvalidOperationException>(() => Process.StartAndForget(startInfo)); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.