Async pre-actions are not covered by process-termination handling: Ctrl+C hard-kills the process instead of cancelling the token
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 74/100
Hướng nghiên cứu
Bắt đầu tại src/System.CommandLine/Invocation/InvocationPipeline.cs, ở InvokeAsync, và so sánh vòng lặp trước tác vụ với đường dẫn tác vụ của lệnh chính. Đọc ProcessTerminationHandler.cs và chạy bản tái hiện net10.0 được cung cấp bằng Ctrl+C trong khi thực hiện trước tác vụ. Hoàn tất khi token trước tác vụ bị hủy, tín hiệu bị chặn và tác vụ có thể unwind trong khoảng thời gian ân hạn đã cấu hình.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
InvocationPipeline.InvokeAsync installs the ProcessTerminationHandler only around the
main command action, never around pre-actions. As a result, when the user presses
Ctrl+C (SIGINT/SIGTERM) while an async pre-action is running:
- the
CancellationTokenthe pre-action received is never cancelled, and - the OS default signal handling is not suppressed, so the process is hard-terminated
immediately — noOperationCanceledException, no unwind, no cleanup.
The same handler running as the command action cancels gracefully. So whether Ctrl+C is
cooperative or fatal depends purely on whether the async work runs in a pre-action or the
command action, which is surprising and undocumented.
Repro
Minimal console app (net10.0) referencing System.CommandLine:
using System.CommandLine;
using System.CommandLine.Invocation;
var slow = new Option<string>("--slow");
slow.Action = new SlowPreAction(); // non-terminating async action => runs as a PreAction
var root = new RootCommand("repro") { slow };
root.SetAction(async (parseResult, ct) => { // async command action
Console.WriteLine("command action started");
try {
await Task.Delay(TimeSpan.FromSeconds(30), ct);
Console.WriteLine("command action finished");
} catch (OperationCanceledException) {
// this catch works as expected
Console.WriteLine("command action cancelled");
}
return 0;
});
return await root.Parse(args).InvokeAsync();
sealed class SlowPreAction : AsynchronousCommandLineAction {
public override bool Terminating => false;
public override async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken ct) {
Console.WriteLine("pre-action started");
try {
await Task.Delay(TimeSpan.FromSeconds(30), ct);
Console.WriteLine("pre-action finished");
} catch (OperationCanceledException) {
// this catch will not work
Console.WriteLine("pre-action cancelled");
}
return 0;
}
}
Case A — cancel during the command action (works as expected)
> repro
command action started
^Ccommand action cancelled
Process finished with exit code 0.
The token is cancelled, Task.Delay throws OperationCanceledException, the process exits
cleanly.
Case B — cancel during the pre-action (the bug)
> repro --slow x
pre-action started
^C
The process exits immediately as if killed — the token is never cancelled, no exception is
observed, and pre-action finished never prints. It behaves as though there were no Ctrl+C
handling installed at all.
Expected behavior
Ctrl+C during an async pre-action should behave the same as during the command action: the
CancellationToken handed to the pre-action is cancelled, the OS default kill is suppressed,
and the pre-action is given the ProcessTerminationTimeout grace period to unwind.
Actual behavior
Pre-actions run with no ProcessTerminationHandler. The token is inert and the process is
hard-terminated by the default signal.
Root cause
In src/System.CommandLine/Invocation/InvocationPipeline.cs, InvokeAsync:
-
Pre-actions are awaited in the loop with no termination handler:
case AsynchronousCommandLineAction asyncAction: result = await asyncAction.InvokeAsync(parseResult, cts.Token); // no ProcessTerminationHandler break; -
The
ProcessTerminationHandler— which registers the SIGINT/SIGTERM handler
(ProcessTerminationHandler.cs,PosixSignalRegistration.Create(...)), sets
context.Cancel = trueto suppress the default kill, and cancels the linkedcts— is
created only for the main command action:var timeout = parseResult.InvocationConfiguration.ProcessTerminationTimeout; if (timeout.HasValue) terminationHandler = new(cts, timeout.Value); var startedInvocation = asyncAction.InvokeAsync(parseResult, cts.Token); ...
Because no handler is installed during the pre-action phase, nothing ever cancels cts there,
and SIGINT/SIGTERM fall through to the runtime default (terminate the process).
Note: even setting aside the hard-kill, a perfectly cooperative pre-action could never observe
cancellation, since cts is not cancellable during that phase.
Suggested fix
Install the process-termination handling around the entire async invocation (pre-actions +
command action), not just the command action — e.g. create the ProcessTerminationHandler
before the pre-action loop so SIGINT is intercepted and cts is cancellable throughout. At
minimum, pre-actions should receive a token that is actually cancelled on Ctrl+C and should not
be hard-killed mid-run.
Environment
System.CommandLine3.0.0-preview.5.26302.115 (also confirmed present onmain)- .NET SDK 10.0.100
- Reproduced on macOS (darwin); the code path is platform-independent (both the
PosixSignalRegistrationandConsole.CancelKeyPressbranches are gated inside the
handler that pre-actions never construct).
▎ Drafted with AI assistance; I reproduced the behavior on 3.0.0-preview.5, confirmed the same code path on main, and verified the root-cause references myself.
- Ngôn ngữ chính
- C#
- Star
- 3.7k
- Fork
- 434
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của dotnet/command-line-api
-
German localization is incompleteCó thể đã có người làm @b-v-d-e-v đã nhận 6 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
dotnet/command-line-api#2852 ·
-
Incomplete French (fr) translation: RequiredOptionWasNotProvided not translatedCó thể đã có người làm @JPBlanc đã nhận 104 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
dotnet/command-line-api#2822 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
dotnet/command-line-api#2792 · 2 bình luận · 15 reaction ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
dotnet/command-line-api#2704 ·
-
GetCompletions should check exit code of invoked applicationCó thể đã có người làm @baradgur đã nhận 1275 ngày trước. Đang mởArea-Completions bug help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
dotnet/command-line-api#2137 · 1 bình luận · 3 reaction ·
Tất cả issue của dotnet/command-line-api
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
microsoft/fluentui-blazor#5410 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Bug pulumi/pulumi
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
activescott/lessmsi#306 ·
-
Docs MSBuild
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
getsentry/sentry-dotnet#5691 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày