在 pexpect spawn 中使用 poll 而不是 select

use poll instead of select in pexpect spawn

我有以下测试代码,

import pexpect
import time

session = {}
try:
        for i in range(1030):
                print(i)
                child = pexpect.spawn(cmd,encoding='utf-8')
                child.expect("mgmt",200)
                session[i]=child
                print(child)
                with open("command.txt","w") as fobj:
                        child.logfile_read=fobj
                        child.sendline ("server 0")
                        child.expect ("server0", 200)
                with open("command.txt","r") as temp:
                        command_output=temp.read()
                        print(command_output)
        time.sleep(5000)
except Exception as e:
        print("mgmt launch failed")
        print(e)

此代码打开超过 1024 个文件描述符并产生以下回溯,

server0>
1018
<pexpect.pty_spawn.spawn object at 0x7f5f8ddf6f28>
buffer (last 100 chars): '>'
after: 'mgmt'
match: <_sre.SRE_Match object; span=(452, 461), match='mgmt'>
match_index: 0
exitstatus: None
flag_eof: False
pid: 11126
child_fd: 1023
closed: False
timeout: 30
delimiter: <class 'pexpect.exceptions.EOF'>
logfile: None
logfile_read: None
logfile_send: None
maxread: 2000
ignorecase: False
searchwindowsize: None
delaybeforesend: 0.05
delayafterclose: 0.1
delayafterterminate: 0.1
server 0
server0>

1019
mgmt launch failed
filedescriptor out of range in select()
Traceback (most recent call last):
  File "test1.py", line 9, in <module>
    child.expect("mgmt",200)
  File "/usr/lib/python3/dist-packages/pexpect/spawnbase.py", line 321, in expect
    timeout, searchwindowsize, async)
  File "/usr/lib/python3/dist-packages/pexpect/spawnbase.py", line 345, in expect_list
    return exp.expect_loop(timeout)
  File "/usr/lib/python3/dist-packages/pexpect/expect.py", line 99, in expect_loop
    incoming = spawn.read_nonblocking(spawn.maxread, timeout)
  File "/usr/lib/python3/dist-packages/pexpect/pty_spawn.py", line 452, in read_nonblocking
    r, w, e = select_ignore_interrupts([self.child_fd], [], [], timeout)
  File "/usr/lib/python3/dist-packages/pexpect/utils.py", line 138, in select_ignore_interrupts
    return select.select(iwtd, owtd, ewtd, timeout)
ValueError: filedescriptor out of range in select()

我已经读到,为了克服这个问题,应该使用 poll() 而不是 select() 但找不到关于在使用 pexpect.spawn() 时如何使用 poll() 的示例.我怎样才能明确地说 Python 来使用 poll() 而不是 socket()?

pexpect 模块不支持这样做 out-of-the-box。但是,monkey-patch spawn 对象的 __select 方法并不是非常困难,这是系统 select 实际被调用的地方。

Monkey-patching 表示用您自己的版本替换对象在 run-time 的方法。这在 python 中很容易做到,如果 要替换的方法具有干净的界面。在这种情况下,它非常 straight-forward 因为 pexpect 已经将 select 功能隔离到这个方法,它有一个非常合乎逻辑和干净的界面。

实现类似于下面的代码。请注意,此处 my_select 函数的大部分是复制当前 __selectEINTR 的处理。另请注意,更通用的解决方案也可以正确处理 owtdewtd 参数。这里没有必要,因为这些参数总是作为我正在查看的 pexpect 模块中的空列表传递。最后警告:不提供保修:)。 None 已经过测试。

import select
import sys
import errno

def my_select(self, iwtd, owtd, ewtd, timeout=None):

    if timeout is not None:
        end_time = time.time() + timeout

    poll_obj = select.poll()
    for fd in iwtd:
        poll_obj.register(fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP)

    while True:
        poll_obj.poll(timeout)
        try:
            poll_fds = poll_obj.poll(timeout)
            return ([fd for fd, _status in poll_fds], [], [])
        except select.error:
            err = sys.exc_info()[1]
            if err.args[0] == errno.EINTR:
                # if we loop back we have to subtract the
                # amount of time we already waited.
                if timeout is not None:
                    timeout = end_time - time.time()
                    if timeout < 0:
                        return([], [], [])
            else:
                # something else caused the select.error, so
                # this actually is an exception.
                raise

# Your main code...
child = pexpect.spawn(cmd,encoding='utf-8')
# Monkey-patch my_select method into place
child.__select = my_select 
child.expect("mgmt",200)
...

monkey-patching 有缺点。如果模块的系统版本升级和重组,monkey-patch 可能不再有意义。因此,如果您对这种风险感到不安,您可以简单地将模块复制到您自己的源层次结构中(可能重命名它以避免混淆),然后直接对其 __select 方法进行相同的更改。

添加到响应中,因为预期 version 4.5 选项 use_poll 可以在创建 spawn 对象时指定:

https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn.init

引用:

The use_poll attribute enables using select.poll() over select.select() for socket handling. This is handy if your system could have > 1024 fds

你的情况:

child = pexpect.spawn(cmd, encoding='utf-8', use_poll=True)