¿Por qué se cuelga Paramiko si lo usa mientras carga un módulo?
-
22-07-2019 - |
Pregunta
Ponga lo siguiente en un archivo hello.py (y easy_install paramiko
si no lo tiene):
hostname,username,password='fill','these','in'
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
Complete la primera línea apropiadamente.
Ahora escriba
python hello.py
y verá algunos resultados de ls.
Ahora, en su lugar, escriba
python
y luego desde el tipo de intérprete
import hello
y listo! Se cuelga! Se destrabará si envuelve el código en una función foo
y hace import hello; hello.foo ()
en su lugar.
¿Por qué se cuelga Paramiko cuando se usa dentro de la inicialización del módulo? ¿Cómo es que Paramiko sabe que se está utilizando durante la inicialización del módulo en primer lugar?
Solución
Paramiko usa hilos separados para el transporte subyacente. Debería nunca tener un módulo que genere un subproceso como efecto secundario de la importación. Según tengo entendido, hay un solo bloqueo de importación disponible, por lo que cuando un subproceso secundario de su módulo intenta otra importación, puede bloquearse indefinidamente, porque su subproceso principal aún mantiene el bloqueo. (Probablemente hay otras trampas que no conozco también)
En general, los módulos no deberían tener efectos secundarios de ningún tipo al importar, o obtendrá resultados impredecibles. Simplemente espere la ejecución con el truco __name__ == '__main__'
, y estará bien.
[EDITAR] Parece que no puedo crear un caso de prueba simple que reproduzca este punto muerto. Todavía supongo que es un problema de subprocesos con la importación, porque el código de autenticación está esperando un evento que nunca se activa. Esto puede ser un error en paramiko o python, pero la buena noticia es que nunca deberías verlo si haces las cosas correctamente;)
Este es un buen ejemplo de por qué siempre desea minimizar los efectos secundarios y por qué las técnicas de programación funcional son cada vez más frecuentes.
Otros consejos
Como JimB señaló que es un problema de importación cuando python intenta importar de forma implícita Decodificador str.decode ('utf-8')
en el primer uso durante un intento de conexión ssh. Consulte la sección Análisis para más detalles.
En general, uno no puede insistir lo suficiente como para evitar que un módulo genere automáticamente nuevos hilos en la importación. Si puede, trate de evitar el código del módulo mágico en general, ya que casi siempre produce efectos secundarios no deseados.
-
La solución fácil y sensata para su problema, como ya se mencionó, es colocar su código en un
if __name__ == '__main__':
que solo se ejecutará si ejecuta este módulo específico y no se ejecutará cuando este módulo sea importado por otros módulos. -
(no recomendado) Otra solución es hacer un str.decode ficticio ('utf-8') en su código antes de llamar a
SSHClient.connect ()
- vea el análisis a continuación .
Entonces, ¿cuál es la causa raíz de este problema?
Análisis (autenticación de contraseña simple)
Sugerencia: si desea depurar subprocesos en la importación de Python y establezca threading._VERBOSE = True
-
paramiko.SSHClient (). connect (.., look_for_keys = False, ..)
genera implícitamente un nuevo hilo para su conexión. También puede ver esto si activa la salida de depuración paraparamiko.transport
.
[Thread-5] [paramiko.transport] DEPURACIÓN: hilo de inicio (modo cliente): 0x317f1d0L
-
esto se hace básicamente como parte de
SSHClient.connect ()
. Cuando se llama aclient.py:324::start_client ()
, se crea un bloqueotransport.py:399::event=threading.Event ()
y el hilo es iniciótransport.py:400::self.start ()
. Tenga en cuenta que el métodostart ()
ejecutará el métodotransport.py:1565::run ()
de la clase. transport.py:1580::self._log (..)
imprime nuestro mensaje de registro " hilo de inicio " y luego procede atransport.py:1584::self._check_banner()
.check_banner
hace una cosa. Recupera el banner ssh (primera respuesta del servidor)transport.py:1707::self.packetizer.readline(timeout)
(tenga en cuenta que el tiempo de espera es solo un tiempo de espera de lectura de socket), comprueba un salto de línea al final y de lo contrario se agota el tiempo de espera.-
En caso de que se reciba un banner de servidor, intenta decodificar utf-8 la cadena de respuesta
packet.py:287::return u (buf)
y ahí es donde ocurre el punto muerto.u (s, encoding = 'utf-8')
realiza un str.decode ('utf-i') e importa implícitamenteencodings.utf8
encodificaciones : 99
a través deencodings.search_function
que termina en un punto muerto de importación.
Entonces, una solución sucia sería importar el decodificador utf-8 una vez para no bloquear esa importación específica debido a los efectos secundarios de la importación del módulo. ( '' .decode ('utf-8')
)
Fix
arreglo sucio - no recomendado
import paramiko
hostname,username,password='fill','these','in'
''.decode('utf-8') # dirty fix
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
buena solución
import paramiko
if __name__ == '__main__':
hostname,username,password='fill','these','in'
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
" " .decode (" utf-8 ") no funcionó para mí, terminé haciendo esto.
from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")
Tengo un contenedor para paramiko con eso implementado. https://github.com/bucknerns/sshaolin