web-dev-qa-db-ja.com

Python:子プロセスを殺す方法)親が死んだとき?

子プロセスは

_subprocess.Popen(arg)
_

親が異常終了したときに確実に強制終了する方法はありますか?これはWindowsとLinuxの両方で機能するために必要です。私はこれを知っています Linuxのソリューション

編集:

プロセスを開始する別の方法を使用してソリューションが存在する場合、subprocess.Popen(arg)を使用して子プロセスを開始する要件を緩和できます。

26
user443854

えっと、昨日自分で調べたところ!子プログラムを変更できないと仮定します。

Linuxでは、prctl(PR_SET_PDEATHSIG, ...)がおそらく唯一の信頼できる選択肢です。 (子プロセスを強制終了することが絶対に必要な場合は、SIGTERMではなくSIGKILLに終了信号を設定することをお勧めします。リンクしたコードはSIGTERMを使用しますが、必要に応じて、子はSIGTERMを無視するオプションを持っています。 )

Windowsでは、最も信頼できるオプションは Jobオブジェクト を使用することです。アイデアは、「ジョブ」(プロセスの一種のコンテナー)を作成し、子プロセスをジョブに配置し、「誰もこのジョブの「ハンドル」を保持していない場合」という魔法のオプションを設定することです。 、その中にあるプロセスを強制終了します。」デフォルトでは、ジョブへの唯一の「ハンドル」は親プロセスが保持するものであり、親プロセスが終了すると、OSはすべてのハンドルを通過して閉じます。これは、開いているハンドルがないことを意味します。仕事。したがって、要求に応じて子を殺します。 (複数の子プロセスがある場合、それらをすべて同じジョブに割り当てることができます。) この答え は、_win32api_モジュールを使用してこれを行うためのサンプルコードを持っています。そのコードは、_subprocess.Popen_ではなくCreateProcessを使用して子を起動します。その理由は、生成された子の「プロセスハンドル」を取得する必要があり、CreateProcessがデフォルトでこれを返すためです。 _subprocess.Popen_を使用したい場合は、OpenProcessの代わりに_subprocess.Popen_とCreateProcessを使用する、その回答のコードの(テストされていない)コピーを次に示します。

_import subprocess
import win32api
import win32con
import win32job

hJob = win32job.CreateJobObject(None, "")
extended_info = win32job.QueryInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation)
extended_info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
win32job.SetInformationJobObject(hJob, win32job.JobObjectExtendedLimitInformation, extended_info)

child = subprocess.Popen(...)
# Convert process id to process handle:
perms = win32con.PROCESS_TERMINATE | win32con.PROCESS_SET_QUOTA
hProcess = win32api.OpenProcess(perms, False, child.pid)

win32job.AssignProcessToJobObject(hJob, hProcess)
_

技術的には、子供がPopenOpenProcessの呼び出しの間に死亡した場合に備えて、ここには小さな競合状態があります。そのことを心配するかどうかを決定できます。

ジョブオブジェクトを使用する場合の欠点の1つは、VistaまたはWin7で実行しているときに、プログラムがWindowsシェルから(つまり、アイコンをクリックして)起動された場合、おそらく すでにジョブがあることです。オブジェクトが割り当てられ 、新しいジョブオブジェクトを作成しようとすると失敗します。 Win8はこれを修正し(ジョブオブジェクトをネストできるようにすることで)、またはプログラムがコマンドラインから実行される場合は問題ありません。

can子を変更する場合(たとえば、multiprocessingを使用する場合など)、おそらく最良のオプションは、親のPIDを渡すことです子に(たとえば、コマンドライン引数として、または_args=_への_multiprocessing.Process_引数で)、次に:

POSIXの場合:os.getppid()を時々呼び出すだけの子スレッドを生成し、戻り値が親から渡されたpidと一致しなくなった場合は、os._exit()を呼び出します。 (このアプローチは、OS Xを含むすべてのUnixに移植可能ですが、prctlトリックはLinux固有です。)

Windowsの場合:OpenProcessおよび_os.waitpid_を使用する子のスレッドを生成します。 ctypesの使用例:

_from ctypes import WinDLL, WinError
from ctypes.wintypes import DWORD, BOOL, HANDLE
# Magic value from http://msdn.Microsoft.com/en-us/library/ms684880.aspx
SYNCHRONIZE = 0x00100000
kernel32 = WinDLL("kernel32.dll")
kernel32.OpenProcess.argtypes = (DWORD, BOOL, DWORD)
kernel32.OpenProcess.restype = HANDLE
parent_handle = kernel32.OpenProcess(SYNCHRONIZE, False, parent_pid)
# Block until parent exits
os.waitpid(parent_handle, 0)
os._exit(0)
_

これにより、前述のジョブオブジェクトで発生する可能性のある問題が回避されます。

本当に確実になりたい場合は、これらすべてのソリューションを組み合わせることができます。

お役に立てば幸いです。

25

Popenオブジェクトは、terminateメソッドとkillメソッドを提供します。

https://docs.python.org/2/library/subprocess.html#subprocess.Popen.terminate

これらは、SIGTERMおよびSIGKILLシグナルを送信します。以下のようなことができます:

from subprocess import Popen

p = None
try:
    p = Popen(arg)
    # some code here
except Exception as ex:
    print 'Parent program has exited with the below error:\n{0}'.format(ex)
    if p:
        p.terminate()

更新:

あなたは正しいです-上記のコードはハードクラッシュや誰かがあなたのプロセスを殺すことから保護しません。その場合は、子プロセスをクラスでラップして、ポーリングモデルを使用して親プロセスを監視できます。 psutilは非標準であることに注意してください。

import os
import psutil

from multiprocessing import Process
from time import sleep


class MyProcessAbstraction(object):
    def __init__(self, parent_pid, command):
        """
        @type parent_pid: int
        @type command: str
        """
        self._child = None
        self._cmd = command
        self._parent = psutil.Process(pid=parent_pid)

    def run_child(self):
        """
        Start a child process by running self._cmd. 
        Wait until the parent process (self._parent) has died, then kill the 
        child.
        """
        print '---- Running command: "%s" ----' % self._cmd
        self._child = psutil.Popen(self._cmd)
        try:
            while self._parent.status == psutil.STATUS_RUNNING:
                sleep(1)
        except psutil.NoSuchProcess:
            pass
        finally:
            print '---- Terminating child PID %s ----' % self._child.pid
            self._child.terminate()


if __name__ == "__main__":
    parent = os.getpid()
    child = MyProcessAbstraction(parent, 'ping -t localhost')
    child_proc = Process(target=child.run_child)
    child_proc.daemon = True
    child_proc.start()

    print '---- Try killing PID: %s ----' % parent
    while True:
        sleep(1)

この例では、永久に実行される「ping -t localhost」b/cを実行します。親プロセスを強制終了すると、子プロセス(pingコマンド)も強制終了されます。

8
Nick