Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/System.CommandLine.Tests/CustomParsingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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()
{
Expand Down
4 changes: 4 additions & 0 deletions src/System.CommandLine/Argument{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ public Func<ArgumentResult, T>? DefaultValueFactory
}
};
}
else
{
ConvertArguments = null;
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/System.CommandLine/Invocation/InvocationPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,22 @@ internal static async Task<int> 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.
Expand Down
11 changes: 6 additions & 5 deletions src/System.CommandLine/Invocation/ProcessTerminationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ internal sealed class ProcessTerminationHandler : IDisposable

internal readonly TaskCompletionSource<int> ProcessTerminationCompletionSource;
private readonly CancellationTokenSource _handlerCancellationTokenSource;
private readonly Task<int> _startedHandler;
private Task<int>? _startedHandler;
private readonly TimeSpan _processTerminationTimeout;
#if NET7_0_OR_GREATER
private readonly IDisposable? _sigIntRegistration, _sigTermRegistration;
#endif


internal Task<int> StartedHandler { set => Volatile.Write(ref _startedHandler, value); }

internal ProcessTerminationHandler(
CancellationTokenSource handlerCancellationTokenSource,
Task<int> 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
Expand Down Expand Up @@ -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)
Expand Down