diff --git a/src/System.CommandLine.Tests/CustomParsingTests.cs b/src/System.CommandLine.Tests/CustomParsingTests.cs index 7a1d14936d..dcc7fce18a 100644 --- a/src/System.CommandLine.Tests/CustomParsingTests.cs +++ b/src/System.CommandLine.Tests/CustomParsingTests.cs @@ -561,6 +561,23 @@ public void Custom_parser_is_called_once_per_parse_operation_when_input_is_provi i.Should().Be(2); } + [Fact] // https://github.com/dotnet/command-line-api/issues/2743 + public void Setting_CustomParser_to_null_reverts_to_default_parsing() + { + Argument argument = new("int") + { + CustomParser = (_) => 0 + }; + + argument.CustomParser = null; + + var command = new RootCommand { argument }; + + var result = command.Parse("123"); + + result.GetValue(argument).Should().Be(123); + } + [Fact] public void Default_value_factory_is_called_once_per_parse_operation_when_no_input_is_provided() { diff --git a/src/System.CommandLine/Argument{T}.cs b/src/System.CommandLine/Argument{T}.cs index 999ac5604f..2ea7b3d726 100644 --- a/src/System.CommandLine/Argument{T}.cs +++ b/src/System.CommandLine/Argument{T}.cs @@ -78,6 +78,10 @@ public Func? DefaultValueFactory } }; } + else + { + ConvertArguments = null; + } } } diff --git a/src/System.CommandLine/Invocation/InvocationPipeline.cs b/src/System.CommandLine/Invocation/InvocationPipeline.cs index c41d2686d6..a2b50afe9c 100644 --- a/src/System.CommandLine/Invocation/InvocationPipeline.cs +++ b/src/System.CommandLine/Invocation/InvocationPipeline.cs @@ -44,21 +44,22 @@ internal static async Task InvokeAsync(ParseResult parseResult, Cancellatio return syncAction.Invoke(parseResult); case AsynchronousCommandLineAction asyncAction: - var startedInvocation = asyncAction.InvokeAsync(parseResult, cts.Token); - var timeout = parseResult.InvocationConfiguration.ProcessTerminationTimeout; if (timeout.HasValue) { - terminationHandler = new(cts, startedInvocation, timeout.Value); + terminationHandler = new(cts, timeout.Value); } + var startedInvocation = asyncAction.InvokeAsync(parseResult, cts.Token); + if (terminationHandler is null) { return await startedInvocation; } else { + terminationHandler.StartedHandler = startedInvocation; // Handlers may not implement cancellation. // In such cases, when CancelOnProcessTermination is configured and user presses Ctrl+C, // ProcessTerminationCompletionSource completes first, with the result equal to native exit code for given signal. diff --git a/src/System.CommandLine/Invocation/ProcessTerminationHandler.cs b/src/System.CommandLine/Invocation/ProcessTerminationHandler.cs index ead35b4eb0..3ff7e7c8ee 100644 --- a/src/System.CommandLine/Invocation/ProcessTerminationHandler.cs +++ b/src/System.CommandLine/Invocation/ProcessTerminationHandler.cs @@ -14,20 +14,20 @@ internal sealed class ProcessTerminationHandler : IDisposable internal readonly TaskCompletionSource ProcessTerminationCompletionSource; private readonly CancellationTokenSource _handlerCancellationTokenSource; - private readonly Task _startedHandler; + private Task? _startedHandler; private readonly TimeSpan _processTerminationTimeout; #if NET7_0_OR_GREATER private readonly IDisposable? _sigIntRegistration, _sigTermRegistration; #endif - + + internal Task StartedHandler { set => Volatile.Write(ref _startedHandler, value); } + internal ProcessTerminationHandler( CancellationTokenSource handlerCancellationTokenSource, - Task startedHandler, TimeSpan processTerminationTimeout) { ProcessTerminationCompletionSource = new (); _handlerCancellationTokenSource = handlerCancellationTokenSource; - _startedHandler = startedHandler; _processTerminationTimeout = processTerminationTimeout; #if NET7_0_OR_GREATER // we prefer the new API as they allow for cancelling SIGTERM @@ -86,8 +86,9 @@ void Cancel(int forcedTerminationExitCode) try { + var startedHandler = Volatile.Read(ref _startedHandler); // wait for the configured interval - if (!_startedHandler.Wait(_processTerminationTimeout)) + if (startedHandler is null || !startedHandler.Wait(_processTerminationTimeout)) { // if the handler does not finish within configured time, // use the completion source to signal forced completion (preserving native exit code)