web-dev-qa-db-ja.com

Pythonプログラムの終了時にサブプロセスが停止していることを確認する

Pythonプログラムの終了時に、作成されたすべてのサブプロセスが停止していることを確認する方法はありますか?サブプロセスとは、subprocess.Popen()で作成されたものを意味します。

そうでない場合、発行しているすべてのキルを繰り返してから-9をキルする必要がありますか?クリーナーは何ですか?

56
pupeno

atexit を使用して、プログラムの終了時に実行されるクリーンアップタスクを登録できます。

atexit.register(func [、* args [、** kargs]])

クリーンアッププロセスでは、独自の待機を実装し、必要なタイムアウトが発生したときにそれを強制終了することもできます。

>>> import atexit
>>> import sys
>>> import time
>>> 
>>> 
>>>
>>> def cleanup():
...     timeout_sec = 5
...     for p in all_processes: # list of your processes
...         p_sec = 0
...         for second in range(timeout_sec):
...             if p.poll() == None:
...                 time.sleep(1)
...                 p_sec += 1
...         if p_sec >= timeout_sec:
...             p.kill() # supported from python 2.6
...     print 'cleaned up!'
...
>>>
>>> atexit.register(cleanup)
>>>
>>> sys.exit()
cleaned up!

Note-登録された関数は、このプロセス(親プロセス)が強制終了されると実行されません。

python> = 2.6の場合、次のWindowsメソッドは不要になりました

Windowsでプロセスを強制終了する方法を次に示します。 Popenオブジェクトにはpid属性があるため、success = win_kill(p.pid)(Needs pywin32 =インストール済み):

    def win_kill(pid):
        '''kill a process by specified PID in windows'''
        import win32api
        import win32con

        hProc = None
        try:
            hProc = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid)
            win32api.TerminateProcess(hProc, 0)
        except Exception:
            return False
        finally:
            if hProc != None:
                hProc.Close()

        return True
42
monkut

* nixでは、プロセスグループを使用すると役立つ場合があります。サブプロセスによって生成されたサブプロセスもキャッチできます。

if __== "__main__":
  os.setpgrp() # create new process group, become its leader
  try:
    # some code
  finally:
    os.killpg(0, signal.SIGKILL) # kill all processes in my group

別の考慮事項は、シグナルをエスカレートすることです:SIGTERM(killのデフォルトシグナル)からSIGKILL(a.k.a kill -9)。シグナルの間にしばらく待ってから、プロセスが正常に終了する機会を与えてからkill -9 それ。

33
orip

subprocess.Popen.wait()は、それらが死んでいることを保証する唯一の方法です。確かに、POSIX OSでは、お子様を待つ必要があります。多くの* nixは、「ゾンビ」プロセス、つまり親が待たなかった死んだ子供を作成します。

子が適度にうまく書かれている場合、終了します。多くの場合、子供はPIPEから読み取ります。入力を閉じることは、子供が店を閉じて終了するべきであるという大きなヒントです。

子供にバグがあり、終了しない場合、それを殺さなければならないかもしれません。このバグを修正する必要があります。

子が「永久に」ループであり、終了するように設計されていない場合は、それを強制終了するか、強制的に終了させる入力またはメッセージを提供する必要があります。


編集。

標準OSには、os.kill( PID, 9 )があります。キル-9は厳しいです、ところで。 SIGABRT(6?)またはSIGTERM(15)でそれらを殺すことができれば、それはより丁寧です。

Windows OSでは、os.killが機能しません。これを見てください ActiveState Recipe Windowsでプロセスを終了するため。

WSGIサーバーである子プロセスがあります。それらを終了するには、特別なURLでGETを実行します。これにより、子はクリーンアップして終了します。

14
S.Lott

警告:Linuxのみ!親が死んだときに子供に信号を受信させることができます。

最初にpython-prctl == 1.5.0をインストールしてから、親コードを変更して次のように子プロセスを起動します

subprocess.Popen(["sleep", "100"], preexec_fn=lambda: prctl.set_pdeathsig(signal.SIGKILL))

これが言うことは:

  • サブプロセスの起動:スリープ100
  • サブプロセスの分岐後、execの前に、子は「親が終了したときにSIGKILLを送信してください」と登録します。
4
Carl D'Halluin

(prctlをインストールせずに)Linuxのソリューションを見つけてください:

def _set_pdeathsig(sig=signal.SIGTERM):
    """help function to ensure once parent process exits, its childrent processes will automatically die
    """
    def callable():
        libc = ctypes.CDLL("libc.so.6")
        return libc.prctl(1, sig)
    return callable


subprocess.Popen(your_command, preexec_fn=_set_pdeathsig(signal.SIGTERM)) 
3
Patrick

oripの答えは役に立ちますが、プロセスを強制終了し、親にエラーコードを返すという欠点があります。私はこのようなことを避けました:

class CleanChildProcesses:
  def __enter__(self):
    os.setpgrp() # create new process group, become its leader
  def __exit__(self, type, value, traceback):
    try:
      os.killpg(0, signal.SIGINT) # kill all processes in my group
    except KeyboardInterrupt:
      # SIGINT is delievered to this process as well as the child processes.
      # Ignore it so that the existing exception, if any, is returned. This
      # leaves us with a clean exit code if there was no exception.
      pass

その後:

  with CleanChildProcesses():
    # Do your work here

もちろん、try/except/finallyでこれを行うことができますが、例外的なケースと非例外的なケースを別々に処理する必要があります。

3
Malcolm Handley

私はこの問題の小さなバリエーションが必要でした(サブプロセスをクリーンアップしますが、Pythonプログラム自体)を終了せずに、そして他の回答の中でここで言及されていないため:

p=subprocess.Popen(your_command, preexec_fn=os.setsid)
os.killpg(os.getpgid(p.pid), 15)

setsidは、新しいセッションでプログラムを実行し、新しいプロセスグループをそのプロセスとその子に割り当てます。したがって、os.killpgを呼び出しても、独自のpythonプロセスもダウンしません。

3
berdario

poll()

子プロセスが終了したかどうかを確認してください。 returncode属性を返します。

3
Igal Serban

Pythonプログラムの終了時に、作成されたすべてのサブプロセスが停止していることを確認する方法はありますか?サブプロセスとは、subprocess.Popen()で作成されたものを意味します。

カプセル化に違反している可能性がありますtest

subprocess._cleanup()
print subprocess._active == []

そうでない場合、発行しているすべてのキルを繰り返してから-9をキルする必要がありますか?クリーナーは何ですか?

外に出てすべての生存者を殺さずに、すべてのサブプロセスが停止していることを確認することはできません。しかし、この問題がある場合、それはおそらくより深い設計上の問題があるためです。

2
ddaa

実際にこれを行う必要がありましたが、リモートコマンドの実行が必要でした。サーバーへの接続を閉じることでプロセスを停止できるようにしたかったのです。また、たとえば、python replで実行している場合、Ctrl-Cを使用して終了できるようにするには、フォアグラウンドとして実行することを選択できます。

import os, signal, time

class CleanChildProcesses:
    """
    with CleanChildProcesses():
        Do work here
    """
    def __init__(self, time_to_die=5, foreground=False):
        self.time_to_die = time_to_die  # how long to give children to die before SIGKILL
        self.foreground = foreground  # If user wants to receive Ctrl-C
        self.is_foreground = False
        self.SIGNALS = (signal.SIGHUP, signal.SIGTERM, signal.SIGABRT, signal.SIGALRM, signal.SIGPIPE)
        self.is_stopped = True  # only call stop once (catch signal xor exiting 'with')

    def _run_as_foreground(self):
        if not self.foreground:
            return False
        try:
            fd = os.open(os.ctermid(), os.O_RDWR)
        except OSError:
            # Happens if process not run from terminal (tty, pty)
            return False

        os.close(fd)
        return True

    def _signal_hdlr(self, sig, framte):
        self.__exit__(None, None, None)

    def start(self):
        self.is_stopped = False
        """
        When running out of remote Shell, SIGHUP is only sent to the session
        leader normally, the remote Shell, so we need to make sure we are sent 
        SIGHUP. This also allows us not to kill ourselves with SIGKILL.
        - A process group is called orphaned when the parent of every member is 
            either in the process group or outside the session. In particular, 
            the process group of the session leader is always orphaned.
        - If termination of a process causes a process group to become orphaned, 
            and some member is stopped, then all are sent first SIGHUP and then 
            SIGCONT.
        consider: prctl.set_pdeathsig(signal.SIGTERM)
        """
        self.childpid = os.fork()  # return 0 in the child branch, and the childpid in the parent branch
        if self.childpid == 0:
            try:
                os.setpgrp()  # create new process group, become its leader
                os.kill(os.getpid(), signal.SIGSTOP)  # child fork stops itself
            finally:
                os._exit(0)  # shut down without going to __exit__

        os.waitpid(self.childpid, os.WUNTRACED)  # wait until child stopped after it created the process group
        os.setpgid(0, self.childpid)  # join child's group

        if self._run_as_foreground():
            hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)  # ignore since would cause this process to stop
            self.controlling_terminal = os.open(os.ctermid(), os.O_RDWR)
            self.orig_fore_pg = os.tcgetpgrp(self.controlling_terminal)  # sends SIGTTOU to this process
            os.tcsetpgrp(self.controlling_terminal, self.childpid)
            signal.signal(signal.SIGTTOU, hdlr)
            self.is_foreground = True

        self.exit_signals = dict((s, signal.signal(s, self._signal_hdlr))
                                 for s in self.SIGNALS)                                     

    def stop(self):
        try:
            for s in self.SIGNALS:
                #don't get interrupted while cleaning everything up
                signal.signal(s, signal.SIG_IGN)

            self.is_stopped = True

            if self.is_foreground:
                os.tcsetpgrp(self.controlling_terminal, self.orig_fore_pg)
                os.close(self.controlling_terminal)
                self.is_foreground = False

            try:
                os.kill(self.childpid, signal.SIGCONT)
            except OSError:
                """
                can occur if process finished and one of:
                - was reaped by another process
                - if parent explicitly ignored SIGCHLD
                    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
                - parent has the SA_NOCLDWAIT flag set 
                """
                pass

            os.setpgrp()  # leave the child's process group so I won't get signals
            try:
                os.killpg(self.childpid, signal.SIGINT)
                time.sleep(self.time_to_die)  # let processes end gracefully
                os.killpg(self.childpid, signal.SIGKILL)  # In case process gets stuck while dying
                os.waitpid(self.childpid, 0)  # reap Zombie child process
            except OSError as e:
                pass
        finally:
            for s, hdlr in self.exit_signals.iteritems():
                signal.signal(s, hdlr)  # reset default handlers

    def __enter__(self):
        if self.is_stopped:
            self.start()

    def __exit__(self, exit_type, value, traceback):
        if not self.is_stopped:
            self.stop()

初期設計をしてくれたMalcolm Handleyに感謝します。 Linuxでのpython2.7の使用。

2
matanmarkind

Windowsのソリューションは、win32 job apiを使用することです。 Windowsで子プロセスを自動的に破棄するにはどうすればよいですか?

既存のpython実装です

https://Gist.github.com/ubershmekel/119697afba2eaecc63

2
ubershmekel
0
st4rtx

これは私が私のposixアプリのためにしたことです:

アプリが存在する場合、このクラスのkill()メソッドを呼び出します。 http://www.pixelbeat.org/libs/subProcess.py

ここでの使用例: http://code.google.com/p/fslint/source/browse/trunk/fslint-gui#608

0
pixelbeat

同様の問題のために私が書いたパッケージsubaliveを試すことができます。 RPCを介して定期的にアライブpingを使用し、マスターが何らかの理由でアライブpingを停止すると、スレーブプロセスが自動的に終了します。

https://github.com/waszil/subalive

マスターの例:

from subalive import SubAliveMaster

# start subprocess with alive keeping
SubAliveMaster(<path to your slave script>)

# do your stuff
# ...

スレーブサブプロセスの例:

from subalive import SubAliveSlave

# start alive checking
SubAliveSlave()

# do your stuff
# ...
0
waszil