Async pre-actions are not covered by process-termination handling: Ctrl+C hard-kills the process instead of cancelling the token
まだ誰も着手していません。
評価
調査の方向性
src/System.CommandLine/Invocation/InvocationPipeline.cs の InvokeAsync から始め、アクション前のループとメインのコマンドアクションのパスを比較します。ProcessTerminationHandler.cs を読み、アクション前に Ctrl+C を入力して、提供されている net10.0 の再現手順を実行します。完了の条件は、アクション前のトークンがキャンセルされ、シグナルが抑制され、設定された猶予期間内にアクションがアンワインドできることです。
索引モデルが 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分
- マージ済み PR(30日)
- 1
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- 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 件 ·
-
難易度 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 件 ·
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 日以内に返信
-
v9 review: Testingオープン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 日以内に返信