Skip to content

OperationCanceledException Not Thrown without Config in Async Apps #2671

@mpatkisson

Description

@mpatkisson

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

  1. 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
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions