Unyablog.

のにれんのブログ

Python の subprocess.run は KeyboardInterrupt で強制的に kill される

Python から Terraform をインタラクティブな(stdin をそのまま渡す)形で呼ぶために、subprocess.run を使って subprocess で走らせようとしている。

ここで、 Ctrl-C (KeyboardInterrupt) のハンドリングがネックになる。 Terraform は Ctrl-C を打つと Graceful な終了を試みることがあるので、Ctrl-C が打たれてもしばらく subprocess を終了しないようにしたい。

try で囲んで except KeyboardInterrupt すれば実現できそうだが、残念ながら subprocess.runsubprocess.call では実現できず、すぐに kill されてしまう。

Python 3.9 で確認。

subprocess.run 中に Ctrl-C を押すとどうなるのか

subprocess.run 中に Ctrl-C を押すと、まず subprocess.run 中で使われている Popen.communicate でハンドリングされる。

ここでは、0.25秒(固定値)*1待った後に KeyboardInterrupt を re-raise している。

github.com

その後、subprocess.run でハンドリングされるが、そこで有無を言わさず process.kill() が呼ばれて kill されてしまう。

github.com

  • Popen が re-raise するまでの待ち時間を伸ばせないこと
  • re-raise した場合 subprocess.runprocess.kill() を呼んでしまうこと

から、 subprocess.run 中に Ctrl-C で subprocess が終了するのを防ぐ手段はない。

どうするか

subprocess.run の便利な機能を使えないのが残念だが、Popen を直接使うようにした。

with subprocess.Popen(args) as p:
    try:
        return_code = p.wait()
    except KeyboardInterrupt:
        # Ctrl-C が打たれたら、プロセスが終わるのを待ってから raise する
        # 補足: 2回目の p.wait() は try-except で囲んでいないので、もう一度 Ctrl-C が打たれた場合は特にハンドリングされず raise される
        return_code = p.wait()
        raise

*1:初回以降は wait しない。詳細: https://github.com/python/cpython/pull/5026