español - subprocess python windows
Python: ¿cómo matar los procesos infantiles cuando el padre muere? (2)
¡Heh, yo solo estaba investigando esto ayer! Asumiendo que no puedes alterar el programa infantil:
En Linux, prctl(PR_SET_PDEATHSIG, ...)
es probablemente la única opción confiable. (Si es absolutamente necesario que se elimine el proceso secundario, es posible que desee establecer la señal de muerte en SIGKILL en lugar de SIGTERM; el código al que se vincula utiliza SIGTERM, pero el niño tiene la opción de ignorar SIGTERM si así lo desea. )
En Windows, las opciones más confiables es usar un objeto Job . La idea es que cree un "Trabajo" (un tipo de contenedor para procesos), luego coloque el proceso hijo en el Trabajo y configure la opción mágica que dice "cuando nadie tiene un ''identificador'' para este Trabajo. , luego mata los procesos que están en él ". De forma predeterminada, el único ''identificador'' del trabajo es el que mantiene su proceso principal, y cuando el proceso primario finaliza, el sistema operativo pasará y cerrará todos sus manejadores, y luego notará que esto significa que no hay manejadores abiertos para el trabajo. Así que entonces mata al niño, según lo solicitado. (Si tiene varios procesos secundarios, puede asignarlos todos al mismo trabajo). Esta respuesta tiene un código de ejemplo para hacerlo, usando el módulo win32api
. Ese código utiliza CreateProcess
para iniciar el hijo, en lugar de subprocess.Popen
. El motivo es que necesitan obtener un "identificador de proceso" para el hijo generado, y CreateProcess
devuelve de forma predeterminada. Si prefiere usar subprocess.Popen
, entonces aquí hay una copia (no probada) del código de esa respuesta, que usa subprocess.Popen
y OpenProcess
lugar de 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)
Técnicamente, hay una pequeña condición de carrera aquí en caso de que el niño muera entre las llamadas de Popen
y OpenProcess
, puedes decidir si quieres preocuparte por eso.
Una desventaja de usar un objeto de trabajo es que cuando se ejecuta en Vista o Win7, si su programa se inicia desde el shell de Windows (es decir, al hacer clic en un icono), es probable que ya haya un objeto de trabajo asignado e intente crear un El nuevo objeto de trabajo fallará. Win8 soluciona esto (permitiendo que los objetos de trabajo estén anidados), o si su programa se ejecuta desde la línea de comandos, entonces debería estar bien.
Si puede modificar el hijo (por ejemplo, como cuando se usa el multiprocessing
), entonces probablemente la mejor opción es pasar el PID del padre al hijo (por ejemplo, como un argumento de línea de comando, o en el argumento args=
a multiprocessing.Process
), y entonces:
En POSIX: Genere un hilo en el hijo que solo llame a os.getppid()
vez en cuando, y si el valor de retorno deja de coincidir con el pid pasado del padre, llame a os._exit()
. (Este enfoque es portátil para todos los Unixes, incluido OS X, mientras que el truco prctl
es específico de Linux).
En Windows: Genere un hilo en el hijo que utiliza OpenProcess
y os.waitpid
. Ejemplo usando 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)
Esto evita cualquiera de los posibles problemas con los objetos de trabajo que mencioné.
Si desea estar realmente seguro, puede combinar todas estas soluciones.
¡Espero que ayude!
El proceso hijo se inicia con
subprocess.Popen(arg)
¿Hay alguna manera de asegurar que se elimine cuando el padre termina de forma anormal? Necesito esto para trabajar tanto en Windows como en Linux. Soy consciente de esta solución para Linux .
Editar:
el requisito de iniciar un proceso hijo con subprocess.Popen(arg)
se puede relajar, si existe una solución utilizando un método diferente para iniciar un proceso.
El objeto Popen ofrece los métodos de terminar y matar.
https://docs.python.org/2/library/subprocess.html#subprocess.Popen.terminate
Estos envían las señales SIGTERM y SIGKILL para usted. Puedes hacer algo parecido a lo siguiente:
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()
ACTUALIZAR:
Usted tiene razón: el código anterior no lo protegerá contra choques duros o alguien que mate su proceso. En ese caso, puede intentar ajustar el proceso secundario en una clase y emplear un modelo de sondeo para ver el proceso principal. Tenga en cuenta que psutil no es estándar.
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)
En este ejemplo, ejecuto ''ping -t localhost'' b / c que se ejecutará para siempre. Si mata el proceso principal, el proceso secundario (el comando ping) también se eliminará.