¿Cómo cierro el stdout-pipe al finalizar un proceso iniciado con el subproceso Popen de Python?

StackOverflow https://stackoverflow.com/questions/1624254

Pregunta

Me pregunto si es posible cerrar el conducto de comunicación al eliminar un subproceso iniciado en un subproceso diferente. Si no llamo a communication (), kill () funcionará como se esperaba, terminando el proceso después de un segundo en lugar de cinco.

Encontré una discusión sobre un problema similar aquí , pero no tengo respuestas reales. Supongo que tengo que poder cerrar la tubería o eliminar explícitamente el sub-subproceso (que es " dormir " en el ejemplo) y matarlo para desbloquear la tubería.

También traté de encontrar la respuesta a ella en SO, pero solo encontré this y this y this , que no aborda directamente este problema por lo que puedo decir ( ?).

Así que lo que quiero hacer es poder ejecutar un comando en un segundo hilo y obtener toda su salida, pero poder matarlo instantáneamente cuando así lo desee. Podría ir a través de un archivo y una cola similar o similar, pero creo que debería haber una mejor manera de hacer esto?

import subprocess, time
from threading import Thread

process = None

def executeCommand(command, runCommand):
    Thread(target=runCommand, args=(command,)).start()

def runCommand(command):
    global process
    args = command.strip().split()
    process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE)

    for line in process.communicate():
        if line:
            print "process:", line,

if __name__ == '__main__':
    executeCommand("./ascript.sh", runCommand)
    time.sleep(1)
    process.kill()

Este es el script:

#!/bin/bash
echo "sleeping five"
sleep 5
echo "slept five"

Salida

$ time python poc.py 
process: sleeping five

real    0m5.053s
user    0m0.044s
sys 0m0.000s
¿Fue útil?

Solución

Creo que el problema es que process.kill () solo elimina el proceso secundario inmediato (bash), no los subprocesos del script bash.

El problema y la solución se describen aquí:

Use Popen (..., preexec_fn = os.setsid) para crear un grupo de procesos y os.pgkill para matar a todo el grupo de procesos. por ejemplo,

import os
import signal
import subprocess
import time
from threading import Thread

process = None

def executeCommand(command, runCommand):
    Thread(target=runCommand, args=(command,)).start()

def runCommand(command):
    global process
    args = command.strip().split()
    process = subprocess.Popen(
        args, shell=False, stdout=subprocess.PIPE, preexec_fn=os.setsid)

    for line in process.communicate():
        if line:
            print "process:", line,

if __name__ == '__main__':
    executeCommand("./ascript.sh", runCommand)
    time.sleep(1)
    os.killpg(process.pid, signal.SIGKILL)

$ time python poc.py 
process: sleeping five

real    0m1.051s
user    0m0.032s
sys 0m0.020s

Otros consejos

Me parece que la forma más fácil de hacer esto y evitar los problemas de subprocesos múltiples sería establecer un indicador de eliminación desde el hilo principal y verificarlo en el hilo de ejecución del script justo antes de la comunicación, eliminando el script cuando la bandera es True .

Parece que puede ser una víctima de la concurrencia super gruesa de Python. Cambie su script a esto:

#!/bin/bash
echo "sleeping five"
sleep 5
echo "sleeping five again"
sleep 5
echo "slept five"

Y luego la salida se convierte en:

process: sleeping five

real    0m5.134s
user    0m0.000s
sys     0m0.010s

Si se ejecutara todo el script, el tiempo sería 10s. Así que parece que el hilo de control de Python no se ejecuta hasta después de que el script de bash se duerme. Del mismo modo, si cambia su secuencia de comandos a esto:

#!/bin/bash
echo "sleeping five"
sleep 1
sleep 1
sleep 1
sleep 1
sleep 1
echo "slept five"

Entonces la salida se convierte en:

process: sleeping five

real    0m1.150s
user    0m0.010s
sys     0m0.020s

En resumen, su código funciona como implementado lógicamente. :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top