Fabric: Stdin del terminal local desconectado si la ejecución de ThreadingGroup incluye suspensión

Creado en 25 jun. 2018  ·  22Comentarios  ·  Fuente: fabric/fabric

Estoy usando un grupo de subprocesos para ejecutar comandos de shell. Después de ejecutar un script que incluye un sleep , el terminal local se deja con stdin separado (las pulsaciones de teclas no son visibles en la línea de comandos) y el terminal debe reiniciarse.

He intentado esto varias veces y descubrí que solo sucede con ThreadingGroups (SerialGroups está bien). El comando sleep puede estar en cualquier lugar de una línea (primer comando, medio, último) y se puede unir en una línea con punto y coma o doble ampersand. Todos los comandos se ejecutan como se esperaba, pero el terminal permanece en mal estado.

Curiosamente, si la ejecución anterior salió con una excepción no detectada, la terminal no se verá afectada.

Reproducir:

from fabric import ThreadingGroup as Group

# raise ValueError()
remotes = Group("host1.example.com", "host2.example.com")
result = remotes.run("echo 1; sleep 1; echo 2")

Ejecute el script de arriba. Después de que salga, escriba algo en la línea de comando. Si no ve ningún resultado, <ctrl>+c y escriba reset<enter> . Para ver el comportamiento posterior a la excepción, descomente la línea raise , ejecute el código, comente la línea y ejecute dos veces más. La primera ejecución exitosa dejará el terminal en buen estado. El segundo dejará stdin separados.

Descubrí este problema con sleep en mis pruebas, pero es posible que otros comandos tengan el mismo efecto. También existe la posibilidad de que esté haciendo algo mal. Si ese es el caso, mis disculpas.

Mi configuración:
pitón 3.6.4
tejido 2.1.3
OSX 10.13.5, conectándose a Ubuntu 14.04

Bug Needs investigation

Todos 22 comentarios

Consulte el n. ° 1814 como posible segundo caso de problema reproducible.

Esto me suena como un error legítimo y no estoy seguro de qué lo está causando. Huele a que podría ser un problema genérico de Unix con tuberías de terminal conectadas a múltiples subprocesos a la vez, o (especialmente siguiendo el ejemplo de # 1814) una condición de carrera alrededor del estado de la tubería, o algo así.

Intentará reproducir y confundir una causa / solución.

Además, esto probablemente requiera una corrección en el nivel de invocación y puede estar puramente en su dominio (en la medida en que simplemente no he hecho mucho con el subproceso en un contexto de invocación puro todavía; pero vea, por ejemplo, pyinvoke / invoke # 194 - es una cosa que debería suceder allí también). En ese caso, moveré esto a un ticket allí y el "arreglo" de Fabric sería actualizar el Invoke una vez que se publique el arreglo.

Estaba en Ubuntu 16.04.2 conectándome al mismo.

Otro informe del mismo problema en el n. ° 1829. Este es mi próximo hito de corrección de errores y me concentraré en eso, con suerte, el próximo día de OSS (lunes).

Intenté reproducir esto (rama 2.0, Python 3.6.4, macOS 10.12) y, desafortunadamente, no pude. Primero probé con doble localhost, luego dos instancias de nube remota separadas, sin dados de ninguna manera; mi terminal está bien después.

Ir a probar un contenedor de Linux en un momento por si eso ayuda, pero dado que el OP también estaba en macOS, no estoy seguro de que haga una diferencia. También intentaré ejecutarlo en un bucle para ver si es solo una reproducción ocasional.

También lo probaré en 2.1 en caso de que de alguna manera lo introduzcamos en 2.1, aunque esto parece muy poco probable.

@jensenak @nicktimko ¿estás reproduciendo esto el 100% del tiempo? 50%? 5%?

@bitprophet en 2.1.3 estaba sucediendo en mi flujo de trabajo real con bastante frecuencia (> 80%, también iba en paralelo a 6 servidores, no 2), aunque en mi ejemplo artificial de # 1814 es mucho más bajo, tal vez 20%. Puedo intentar crear una configuración de Docker o, en su defecto, una configuración de Vagrant para reproducir.

@bitprophet Esto ha sido el 100% del tiempo para mí. Solo para estar seguro, comencé un nuevo virtualenv con solo tela instalada. Probé 2.0, 2.1 y 2.2. El código de ejemplo que pegué produjo el comportamiento descrito cada vez. En todas las pruebas, me estaba conectando a mandos a distancia de Ubuntu 14.04.

Estoy en una versión diferente de OSX (10.13). ¿Quizás eso esté relacionado? Aunque @nicktimko no estaba en OSX en absoluto.

En caso de que otra versión sea un problema, así es como se veía pip freeze en mi virtualenv:

asn1crypto==0.24.0
bcrypt==3.1.4
cffi==1.11.5
cryptography==2.3
fabric==2.2.1
idna==2.7
invoke==1.1.0
paramiko==2.4.1
pyasn1==0.4.4
pycparser==2.18
PyNaCl==1.2.1
six==1.11.0

Dado que todos estos se instalaron como dependencias de la estructura 2.2, esperaría que sus versiones se vean similares.

Si puedo hacer más para ayudar, estoy más que dispuesto. Simplemente no estoy seguro de dónde más buscar.

¿Con qué compromiso debo probar? ¿Ha realizado algún cambio recientemente que pueda afectar las cosas? Intentaré con la congelación anterior, también podría proporcionar otro reqs.txt congelado y puedo ver si eso funciona o no para mí.

@nicktimko @jensenak Gracias por la información adicional. Seguiré intentando reproducirlo aquí; al 20% definitivamente no lo habría intentado lo suficiente como para activarlo. Mis controles remotos han sido Mac y algunos Debian más antiguos, puedo probar Ubuntu Trusty en caso de que sea de alguna manera específico para eso (lo cual sería extraño, pero bueno, todo esto es extraño).

Además, ¿cuáles son sus entornos de shell locales? El mío es zsh en (de nuevo, macOS 10.12) incorporado Terminal.app, dentro de tmux. También probaré algunas permutaciones alrededor de ese ángulo en un momento.

AHA. ¡Esto parece ser específico de bash! Todavía no pude reproducir con zsh fuera de tmux, pero en el momento en que lo intento con bash, inmediatamente se mencionan los síntomas. Lo mismo ocurre dentro de tmux, por lo que tmux no tiene relación, es una cuestión de shell.

_Por qué_ esto se comportaría de manera diferente bajo bash vs zsh, no tengo idea inmediata. ¿Podría ser específico de cómo se implementan o (parece más probable) tal vez algo en mis archivos de puntos zsh esté previniendo el problema? Tendrá que excavar ... aunque es muy probable que sea necesario identificar una solución en el lado de Python de cualquier manera.

EDITAR: también, la reproducción ocurre incluso cuando se conecta al sshd de mi localhost varias veces al mismo tiempo, lo cual no es demasiado sorprendente. Entonces, el extremo remoto parece no importar.

Además, traté de verificar la nota sobre "la ejecución anterior excepto evita el problema para la siguiente ejecución solamente", pero eso no me ocurrió; Tengo el comportamiento todo el tiempo independientemente.

Moar: Eliminé el sleep para ver qué pasaba; Todavía puedo reproducir, aunque ahora es un poco más intermitente (aunque debido a que esto no es algo fácil de reproducir en un bucle automático, todo es reproducción manual, lo que significa un número bajo de casos de prueba, lo que significa que el verdadero% de ocurrencia será real difícil de medir con precisión.)

Eso también es bueno, cuantos menos desencadenantes extraños, mejor. Esto huele a que _ debería_ ser un error básico y tonto de subprocesamiento en alguna parte, que normalmente no se vería afectado por nada específico en el extremo remoto o local, aparte del tiempo que hace que una condición de carrera (o w / e) sea más probable.

Me pregunto si esto está relacionado con pyinvoke / invoke # 552, que se reduce a la subclase de subprocesos de manejo de excepciones de Invoke (utilizada en ThreadingGroup aquí) que posiblemente haya estropeado la detección de muerte de subprocesos.

Tendré que asegurarme de que entiendo que (su solución potencial, pyinvoke / invoke # 553, no fue una fusión instantánea, ya que parecía extraño que hubiéramos obtenido algo aparentemente funcional, tan mal) y luego ver si aplicarlo hace este síntoma desaparece.

Quité el sueño para ver qué pasaba; Todavía puedo reproducirme, aunque ahora es un poco más intermitente

Suena como el caso de prueba que tenía, donde necesitaba golpearlo varias veces antes de que fallara. Parece que lo tienes bien manejado

Hoy noté que tampoco podía reproducir el comportamiento de excepción que describí hace un mes ... desafortunadamente no recuerdo lo que estaba haciendo entonces. : /

De hecho, estoy ejecutando bash aquí. Buen descubrimiento. El hecho de que el problema sea intermitente sin dormir me hace preguntarme si se trata de una condición de carrera de algún tipo.

Dices eso, pero ahora no puedo volver a reproducirlo, o al menos es MUY intermitente. Volver a dormir hace que suba con mucha más frecuencia. Me encantan las condiciones de carrera.

Al observar el problema de Invoke, el reportero incluso menciona un terminal estropeado como síntoma; pero extrañamente no puedo reproducir _ ese_ síntoma incluso bajo bash, con su código. Aún así, no me sorprendería si la causa raíz es la misma (tiene que ver con algunas cosas sobre la muerte de subprocesos y el cierre de stdin, o tal vez volver al almacenamiento en búfer linewise, correctamente antes de la salida).

Verificando los puntos que mencionó el otro problema, contra el caso de reproducción aquí:

  • el bit ExceptionHandlingThread.is_dead no parece importar, es presumiblemente correcto, lo que tiene cierto sentido ya que está destinado a manejar excepciones en el hilo y ninguno de estos casos realmente maneja excepciones. is_dead es False para los 3 subprocesos de trabajo (stdin / out / err) cuando lo esperaría.
  • la afirmación de que no estamos cerrando correctamente el subproceso 'stdin se siente más cerca de la marca; si eso deja el stdin del terminal de control adjunto a un descriptor de archivo ahora muerto o algo ...? (De todos modos, debería saber mejor lo que sucede en este caso).

    • Excepto ... en el caso de Fabric, no hay un subproceso local ni un paso directo de descriptores de archivo, por lo que ese no puede ser el caso.

    • ¿Significa que es más probable que el problema sea otra cosa?


Intentando otra táctica ... ¿qué pasa exactamente con el entorno de la terminal después de que aparece el error, ha cambiado? Ejecutando stty -a bajo bash con y sin corrupción de errores presente, las diferencias que puedo ver son:

  • lflags : la terminal con errores tiene -icanon , -echo , -pendin (en comparación con el término normal donde todos carecen de un signo menos). No hacer eco ciertamente parece un problema, asumiendo que eso es lo que eso significa.
  • iflags : bugged-out tiene -ixany y ignpar (el primer ejemplo de algo configurado, no desarmado, en la mala configuración)
  • oflags y cflags idénticos, al igual que cchars (estaría realmente extraño si los caracteres de control hubieran cambiado ...)

Según man stty :

  • icanon controla el procesamiento de BORRAR y MATAR; probablemente no sea una gran diferencia (aunque podría ser interesante por qué esto está configurado o no configurado)
  • echo es lo que parece, si se hace eco, y es claramente el mayor problema práctico del error.
  • pendin indica si la entrada (presumiendo stdin) está pendiente después de un cambio canónico (y dado que icanon está claramente invertido ... sí) y se volverá a ingresar cuando una lectura se vuelva pendiente o más entrada llega. No está claro por qué esto importa, o por qué está configurado normalmente y no configurado cuando tiene errores (hubiera esperado lo último, en todo caso).
  • ixany permite que cualquier carácter 'inicie salida' (y cuando no está configurado, solo permite INICIO. ¿Ok?)
  • ignpar significa ignorar (o desarmar, no ignorar) caracteres con errores de paridad.

En general, se siente como si se estuviera aplicando un 'modo' de nivel superior al terminal, similar a cómo configuramos stdin en lectura con búfer de caracteres para permitirnos leer 1 byte a la vez en lugar de esperar hasta que el usuario ingrese.

Lo que suena como el comportamiento que se muestra (más o menos ...), y sobre lo que me preguntaba antes; pero al leer el código en cuestión (porque ese parche de Invoke también lo menciona, aunque re: thread death), el cambio de modo se expresa como un administrador de contexto, por lo que _debería_ estar siempre desarmado independientemente de cómo salgamos de ese bucle. Pero tendré que comprobarlo tres veces ahora.

Menor: simplemente decir stty echo para configurar echo es suficiente para 'arreglar' una terminal; incluso si icanon , pendin etc todavía están sin configurar. Realmente no ayuda, pero bueno, es bueno saberlo, supongo.

¡OK! Creo que lo descubrí, mientras miraba ese contextmanager: probablemente sea porque el contextmanager captura el estado actual del terminal para restaurar al cierre del bloque. Pero, ¿qué estamos haciendo en este caso? Estamos ejecutando _dos subprocesos de alto nivel separados_, cada uno de los cuales ejecuta su propia copia de este administrador de contexto.

Y en Invoke, aunque pretendemos ser seguros para los subprocesos, actualmente no probamos nada más que nuestros propios subprocesos de E / S de bajo nivel; El 99% de la "seguridad de subprocesos" es simplemente el uso del estado de objeto autónomo en lugar del horrible estado del módulo global de Fabric 1. Por lo tanto, esta parte particular de mantenimiento del estado nunca se ejecuta al mismo tiempo que ella misma (en parte porque el "estado" es literalmente la terminal de control, de la cual solo hay una, así que ... estado global ...).

Aún no lo he probado al 100% (a punto de hacerlo), pero no hay forma de que esto no sea así. Es muy probable que el subproceso que se ejecuta en segundo lugar capture los atributos de la terminal de control _después de que el primer subproceso ya lo haya configurado en modo de búfer de caracteres; entonces, si ese segundo hilo también _ termina_ el segundo (de nuevo, probablemente pero no seguro) "restaura" el mal estado, deshaciendo efectivamente la restauración del primer hilo.

Confirmado que el indicador ECHO, por ejemplo, definitivamente está siendo capturado por el administrador de contexto que no es el primero, y luego restaurado por el mismo. Trabajando en una solución, que creo que terminará siendo simplemente "tratar de averiguar si setcbreak parece ya aplicado, y no operativo en ese caso en lugar de hacer el baile de instantánea-modificación-restauración".

Debería tener el efecto deseado, es un poco más limpio para arrancar (nunca ejecuta setcbreak> 1 vez) y evita un caso de esquina en el que una solución ingenua siempre puede simplemente configurar ECHO, etc. tty-like pero _ya_ ya estaba configurado para no hacer eco. (Es improbable, claro, pero probablemente no imposible).

Dado que este es un problema de solo invocación, le daré un hogar en ese rastreador; espero hacer una prueba y una solución para esto pronto, pero si tienen algo más que agregar, ingresen a

Para ser claro, una vez que se solucione, debería estar disponible en Invoke 1.0.2 / 1.1.1 (y posiblemente 1.2.0 si lo obtengo al mismo tiempo) y _no_ Las actualizaciones de Fabric deberían ser necesarias, solo Invoke.

@bitprophet ¡Genial! Funciona después de actualizar Invoke :)
Gracias por su esfuerzo.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

neemxyang picture neemxyang  ·  6Comentarios

peteruhnak picture peteruhnak  ·  6Comentarios

Grazfather picture Grazfather  ·  4Comentarios

peteruhnak picture peteruhnak  ·  4Comentarios

supriyopaul picture supriyopaul  ·  4Comentarios