【问题标题】:Why does paramiko sporadically raise an exception?为什么 paramiko 偶尔会引发异常?
【发布时间】:2020-04-04 00:49:33
【问题描述】:

为方便起见,我编写了一个小包装类来登录远程主机,执行命令,结束检索数据:

def MySSHClient:

    def connect(self, remoteHost, remotePort, userName, password):
        self.__s = paramiko.SSHClient()
        self.__s.load_system_host_keys()
        self.__s.connect(remoteHost, remotePort, userName, password)

    def exec_command(self, command):
        bufsize = -1
        chan = self.__s.get_transport().open_session()
        chan.exec_command(command)
        stdin = chan.makefile('wb', bufsize)
        stdout = chan.makefile('r', bufsize)
        stderr = chan.makefile_stderr('r', bufsize)
        stdin.close()
        exitcode = chan.recv_exit_status()
        r = MySSHCommandResult(command, stdin, stdout, stderr, exitcode)
        chan.close()
        return r

    def close(self):
        self.__s.close()

此代码改编自原始 paramiko python 实现。我刚刚添加了最后 5 行。

(仅供参考:MySSHCommandResult 在构建期间从 stdout 和 strerr 读取所有数据并将其存储以供进一步使用。)

MySSHClient 类在一个简单的 python 程序中使用:

....

exitCode = 0
s = None
try:
    ....
    exitCode = 3
    s = MySSHClient()
    s.connect(host, port, login, password)
    exitCode = 4
    result = s.exec_command(myCommand)
    exitCode = 5
    if not result.isSuccess():
        raise Exception("Failed to execute command!")
    result.dump()    # for current debugging purposes
    exitCode = 0
except:
    pass

if s is not None:
    s.close()
sys.exit(exitCode)

(通过这些退出代码,python 程序会告诉调用者一切是否成功。如您所见,使用了各种退出代码以便在失败时进行一些错误诊断。)

到目前为止一切顺利。基本上这行得通。但我不明白的是,有时我的 python 程序会提供这样的额外输出:

Exception ignored in: <bound method BufferedFile.__del__ of <paramiko.ChannelFile from <paramiko.Channel 0 (closed) ->     <paramiko.Transport at 0x74300588 (unconnected)>>>>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 61, in __del__
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 79, in close
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 88, in flush
TypeError: 'NoneType' object is not callable

或者像这样:

Exception ignored in: <object repr() failed>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 61, in __del__
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 79, in close
  File "/usr/local/lib/python3.5/dist-packages/paramiko/file.py", line 88, in flush
TypeError: 'NoneType' object is not callable

一切正常,但有大约 10% 到 20% 的时间我会看到这些错误消息。有谁知道为什么有时程序终止时清理会失败?如何避免这些错误消息?

【问题讨论】:

  • 您使用的是哪个版本的 paramiko,确切地说?
  • 感谢您的提问!我正在这里安装一个全新的 Ubuntu 16.04。奇怪的是安装了 paramiko 1.17.0。尽管如此,升级到最新版本的 paramiko - 2.0.0 版 - 并没有改变任何东西。甚至错误消息的行号也保持不变。我刚刚通过 pip3 升级,用 2.0.0 版本测试,没有用。
  • 这很可能是 paramiko 错误。你应该写一个错误报告。此外,您应该确保chan.makefile() 打开的每个“文件”都是close()d。尽管仅此一项不太可能解决问题,但它可以确保在出现此错误时不会丢失任何数据。
  • 嗯。你真的认为这是一个错误?考虑到可能有数百万人在使用 paramiko,我不这么认为。
  • 查看paramiko/file.py的第88行,唯一被调用的对象是BytesIO。这可以“突然”变成None 的唯一方法是在解释器关闭时进行垃圾收集(除非有人在某处明确设置它None,这是不太可能的)。

标签: python ssh paramiko


【解决方案1】:

由于某种原因,sys.exit时没有自动清理垃圾

要手动强制清除,您可以简单地使用 del 删除分配的对象。

这是我的代码:

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, port, username, password)
stdin_raw, stdout_raw, stderr_raw = client.exec_command(cmd)
exit_code = stdout_raw.channel.recv_exit_status() 

stdout = []
for line in stdout_raw:
    stdout.append(line.strip())

stderr = []
for line in stderr_raw:
    stderr.append(line.strip())


# Clean up elements
client.close()
del client, stdin_raw, stdout_raw, stderr_raw


logger.debug("stdout: %s" % stdout)
logger.debug("stderr: %s" % stderr)
logger.debug("exit_code: %s" % exit_code)

请注意:

del client, stdin_raw, stdout_raw, stderr_raw

这是我的来源: https://github.com/paramiko/paramiko/issues/1078#issuecomment-596771584

【讨论】:

    【解决方案2】:

    我遇到了完全相同的问题(起初没有找到您的问题,打开了我的问题,现在已删除)。如果您没有以sys.exit 退出,您应该不会再看到该错误。在我的情况下,我所做的是将对 paramiko 等的调用包装在一个函数中,这样sys.exit 就不会在同一范围内被调用。在你的情况下,你可以这样做:

    def my_func(host, port, login, password, myCommand)
        exitCode = 0
        s = None
        try:
            ....
            exitCode = 3
            s = MySSHClient()
            s.connect(host, port, login, password)
            exitCode = 4
            result = s.exec_command(myCommand)
            exitCode = 5
            if not result.isSuccess():
                raise Exception("Failed to execute command!")
            exitCode = 0
        except:
            pass
        if s is not None:
            s.close()
        return exitCode
    
    exit_code = my_func(my_host, my_port, my_login, my_password, my_command)
    sys.exit(exit_code)
    

    它应该可以工作!

    【讨论】:

    • 感谢您的实验!但我想知道:这有什么不同?如果调用 exit(),这表明清理 Paramico 有问题吗?您对此有何看法?
    • 我对这个问题的理解有限,是存在垃圾回收问题。 sys.exit 通过引发异常来工作,以便执行 finally 块中的语句。我的猜测是一个内部对象可能有时被垃圾收集器销毁之前它在paramiko的finally的某处被调用,而当从函数调用它时,对象必须继续存在,直到函数返回。
    • 如果你有兴趣,我已经在 paramiko github 上开了一个ticket
    • 哦,是的!谢谢!
    • 没问题。如果您有机会尝试,请告诉我我的解决方案是否也为您解决了问题。
    【解决方案3】:

    我和你有同样的问题。当我使用exec_command(command) 时,我发现我的命令字符串末尾有一个'\n'。我删除了'\n',它可以工作。 exec_command'\n'隔开,可以执行一系列命令。所以我认为最后一个 '\n' 启动了一个新对象,但它内部没有,所以导致 None 类型错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-29
      • 1970-01-01
      相关资源
      最近更新 更多