OperationCanceledException Not Thrown without Config in Async Apps
This documentation (published 02 AUG 25) on Learn implies that System.CommandLine will, without additional configuration, throw OperationCanceledException when CTRL+C is pressed allowing library consumers to gracefully handle any necessary cleanup or shutdown messaging.
Versions 2.0.0-beta5 and 2.0.0-beta6 both supported this behavior, but 2.0.0-beta7 and beyond break it.
Steps to reproduce
-
Clone and run https://github.com/mpatkisson/culbertson with .NET 9
> git clone https://github.com/mpatkisson/culbertson.git
> cd culbertson
> dotnet run --project ./src/Culbertson
-
Once the project is running, press CTRL+C.
Expected Behavior
This routine should be handled when CTRL+C is pressed, thus printing these messages along with a slight delay for effect,
Termination requested. Cleaning up...
# 1 second delay
Cleanup complete. Exiting.
Actual Behavior
The program does exit when CTRL+C is pressed, but OperationCanceledException is never thrown, so cleanup messaging is not displayed.
Further Analysis
My guess is OperationCanceledException should be thrown in Beta 7 as it is in Betas 5 and 6. If this was a design decision, then apologies (but the docs / code commentary are confusing if so).
7bb08a6 was the latest commit supporting the expected behavior. 7bb08a6 was followed by 3132acb | Separate parse from invocation configurations (#2606) which had a lot of changes.
In the InvocationPipeline, this switch case creates a ProcessTerminationHandler only if InvocationConfiguration.ProcessTerminationTimeout is non-null. Before Beta7 a default value of 2s was being set on CommandLineConfiguration.ProcessTerminationTimeout like so
public TimeSpan? ProcessTerminationTimeout { get; set; } = TimeSpan.FromSeconds(2);
Commit 3132acb | Separate parse from invocation configurations (#2606) renamed CommandLineConfiguration to InvocationConfiguration and dropped the default setting which now looks like this.
/// <summary>
/// Enables signaling and handling of process termination (Ctrl+C, SIGINT, SIGTERM) via a <see cref="CancellationToken"/>
/// that can be passed to a <see cref="CommandLineAction"/> during invocation.
/// If not provided, a default timeout of 2 seconds is enforced.
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/// </summary>
public TimeSpan? ProcessTerminationTimeout { get; set; }
Possible Fix
My sense is that the default value on InvocationConfiguration.ProcessTerminationTimeout got lost in the mix of 3132acb and is still not fixed in the latest.
Re-adding the default brings behavior around CTRL+C back in line with earlier versions and the Learn docs. I've tested this small change using this branch with success.
OperationCanceledException Not Thrown without Config in Async Apps
This documentation (published 02 AUG 25) on Learn implies that
System.CommandLinewill, without additional configuration, throwOperationCanceledExceptionwhenCTRL+Cis pressed allowing library consumers to gracefully handle any necessary cleanup or shutdown messaging.Versions 2.0.0-beta5 and 2.0.0-beta6 both supported this behavior, but 2.0.0-beta7 and beyond break it.
Steps to reproduce
Clone and run https://github.com/mpatkisson/culbertson with .NET 9
Once the project is running, press
CTRL+C.Expected Behavior
This routine should be handled when
CTRL+Cis pressed, thus printing these messages along with a slight delay for effect,Termination requested. Cleaning up... # 1 second delay Cleanup complete. Exiting.Actual Behavior
The program does exit when
CTRL+Cis pressed, butOperationCanceledExceptionis never thrown, so cleanup messaging is not displayed.Further Analysis
My guess is
OperationCanceledExceptionshould be thrown in Beta 7 as it is in Betas 5 and 6. If this was a design decision, then apologies (but the docs / code commentary are confusing if so).7bb08a6was the latest commit supporting the expected behavior.7bb08a6was followed by3132acb | Separate parse from invocation configurations (#2606)which had a lot of changes.In the
InvocationPipeline, this switch case creates aProcessTerminationHandleronly ifInvocationConfiguration.ProcessTerminationTimeoutis non-null. Before Beta7 a default value of 2s was being set onCommandLineConfiguration.ProcessTerminationTimeoutlike soCommit
3132acb | Separate parse from invocation configurations (#2606)renamedCommandLineConfigurationtoInvocationConfigurationand dropped the default setting which now looks like this.Possible Fix
My sense is that the default value on
InvocationConfiguration.ProcessTerminationTimeoutgot lost in the mix of 3132acb and is still not fixed in the latest.Re-adding the default brings behavior around
CTRL+Cback in line with earlier versions and the Learn docs. I've tested this small change using this branch with success.