Warum paramiko hängen, wenn Sie es beim Laden eines Moduls verwenden?
-
22-07-2019 - |
Frage
Setzen Sie die folgenden in eine Datei hello.py (und easy_install paramiko
wenn Sie es nicht vor):
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()
Geben Sie hier die erste Zeile entsprechend.
Geben Sie nun
python hello.py
und Sie werden einige ls-Ausgabe.
Jetzt anstelle Typ
python
und dann aus dem Interpreter Typ
import hello
und voila! Es hängt! Es wird unhang wenn Sie den Code in einer Funktion foo
wickeln und sie import hello; hello.foo()
statt.
Warum hängt paramiko wenn im Modul Initialisierung verwendet? Wie ist paramiko auch bewusst, dass es bei der Initialisierung des Moduls in erster Linie verwendet wird?
Lösung
paramiko verwendet separates Threads für den zugrunde liegenden Transport. Sie sollte nie hat ein Modul, das einen Faden als Nebeneffekt des Imports laicht. Wie ich es verstehe, gibt es eine einzige Importsperre zur Verfügung, so dass, wenn ein Kind Thread aus Ihrem Modul einer weiteren Import versucht, kann es auf unbestimmte Zeit sperren, weil Ihr Hauptthread noch die Sperre hält. (Es gibt wahrscheinlich andere gotchas, die ich nicht bewusst zu bin)
In der Regel Module sollten keine Nebenwirkungen jeglicher Art haben beim Import, oder Sie gehen zu unvorhersehbaren Ergebnissen kommen. Nur halten Sie die Ausführung mit dem __name__ == '__main__'
Trick, und Sie werden in Ordnung sein.
[EDIT] Ich kann nicht scheinen, einen einfachen Testfall zu erstellen, die aus dieser Sackgasse wiedergibt. Ich gehe davon nach wie vor, es ist ein Threading-Problem mit dem Import, weil der Auth-Code wird auf ein Ereignis warten, die nie ausgelöst. Dies kann ein Fehler in paramiko sein, oder Python, aber die gute Nachricht ist, dass Sie nicht immer es sehen sollen, wenn Sie die Dinge richtig tun;)
Dies ist ein gutes Beispiel dafür, warum Sie immer Nebenwirkungen minimieren wollen, und warum funktionale Programmiertechniken häufiger geworden sind.
Andere Tipps
Wie JimB wies darauf hin, es ist eine Import Ausgabe , wenn Python versucht, die implizit zu importieren str.decode('utf-8')
Decoder bei der ersten Verwendung während einer SSH Verbindungsversuch. Siehe Analyse Abschnitt.
In der Regel kann man nicht genug betonen, die Sie vermeiden sollten automatisch Laichen neue Themen beim Import ein Modul mit. Wenn Sie können, versuchen im Allgemeinen Magie-Modul-Code zu vermeiden, da es fast immer führt zu unerwünschten Nebenwirkungen.
-
Die einfach - und gesund - fix für Ihr Problem - wie bereits erwähnt - ist der Code in einem
if __name__ == '__main__':
Körper zu setzen, die nur dann ausgeführt werden, wenn Sie dieses spezielle Modul ausführen und werden nicht ausgeführt werden, wenn diese mmodule importiert von anderen Modulen. -
(nicht empfohlen) Eine andere Lösung ist nur ein Dummy str.decode tun ( 'utf-8') in Ihrem Code, bevor Sie
SSHClient.connect()
nennen - vgl. Analyse unter
Also was ist die Ursache dieses Problems?
Analyse (einfaches Passwort Auth)
Hinweis: Wenn Sie in Python Import debuggen Einfädeln und setzt threading._VERBOSE = True
-
paramiko.SSHClient().connect(.., look_for_keys=False, ..)
laicht implizit einen neuen Thread für Ihre Verbindung. Sie können dies auch sehen, wenn Sie auf Debug-Ausgabe fürparamiko.transport
drehen.
[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
-
Dies ist im Grunde als Teil
SSHClient.connect()
getan. Wennclient.py:324::start_client()
aufgerufen wird, wird eine Sperre erstellttransport.py:399::event=threading.Event()
und der Thread gestartettransport.py:400::self.start()
. Beachten Sie, dass diestart()
Methode wird dann dietransport.py:1565::run()
Methode der Klasse auszuführen. -
transport.py:1580::self._log(..)
druckt den unsere Log-Nachricht "Anfangsfaden" und geht danntransport.py:1584::self._check_banner()
. -
check_banner
ist eine Sache. Es ruft die ssh Banner (erste Antwort vom Server)transport.py:1707::self.packetizer.readline(timeout)
(beachten Sie, dass das Timeout ist nur eine Steckdose lesen Timeout), geprüft, ob ein Zeilenvorschub am Ende und sonst mal aus. -
Falls ein Server Banner empfangen wurde, versucht es auf UTF-8 dekodieren die Antwortzeichenfolge
packet.py:287::return u(buf)
und das ist, wo die Blockade geschieht. Dieu(s, encoding='utf-8')
hat eine str.decode ( 'utf-i') und implizit importiertencodings.utf8
inencodings:99
überencodings.search_function
in einer Import Sackgasse enden.
So ein schmutziges fix wäre, nur die einmal utf-8-Decoder zu importieren, um auf diesem specifiy Import nicht zu blockieren aufgrund Imports Nebenwirkungen Modul. (''.decode('utf-8')
)
Fix
schmutzig fix - nicht empfohlen
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()
gute fix
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()
"". Dekodieren ( "utf-8") nicht für mich arbeiten, ich endete dies zu tun.
from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")
Ich habe einen Wrapper für paramiko damit umgesetzt. https://github.com/bucknerns/sshaolin