¿Por qué subprocess.Popen no espera hasta que finalice el proceso secundario?

Estoy teniendo un problema con el método subprocess.Popen de Python.

Aquí hay un script de prueba que demuestra el problema. Se está ejecutando en un cuadro de Linux.

#!/usr/bin/env python import subprocess import time def run(cmd): p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) return p ### START MAIN # copy some rows from a source table to a destination table # note that the destination table is empty when this script is run cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' run(cmd) # check to see how many rows exist in the destination table cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' process = run(cmd) count = (int(process.communicate()[0][:-1])) # if subprocess.Popen() waited for the child to terminate than count should be # greater than 0 if count > 0: print "success: " + str(count) else: print "failure: " + str(count) time.sleep(5) # find out how many rows exists in the destination table after sleeping process = run(cmd) count = (int(process.communicate()[0][:-1])) print "after sleeping the count is " + str(count) 

Por lo general, la salida de este script es:

 success: 100000 

pero a veces es

 failure: 0 after sleeping the count is 100000 

Tenga en cuenta que en el caso de falla, la selección inmediatamente después de la inserción muestra 0 filas, pero después de dormir durante 5 segundos, una segunda selección muestra correctamente un número de filas de 100000. Mi conclusión es que una de las siguientes afirmaciones es verdadera:

  1. subprocess.Popen no está esperando a que finalice el subproceso secundario – Esto parece contradecir la documentación
  2. el inserto mysql no es atómico, mi comprensión de mysql parece indicar que el inserto es atómico
  3. La selección no es ver el recuento de filas correcto de inmediato. De acuerdo con un amigo que sabe mysql mejor que yo, esto tampoco debería suceder

¿Qué me estoy perdiendo?

Para su información, soy consciente de que esta es una forma pirata de interactuar con mysql de Python y MySQLdb probablemente no tenga este problema, pero tengo curiosidad por saber por qué este método no funciona.

subprocess.Popen , cuando se crea una instancia, ejecuta el progtwig. Sin embargo, no lo espera; lo dispara en segundo plano como si hubiera escrito cmd & en un shell. Por lo tanto, en el código anterior, esencialmente ha definido una condición de carrera: si las inserciones pueden terminar a tiempo, parecerá normal, pero si no, obtendrá el resultado inesperado. No está esperando a que termine su primer PID de run() ‘, simplemente está devolviendo su instancia de Popen y continuando.

No estoy seguro de cómo este comportamiento contradice la documentación, porque hay algunos métodos muy claros en Popen que parecen indicar que no se espera, como:

 Popen.wait() Wait for child process to terminate. Set and return returncode attribute. 

Estoy de acuerdo, sin embargo, en que la documentación para este módulo podría mejorarse.

Para esperar a que finalice el progtwig, recomiendo usar el método de conveniencia del subprocess.call , subprocess.call , o usar la communicate en un objeto Popen (para el caso de que necesite stdout). Ya estás haciendo esto para tu segunda llamada.

 ### START MAIN # copy some rows from a source table to a destination table # note that the destination table is empty when this script is run cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' subprocess.call(cmd) # check to see how many rows exist in the destination table cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) try: count = (int(process.communicate()[0][:-1])) except: count = 0 

Además, en la mayoría de los casos, no es necesario ejecutar el comando en un shell. Este es uno de esos casos, pero tendrá que volver a escribir su comando como una secuencia. Hacerlo de esa manera también le permite evitar la inyección de shell tradicional y preocuparse menos por la cotización, como por ejemplo:

 prog = ["mysql", "-u", "ve", "--execute", 'insert into foo values ("snargle", 2)'] subprocess.call(prog) 

Esto incluso funcionará, y no se inyectará como esperaría:

 prog = ["printf", "%s", "<", "/etc/passwd"] subprocess.call(prog) 

Inténtalo interactivamente. Evita las posibilidades de inyección de shell, especialmente si acepta la entrada del usuario. Sospecho que está utilizando el método de comunicación menos impresionante de la cadena con el subproceso porque tuvo problemas para hacer que las secuencias funcionen: ^)

Si no necesitas absolutamente usar subproceso y popen, generalmente es más sencillo usar os.system . Por ejemplo, para los scripts rápidos a menudo hago algo como esto:

 import os run = os.system #convenience alias result = run('mysql -u ve --execute="select * from wherever" test') 

A diferencia de popen, os.system esperar a que el proceso regrese antes de pasar a la siguiente etapa de su script.

Más información al respecto en los documentos: http://docs.python.org/library/os.html#os.system

Amigo, ¿por qué pensaste que el subprocess.Popen devolvió un objeto con un método de wait , a menos que fuera porque la espera NO fue implícita, intrínseca, inmediata e inevitable, como parece suponer …?! La razón más común para generar un subproceso no es esperar inmediatamente a que termine, sino dejarlo continuar (p. Ej., En otro núcleo, o en el peor de los casos por tiempo limitado), ese es el aspecto del sistema operativo y del hardware. ) al mismo tiempo que el proceso padre continúa; cuando el proceso principal debe esperar a que finalice el subproceso, obviamente llamará a la wait en el objeto devuelto por el subprocess.Process original.Proceso de llamada.