11

将以下内容放入文件hello.pyeasy_install paramiko如果你还没有的话):

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()

适当填写第一行。

现在输入

python hello.py

你会看到一些 ls 输出。

现在改为输入

python

然后从解释器类型中

import hello

瞧!它挂了!如果您将代码包装在一个函数中fooimport hello; hello.foo()改为执行,它将取消挂起。

为什么 Paramiko 在模块初始化中使用时会挂起? Paramiko 是如何知道它在模块初始化期间首先被使用的?

4

3 回答 3

18

Paramiko 为底层传输使用单独的线程。作为导入的副作用,您永远不应该有一个生成线程的模块。据我了解,只有一个导入锁可用,因此当您的模块中的子线程尝试另一个导入时,它可以无限期地阻塞,因为您的主线程仍然持有锁。(可能还有其他我不知道的陷阱)

一般来说,模块在导入时不应该有任何副作用,否则你会得到不可预知的结果。用这个把戏推迟执行__name__ == '__main__',你会没事的。

[编辑] 我似乎无法创建一个重现此死锁的简单测试用例。我仍然认为这是导入的线程问题,因为身份验证代码正在等待一个永远不会触发的事件。这可能是 paramiko 或 python 中的一个错误,但好消息是,如果你做的事情正确,你永远不会看到它;)

这是一个很好的例子,为什么您总是希望尽量减少副作用,以及为什么函数式编程技术变得越来越流行。

于 2009-01-16T16:01:36.910 回答
5

正如JimB 所指出的,当 python 尝试在 ssh 连接尝试期间首次使用时隐式导入解码器时,这是一个导入问题。str.decode('utf-8')有关详细信息,请参阅分析部分。

一般来说,你不能强调你应该避免让一个模块在导入时自动产生新线程。如果可以的话,尽量避免使用魔法模块代码,因为它几乎总是会导致不必要的副作用。

  1. 如前所述,解决您的问题的简单且明智的方法是将您的代码放在一个if __name__ == '__main__':主体中,该主体仅在您执行此特定模块时才会执行,并且不会在其他模块导入此 mmodule 时执行。

  2. (不推荐)另一个解决方法是在您调用之前在您的代码中执行一个虚拟 str.decode('utf-8') SSHClient.connect()- 请参阅下面的分析。

那么这个问题的根本原因是什么?

分析(简单密码验证)

提示:如果你想在 python 导入和设置中调试线程threading._VERBOSE = True

  1. paramiko.SSHClient().connect(.., look_for_keys=False, ..)隐含地为您的连接生成一个新线程。如果您打开paramiko.transport.

[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L

  1. 这基本上是作为SSHClient.connect(). 当client.py:324::start_client()被调用时,会创建一个锁transport.py:399::event=threading.Event()并启动线程transport.py:400::self.start()。请注意,该start()方法随后将执行该类的transport.py:1565::run()方法。

  2. transport.py:1580::self._log(..)打印我们的日志消息“启动线程”,然后继续transport.py:1584::self._check_banner()

  3. check_banner做一件事。它检索 ssh 横幅(来自服务器的第一个响应)transport.py:1707::self.packetizer.readline(timeout)(请注意,超时只是套接字读取超时),最后检查换行符,否则超时。

  4. 如果收到服务器横幅,它会尝试对响应字符串进行 utf-8 解码packet.py:287::return u(buf),这就是发生死锁的地方。执行u(s, encoding='utf-8')str.decode('utf-i') 并隐式导入via最终导致导入死锁encodings.utf8encodings:99encodings.search_function

因此,一个肮脏的解决方法是只导入一次 utf-8 解码器,以免由于模块导入副作用而阻塞该特定导入。( ''.decode('utf-8'))

使固定

肮脏的修复-不推荐

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()

很好的修复

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()

参考paramiko 问题跟踪器:问题 104

于 2015-09-30T21:02:11.360 回答
0

"".decode("utf-8") 对我不起作用,我最终这样做了。

from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")

我有一个实现了 paramiko 的包装器。 https://github.com/bucknerns/sshaolin

于 2016-04-05T03:36:47.443 回答