As JimB pointed out it is an import issue when python tries to implicitly import the str.decode('utf-8')
decoder on first use during an ssh connection attempt. See Analysis section for details.
In general, one cannot stress enough that you should avoid having a module automatically spawning new threads on import. If you can, try to avoid magic module code in general as it almost always leads to unwanted side-effects.
The easy - and sane - fix for your problem - as already mentioned - is to put your code in a if __name__ == '__main__':
body which will only be executed if you execute this specific module and wont be executed when this mmodule is imported by other modules.
(not recommended) Another fix is to just do a dummy str.decode('utf-8') in your code before you call SSHClient.connect()
- see analysis below.
So whats the root cause of this problem?
Analysis (simple password auth)
Hint: If you want to debug threading in python import and set threading._VERBOSE = True
paramiko.SSHClient().connect(.., look_for_keys=False, ..)
implicitly spawns a new thread for your connection. You can also see this if you turn on debug output for paramiko.transport
.
[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
this is basically done as part of SSHClient.connect()
. When client.py:324::start_client()
is called, a lock is created transport.py:399::event=threading.Event()
and the thread is started transport.py:400::self.start()
. Note that the start()
method will then execute the class's transport.py:1565::run()
method.
transport.py:1580::self._log(..)
prints the our log message "starting thread" and then proceeds to transport.py:1584::self._check_banner()
.
check_banner
does one thing. It retrieves the ssh banner (first response from server) transport.py:1707::self.packetizer.readline(timeout)
(note that the timeout is just a socket read timeout), checks for a linefeed at the end
and otherwise times out.
In case a server banner was received, it attempts to utf-8 decode the response string packet.py:287::return u(buf)
and thats where the deadlock happens. The u(s, encoding='utf-8')
does a str.decode('utf-i') and implicitly imports encodings.utf8
in encodings:99
via encodings.search_function
ending up in an import deadlock.
So a dirty fix would be to just import the utf-8 decoder once in order to not block on that specifiy import due to module import sideeffects. (''.decode('utf-8')
)
Fix
dirty fix - not recommended
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()
good 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()
ref paramiko issue tracker: issue 104