OSError:[Errno 24]在Twisted

时间:2016-03-23 09:51:20

标签: python python-2.7 twisted centos7

我遇到了一个奇怪的问题:我正在运行大量的utils.getProcessOutputAndValue('cmd', [args])命令,结果取决于我是否使用task.react()reactor.run()启动了反应堆

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from progress.bar import IncrementalBar
from twisted.internet import defer
from twisted.internet import task
from twisted.internet import utils
from twisted.python import usage


class Options(usage.Options):
    optFlags = [['reactor', 'r', 'Use reactor.run().'],
                ['task', 't', 'Use task.react().'],
                ['cwr', 'w', 'Use callWhenRunning().']]
    optParameters = [['limit', 'l', 255, 'Number of file descriptors to open.'],
                     ['cmd', 'c', 'echo Testing {i}...', 'Command to run.']]


def run(opt):
    limit = int(opt['limit'])
    cmd, args = opt['cmd'].split(' ', 1)
    bar = IncrementalBar('Running {cmd}'.format(cmd=opt['cmd']), max=limit)
    requests = []
    for i in range(0, limit):
        try:
            _args = args.format(i=i)
            args = _args
        except KeyError:
            pass
        requests.append(utils.getProcessOutputAndValue('echo', [args]))
        bar.next()
    bar.finish()
    return defer.gatherResults(requests)


@defer.inlineCallbacks
def main(reactor, opt):
    d = defer.Deferred()
    limit = int(opt['limit'])
    cmd, args = opt['cmd'].split(' ', 1)
    bar = IncrementalBar('Running {cmd}'.format(cmd=opt['cmd']), max=limit)
    for i in range(0, limit):
        try:
            _args = args.format(i=i)
            args = _args
        except KeyError:
            pass
        yield utils.getProcessOutputAndValue('echo', [args])
        bar.next()
    bar.finish()
    defer.returnValue(d.callback(True))


if __name__ == '__main__':
    opt = Options()
    opt.parseOptions()

    if opt['reactor']:
        from twisted.internet import reactor
        task.deferLater(reactor, 0, run, opt)
        reactor.run()

    elif opt['task']:
        from twisted.internet.task import react
        react(main, [opt])

    elif opt['cwr']:
        from twisted.internet import reactor
        reactor.callWhenRunning(run, opt)
        reactor.run()

当使用limit高于400(在我的情况下)时,我收到以下错误:

Upon execvpe echo ['echo', 'Testing 0...'] in environment id 42131264
:Traceback (most recent call last):
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 428, in _fork
    self._setupChild(**kwargs)
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 803, in _setupChild
    for fd in _listOpenFDs():
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 638, in _listOpenFDs
    return detector._listOpenFDs()
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 553, in _listOpenFDs
    self._listOpenFDs = self._getImplementation()
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 576, in _getImplementation
    after = impl()
  File "/home/vagrant/.env/sm/lib/python2.7/site-packages/Twisted-15.5.0-py2.7-linux-x86_64.egg/twisted/internet/process.py", line 606, in _procFDImplementation
    return [int(fd) for fd in self.listdir(dname)]
OSError: [Errno 24] Too many open files: '/proc/23421/fd'
Unhandled error in Deferred:

如果我使用task.react()

,则不会发生

简历中:

  • python pyerr.py -l100 -r确定
  • python pyerr.py -l100 -t确定
  • python pyerr.py -l100 -w确定
  • python pyerr.py -l400 -r OSERR
  • python pyerr.py -l400 -t确定
  • python pyerr.py -l400 -w OSERR

问题是我有一个使用reactor的大应用程序,因为它的应用程序响应SMTP连接(所以不能使用task.react,我不想停止反应堆)。

我一直以为task.react只是在延迟完成后才停止反应堆,但我想这比这更多......

修改pstreetask.react

reactor.run比较

reactor.run(python pyerr.py -l400 -r)

init-+-VBoxService---7*[{VBoxService}]
     |-acpid
     |-atd
     |-cron
     |-dbus-daemon
     |-dhclient
     |-6*[getty]
     |-master-+-pickup
     |        `-qmgr
     |-mysqld---18*[{mysqld}]
     |-nginx---4*[nginx]
     |-php5-fpm---2*[php5-fpm]
     |-puppet---{puppet}
     |-rpc.idmapd
     |-rpc.statd
     |-rpcbind
     |-rsyslogd---3*[{rsyslogd}]
     |-ruby---{ruby}
     |-sshd-+-3*[sshd---sshd---sftp-server]
     |      |-sshd---sshd---2*[sftp-server]
     |      |-sshd---sshd---bash---pstree
     |      `-sshd---sshd---bash---python-+-323*[echo]
     |                                    `-5*[python]
     |-systemd-logind
     |-systemd-udevd
     |-upstart-file-br
     |-upstart-socket-
     `-upstart-udev-br

task.react(python pyerr.py -l400 -t)

init-+-VBoxService---7*[{VBoxService}]
     |-acpid
     |-atd
     |-cron
     |-dbus-daemon
     |-dhclient
     |-6*[getty]
     |-master-+-pickup
     |        `-qmgr
     |-mysqld---18*[{mysqld}]
     |-nginx---4*[nginx]
     |-php5-fpm---2*[php5-fpm]
     |-puppet---{puppet}
     |-rpc.idmapd
     |-rpc.statd
     |-rpcbind
     |-rsyslogd---3*[{rsyslogd}]
     |-ruby---{ruby}
     |-sshd-+-3*[sshd---sshd---sftp-server]
     |      |-sshd---sshd---2*[sftp-server]
     |      |-sshd---sshd---bash---pstree
     |      `-sshd---sshd---bash---python---echo
     |-systemd-logind
     |-systemd-udevd
     |-upstart-file-br
     |-upstart-socket-
     `-upstart-udev-br

注意这个

之间的区别
 |      `-sshd---sshd---bash---python-+-323*[echo]
 |                                    `-5*[python]

和这个

 |      `-sshd---sshd---bash---python---echo

在一个cas中,似乎进程一旦完成就不会关闭。

我在4台不同的机器上测试了这个问题:

  • Ubuntu 14.04
  • Centos 6
  • Centos 7

问题完全一样。

要试一试,请尝试运行watch -n 0.1 "pstree"以查看流程是如何发展的。

编辑:我知道为什么会发生这种情况,这要归功于Glyph的答案,但是如何使这适应我的真人案例呢?

我使用Twisted开发的应用程序是基于Milter的SMTP过滤器,这里是如何工作的(假设我们要检查电子邮件签名):

  • 连接在端口25上打开
  • milter协议获取所有电子邮件详细信息
  • milter呼叫远程"模块"将使用/usr/bin/openssl mime调用
  • 处理签名检查的服务器
  • 模块将返回一个答案,表明签名是否有效

在这种情况下,我的问题是我得到150个同时连接,将有150个模块调用(TCP协议),并且该模块将在每个连接时调用一次openssl命令。

该模块完全不可知,因此无法知道其他呼叫是否正在运行。我认为你应该把DeferredSemaphore放在哪里?

我的问题是smtp连接也是不可知的,并且不了解其他可能的打开连接。

您认为处理这种并行主义的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

此处的问题与task.reactreactor.run之间的区别无关,而是与runmain的实施之间的微妙但显着的差异功能

不同之处在于run正在并行生成limit个进程 ,同时存放数千个同时打开的文件描述符,轻松超越系统的限制。但是,main正在等待每个进程在启动下一个进程之前完全执行,这意味着它一次不会使用超过4个或5个。

原因是maininlineCallbacks修饰并产生getProcessOutputAndValue Deferred,暂停执行main直到Deferred已经完成了。

在实际应用中,这些方法都不理想。你想要一些并行性,但不是无限制的。 Twisted带有一些实用程序,例如DeferredSemaphore,以促进有限的并行性,而不会将所有内容限制为一次只运行一个任务。 Jean-Paul Calderone写了一篇文章 - 10年前! - 这解释了如何使用它,here

但是,为了证明该问题与task.react无关,这里是您的示例的修改版本,该版本删除了run函数,并使用{{进行了一对一的比较1}}:

main

编辑,回复问题中的修改:

由于您的真实问题与传入连接有关,而不仅仅是#!/usr/bin/env python # -*- coding: utf-8 -*- from progress.bar import IncrementalBar from twisted.internet import defer from twisted.internet import task from twisted.internet import utils from twisted.python import usage class Options(usage.Options): optFlags = [['reactor', 'r', 'Use reactor.run().'], ['task', 't', 'Use task.react().'], ['cwr', 'w', 'Use callWhenRunning().']] optParameters = [['limit', 'l', 255, 'Number of file descriptors to open.'], ['cmd', 'c', 'echo Testing {i}...', 'Command to run.']] @defer.inlineCallbacks def main(reactor, opt): d = defer.Deferred() limit = int(opt['limit']) cmd, args = opt['cmd'].split(' ', 1) bar = IncrementalBar('Running {cmd}'.format(cmd=opt['cmd']), max=limit) for i in range(0, limit): try: _args = args.format(i=i) args = _args except KeyError: pass yield utils.getProcessOutputAndValue('echo', [args]) bar.next() bar.finish() defer.returnValue(d.callback(True)) if __name__ == '__main__': opt = Options() opt.parseOptions() if opt['reactor']: from twisted.internet import reactor task.deferLater(reactor, 0, main, reactor, opt) reactor.run() elif opt['task']: from twisted.internet.task import react react(main, [opt]) elif opt['cwr']: from twisted.internet import reactor reactor.callWhenRunning(main, reactor, opt) reactor.run() 循环,而不是使用for,您可能需要维护一个计数器,并采取从listenTCP返回的对象或从TCP4ServerEndpoint返回的DeferredSemaphore的结果实现IPushProducer并调用Deferred的优点当太多并发连接正在工作时,pauseProducing()完成该工作。