Detectar cuando un proceso hijo está esperando entrada

Estoy escribiendo un programa de Python para ejecutar un código arbitrario subido por el usuario (y, por lo tanto, en el peor de los casos, inseguro, erróneo y fallido) en un servidor Linux. Dejando de lado las preguntas de seguridad, mi objetivo es determinar si el código (que podría estar en cualquier idioma, compilado o interpretado) escribe las cosas correctas parastdout, stderr y otros archivos en la entrada dada introducida en el programastdin. Después de esto, necesito mostrar los resultados al usuario.

La solucion actual

Actualmente, mi solución es generar el proceso hijo usandosubprocess.Popen(...) con manejadores de archivos para elstdout, stderr ystdin. El archivo detrás de lastdin el asa contiene las entradas que el programa lee durante la operación, y una vez que el programa ha terminado, elstdout ystderr Los archivos se leen y se verifican para ver si están correctos.

El problema

De lo contrario, este enfoque funciona perfectamente, pero cuando muestro los resultados, no puedo combinar las entradas y salidas dadas para que las entradas aparezcan en los mismos lugares que lo harían al ejecutar el programa desde un terminal. Es decir. para un programa como

print "Hello."
name = raw_input("Type your name: ")
print "Nice to meet you, %s!" % (name)

El contenido del archivo que contiene el programa.stdout sería, después de correr, ser:

Hello.
Type your name: 
Nice to meet you, Anonymous!

Dado que el contenido del archivo que contiene elstdin fueronAnonymous<LF>. Entonces, en resumen, para el código de ejemplo dado (y, equivalentemente, paraalguna otro código) Quiero lograr un resultado como:

Hello.
Type your name: Anonymous
Nice to meet you, Anonymous!

Por lo tanto, el problema es detectar cuando el programa está esperando una entrada.

Métodos probados

He intentado los siguientes métodos para resolver el problema:

Popen.communicate (...)

Esto permite que el proceso padre envíe por separado los datos a lo largo de untubo, pero solo se puede llamar una vez y, por lo tanto, no es adecuado para programas con múltiples salidas y entradas, como se puede deducir de la documentación.

Leyendo directamente dePopen.stdout yPopen.stderr y escribiendo aPopen.stdin

La documentación advierte contra esto, y laPopen.stdouts .read() y.readline() Las llamadas parecen bloquearse infinitamente cuando los programas comienzan a esperar por la entrada.

Utilizandoselect.select(...) para ver si los manejadores de archivos están listos para E / S

Esto no parece mejorar nada. Al parecer, las tuberías están siempre listas para leer o escribir, por lo queselect.select(...) No ayuda mucho aquí.

Usando un hilo diferente para la lectura sin bloqueo

Como se sugiere enesta respuesta, He intentado crear un separadoHilo() que almacena los resultados de la lectura de lastdout en unaCola(). Las líneas de salida antes de una línea que requieren la entrada del usuario se muestran bien, pero la línea en la que el programa comienza a esperar la entrada del usuario ("Type your name: " en el ejemplo anterior) nunca se lee.

Usando unPTY esclavo como el proceso hijo 'maneja'

Como se indicaaquí, He intentadopty.openpty() para crear un pseudo terminal con descriptores de archivo maestro y esclavo. Después de eso, he dado el descriptor del archivo esclavo como un argumento para elsubprocess.Popen(...) llamadastdout, stderr ystdin parámetros Leyendo a través del descriptor de archivo maestro abierto conos.fdopen(...) produce el mismo resultado que al utilizar un hilo diferente: la línea que exige la entrada no se lee.

Editar: Usando el ejemplo de @Antti Haapala depty.fork() para la creación de procesos hijo en lugar desubprocess.Popen(...) Parece que también me permite leer la salida creada porraw_input(...).

Utilizandoespere

También he probado elread(), read_nonblocking() yreadline() métodos (documentadosaquí) de un proceso generado con pexpect, pero el mejor resultado, que obtuve conread_nonblocking(), es el mismo que antes: la línea con salidas antes de querer que el usuario ingrese algo no se lee. es lo mismo que con un PTY creado conpty.fork(): la línea de entrada exigentehace leer

Editar: Mediante el usosys.stdout.write(...) ysys.stdout.flush() en lugar deprinten midominar El programa, que crea el elemento secundario, parece corregir la línea de aviso que no se muestra, aunque en realidad se leyó en ambos casos.

Otros

También he intentadoselect.poll(...), pero parecía que la tubería o los descriptores de archivos maestros PTY están siempre listos para escribir.

NotasOtras solucionesLo que también se me pasó por la mente es intentar alimentar la entrada cuando ha pasado algún tiempo sin que se haya generado una nueva salida. Esto, sin embargo, es arriesgado, porque no hay manera de saber si el programa está haciendo un cálculo pesado.Como @Antti Haapala mencionó en su respuesta, elread() El envoltorio de llamadas del sistema de glibc podría reemplazarse para comunicar las entradas al programa maestro. Sin embargo, esto no funciona con programas vinculados estáticamente o ensamblados. (Aunque ahora que lo pienso, cualquier llamada de este tipo podría ser interceptada desde el código fuente y reemplazada por la versión parcheada deread() - podría ser minucioso implementar aún.)Modificando el código del kernel de Linux para comunicar elread() Las llamadas al programa son probablemente una locura ...PTYs

Creo que el PTY es el camino a seguir, ya que simula una terminal y se ejecutan programas interactivos en todas partes. La pregunta es, ¿cómo?

Respuestas a la pregunta(2)

Su respuesta a la pregunta