Python 如果在加载模块时使用Paramiko,为什么它会挂起?

Python 如果在加载模块时使用Paramiko,为什么它会挂起?,python,multithreading,ssh,module,paramiko,Python,Multithreading,Ssh,Module,Paramiko,将以下内容放入一个文件hello.py(如果您没有安装paramiko,请使用轻松安装paramiko): 适当地填写第一行 现在输入 python hello.py 您将看到一些ls输出 现在改为打字 python 然后从解释器类型中 import hello 瞧!它挂起来了!如果您将代码包装在函数foo中并执行import hello,它将取消处理;hello.foo() 为什么Paramiko在模块初始化中使用时会挂起Paramiko最初是如何意识到它在模块初始化期间被使用的?Par

将以下内容放入一个文件hello.py(如果您没有安装paramiko,请使用
轻松安装paramiko
):

适当地填写第一行

现在输入

python hello.py
您将看到一些ls输出

现在改为打字

python
然后从解释器类型中

import hello
瞧!它挂起来了!如果您将代码包装在函数
foo
中并执行
import hello,它将取消处理;hello.foo()


为什么Paramiko在模块初始化中使用时会挂起Paramiko最初是如何意识到它在模块初始化期间被使用的?

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

一般来说,模块在导入时不应该有任何副作用,否则您将得到不可预知的结果。只需使用
\uuuu name\uuuu=='\uuuuuu main\uuuu'
技巧推迟执行,就可以了

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

这是一个很好的例子,为什么您总是希望最小化副作用,为什么函数式编程技术变得越来越流行。

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

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

  • 如前所述,解决您的问题的简单而明智的方法是将您的代码放在
    中,如果uuuu name_uuuuu==''uuuuuuuu main_uuuu':
    主体中,该主体仅在您执行此特定模块时才会执行,而在其他模块导入此mmodule时不会执行

  • (不推荐)另一种修复方法是,在调用
    SSHClient.connect()
    之前,只需在代码中执行一个伪str.decode('utf-8')-请参阅下面的分析

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

    分析(简单密码验证)

    提示:如果要在python中调试线程,请导入并设置
    threading.\u VERBOSE=True

  • paramiko.SSHClient()。如果为
    paramiko.transport
    打开调试输出,也可以看到这一点
    [Thread-5][paramiko.transport]调试:启动线程(客户端模式):0x317f1d0L

  • 这基本上是作为
    SSHClient.connect()
    的一部分完成的。调用
    client.py:324::start\u client()
    时,将创建一个锁
    transport.py:399::event=threading.event()
    ,并启动线程
    transport.py:400::self.start()
    。请注意,
    start()
    方法随后将执行类的
    transport.py:1565::run()
    方法

  • transport.py:1580::self.\u log(..)
    打印我们的日志消息“starting thread”,然后进入
    transport.py:1584::self.\u check\u banner()

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

  • 如果收到服务器横幅,它将尝试utf-8解码响应字符串
    packet.py:287::return u(buf)
    ,这就是死锁发生的地方。
    u(s,encoding='utf-8')
    执行str.decode('utf-i')并隐式导入
    编码。utf8
    编码中:99
    通过
    编码。搜索功能
    最终导致导入死锁

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

    修复

    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()
    
    脏补丁-不推荐

    修复得好

    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

    ”。解码(“utf-8”)对我不起作用,我最终做了这个

    from paramiko import py3compat
    # dirty hack to fix threading import lock (issue 104) by preloading module
    py3compat.u("dirty hack")
    
    我为paramiko实现了一个包装器。

    如果在命令行上执行相同的ssh调用,会发生什么?工作正常。当你在一个模块中运行这段代码时,它对你来说正常吗?当你通过命令行运行它时,你是一行一行地运行还是一次全部运行?@jdangel:我想你的意思是当我通过解释器运行它时;在命令行上,它只是“ssh”user@hostls/'。在解释器中,我自己输入击键,所以是一行一行。如果你一次复制整个内容并将其粘贴到解释器中,而不是一行一行地粘贴到解释器中,会发生什么情况。我相信这确实是paramiko中的一个bug(或至少是缺陷)。谢谢你,Jim!(很抱歉,几个月后我才注意到你的答案——StackOverflow还没有一个很好的通知系统!)你能举出一个示例来说明name=='main'的用法吗?@Tectrendz:看看几乎所有的python脚本:+1实际上导入锁比生成它们时更为复杂。如果您遵循最佳实践并在中执行等待(例如
    signal.pause()
    )<
    from paramiko import py3compat
    # dirty hack to fix threading import lock (issue 104) by preloading module
    py3compat.u("dirty hack")