Come posso chiudere lo stdout-pipe quando termina un processo avviato con il sottoprocesso di Python Popen?
-
06-07-2019 - |
Domanda
Mi chiedo se è possibile chiudere la pipa di comunicazione quando si uccide un sottoprocesso iniziato in un thread diverso. Se non chiamo communic (), allora kill () funzionerà come previsto, terminando il processo dopo un secondo anziché cinque.
Ho trovato una discussione su un problema simile qui , ma non ho avuto risposte reali. Presumo che devo essere in grado di chiudere la pipe o di uccidere esplicitamente il sottoprocesso (che è "sleep" nell'esempio) e ucciderlo per sbloccare la pipe.
Ho anche cercato di trovare la sua risposta su SO, ma ho trovato solo questo e this e this , che non affronta direttamente questo problema per quanto ne so ?).
Quindi la cosa che voglio fare è essere in grado di eseguire un comando in un secondo thread e ottenere tutto il suo output, ma essere in grado di ucciderlo istantaneamente quando lo desidero. Potrei passare attraverso un file e codificarlo o simile, ma penso che ci dovrebbe essere un modo migliore per farlo?
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()
Questo è lo script:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "slept five"
Output
$ time python poc.py
process: sleeping five
real 0m5.053s
user 0m0.044s
sys 0m0.000s
Soluzione
Penso che il problema sia che process.kill () uccide solo il processo figlio immediato (bash), non i sottoprocessi dello script bash.
Il problema e la soluzione sono descritti qui:
- http://www.doughellmann.com/PyMOTW/subprocess/ # processo-gruppi-sessions
- Come terminare un sottoprocesso Python avviato con shell = true
Usa Popen (..., preexec_fn = os.setsid) per creare un gruppo di processi e os.pgkill per uccidere l'intero gruppo di processi. ad esempio
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
Altri suggerimenti
Mi sembra che il modo più semplice per fare questo e eludere i problemi di multithreading sia impostare un kill flag dal thread principale e controllarlo nel thread in esecuzione dello script appena prima della comunicazione, uccidendo lo script quando la bandiera è True
.
Sembra che tu possa essere vittima della concorrenza a grana super grossolana di Python. Cambia il tuo script in questo:
#!/bin/bash
echo "sleeping five"
sleep 5
echo "sleeping five again"
sleep 5
echo "slept five"
E quindi l'output diventa:
process: sleeping five
real 0m5.134s
user 0m0.000s
sys 0m0.010s
Se viene eseguito l'intero script, il tempo sarebbe 10 s. Quindi sembra che il thread di controllo python non venga effettivamente eseguito fino a quando lo script bash non viene dormito. Allo stesso modo, se cambi lo script in questo:
#!/bin/bash
echo "sleeping five"
sleep 1
sleep 1
sleep 1
sleep 1
sleep 1
echo "slept five"
Quindi l'output diventa:
process: sleeping five
real 0m1.150s
user 0m0.010s
sys 0m0.020s
In breve, il codice funziona come implementato logicamente. :)