コンテンツにスキップ

PowerShellでパイプラインが中断するとき何がおこるか

[質問]

たとえばLinuxのシェルのコマンドラインで、プロセスをパイプでつなぐ場合、下流のプロセスが終了すると上流プロセスにシグナルで「パイプが中断した」という情報が伝わります。PowerShellのパイプラインでは、上流はどのようにしてパイプラインが閉じたことを知るのですか。

[回答] Gemini

非常に鋭い視点ですね。Linux(POSIX)の世界では、パイプの下流が死ぬと上流プロセスに SIGPIPE シグナルが送られ、EPIPE エラーで書き込みが失敗するという「OSレイヤーの仕組み」があります。

一方、PowerShellのパイプラインは「プロセス間のバイトストリーム」ではなく、.NETオブジェクトのメソッド呼び出しの連鎖として実装されています。


上流が「閉じたこと」を知る仕組み:停止例外(Stopping Exception)

PowerShellでは、下流のコマンドレット(例:Select-Object -First 5)が「もうデータはいらない」と判断すると、内部的に PipelineStoppedException という特殊な例外(または停止通知)を上流に投げます。

具体的に上流が停止を知るプロセスは以下の通りです。

  1. 下流の判断: Select-Object -First 5 が5つ目のデータを受け取ると、自身の処理を完了させ、パイプラインを「停止状態」にします。
  2. 上流への通知: 下流コマンドレットが PipelineStoppedException をスロー(またはそれに準ずるフラグをセット)します。
  3. 上流の検知: 上流のループ(forForEach-Object)が次のオブジェクトを WriteObject() メソッドで出力しようとした瞬間、その例外をキャッチします。
  4. クリーンアップ: 上流コマンドレットは例外を受けて、自身の EndProcessing ブロック(または finally ブロック)を実行し、安全に終了します。

実験:上流が止まったことを証明する

以下のコードを実行すると、Select-Object が止めた瞬間に上流の for ループも停止することが確認できます。

& {
    try {
        for ($i = 1; ; $i++) {
            Write-Host "上流: $i を送信中..." -ForegroundColor Gray
            $i  # パイプラインに流す
        }
    }
    finally {
        Write-Host "上流: パイプラインの切断を検知して終了します。" -ForegroundColor Cyan
    }
} | Select-Object -First 3

実行結果:

  • 3 を送信した直後、Select-Object が「完了」を宣言。
  • 上流の finally ブロックが走り、無限ループが止まる。

Linuxとの決定的な違い

比較項目 Linux (bash/zshなど) PowerShell
伝達手段 SIGPIPE (OSシグナル) .NET Exception (例外処理)
データの単位 バイト / テキスト .NET オブジェクト
制御の主体 カーネルがパイプを管理 PowerShellエンジンが列挙子(Enumerator)を管理

まとめ

PowerShellの上流プロセスは、「次にオブジェクトを渡そうとした時(WriteObject実行時)に、下流が受け取りを拒否したこと(例外)」をもって、パイプラインが閉じたことを知ります。

これにより、無限ループを書いても Select-Object -First などと組み合わせるだけで、リソースを無駄にせず安全に停止できる設計になっています。

この「オブジェクトベースのパイプライン」の挙動について、さらに深掘りしたい部分はありますか?