Async pre-actions are not covered by process-termination handling: Ctrl+C hard-kills the process instead of cancelling the token
还没有人认领这个 Issue。
评估
调研方向
从 src/System.CommandLine/Invocation/InvocationPipeline.cs 中的 InvokeAsync 开始,将操作前循环与主命令操作路径进行比较。阅读 ProcessTerminationHandler.cs,并在操作前按下 Ctrl+C 运行提供的 net10.0 重现。完成的标准是:操作前 token 被取消、信号被抑制,并且操作可以在配置的宽限期内展开完成。
由索引模型根据 Issue 内容生成。
描述
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.
- 主要语言
- C#
- 星标
- 3.7k
- 派生
- 434
- 平均合并
- 4 小时 46 分钟
- 30 天内合并 PR
- 1
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
dotnet/command-line-api 的其他 Issue
-
German localization is incomplete可能已有人在做 @b-v-d-e-v 于 7 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 65/100
dotnet/command-line-api#2852 ·
-
Incomplete French (fr) translation: RequiredOptionWasNotProvided not translated可能已有人在做 @JPBlanc 于 105 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 65/100
dotnet/command-line-api#2822 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 62/100
dotnet/command-line-api#2792 · 2 条评论 · 16 个 reaction ·
-
难度 2/5 1-3 小时 新手友好度 62/100
dotnet/command-line-api#2704 ·
-
GetCompletions should check exit code of invoked application可能已有人在做 @baradgur 于 1276 天前认领。 未关闭Area-Completions bug help wanted
难度 2/5 1-3 小时 新手友好度 72/100
dotnet/command-line-api#2137 · 1 条评论 · 3 个 reaction ·
查看 dotnet/command-line-api 的全部 Issue
相似的 Issue
-
type/automation type/tech-debt
难度 1/5 1 小时以内 新手友好度 72/100
维护者通常 1 天内回复
-
no-stack-trace
难度 2/5 1-3 小时 新手友好度 83/100
维护者通常 1 天内回复
-
enhancement
难度 2/5 1-3 小时 新手友好度 65/100
维护者通常 1 天内回复
-
docs/external squad/utforming
难度 2/5 1-3 小时 新手友好度 75/100
Altinn/altinn-studio#21041 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
stryker-mutator/stryker-net#3892 ·
维护者通常 1 天内回复