Gevent recursionerror maximum recursion depth exceeded while calling a python object

python使用gevent,然后运行时不报错,debug会出错

Connected to pydev debugger (build 183.4588.64) RecursionError: maximum recursion depth exceeded while calling a Python object Exception ignored in: '_pydevd_frame_eval.pydevd_frame_evaluator_win32_37_64.get_bytecode_while_frame_eval' RecursionError: maximum recursion depth exceeded while calling a Python object Fatal Python error: Cannot recover from stack overflow.

打开pycharm设置

Gevent recursionerror maximum recursion depth exceeded while calling a python object

解决,目前还不知道具体原因是啥
这里有相关的讨论:
https://github.com/gevent/gevent/issues/804

gevent和它的monkey补丁还是挺好用的,但是有时候它也有坑。gevent monkey补丁作用是将阻塞改成非阻塞以提升运行速度,一般都是

from gevent import monkey;monkey.patchall()

但当我做多进程加多线程加多协程实验的时候出现了无限递归的问题,仔细查看monkey.patchall()如下

def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False, # Deprecated, to be removed. subprocess=True, sys=False, aggressive=True, Event=True, builtins=True, signal=True, queue=True, **kwargs): """ Do all of the default monkey patching (calls every other applicable function in this module). :return: A true value if patching all modules wasn't cancelled, a false value if it was.

在我的实验场景下把thread=True改成thread=False就能解决问题,即:

from gevent import monkey;monkey.patchall(thread=False)

总结一下:应该就是monkey补丁将thread从阻塞改成非阻塞而导致的问题,把thread方面的补丁取消即可
如果你也遇到了类似的问题,可以考虑修改相关的参数,可能是因为monkey补丁将某些应该阻塞的地方改成了非阻塞
附gevent的一些缺陷:

  • 阻塞(真正的阻塞,在内核级别):在程序中的某个地方停止了所有的东西.这很像C代码中monkey patch没有生效
  • 保持CPU处于繁忙状态:greenlet不是抢占式的,这可能导致其他greenlet不会被调度
  • 在greenlet之间存在死锁的可能

报错信息 

Gevent recursionerror maximum recursion depth exceeded while calling a python object

源码位置

Gevent recursionerror maximum recursion depth exceeded while calling a python object

分析

 很尴尬,完全看不出原因导致这个报错

解决方法

通过删除代码的方式一部一部删除,找到了问题出处

Gevent recursionerror maximum recursion depth exceeded while calling a python object

原因是包的顺序出现了问题,把位置互换一下,发现没有报错了,但是很明确的告诉你这两个包没有交互关系,没有依赖关系,但是就是这么神奇,为什么这么神奇

看一个实例解释

vim test.py :

import gevent.monkey from requests.packages.urllib3.util.ssl_ import create_urllib3_context # 将monkey patch置于create_urllib3_context引入之前可防止此错误出现。 gevent.monkey.patch_all() create_urllib3_context() # output: # Traceback (most recent call last): # File "test.py", line 7, in <module> # create_urllib3_context() # File "/usr/lib/python3.6/site-packages/requests/packages/urllib3/util/ssl_.py", line 265, in create_urllib3_context # context.options |= options # File "/usr/lib/python3.6/ssl.py", line 459, in options # super(SSLContext, SSLContext).options.__set__(self, value) # File "/usr/lib/python3.6/ssl.py", line 459, in options # super(SSLContext, SSLContext).options.__set__(self, value) # File "/usr/lib/python3.6/ssl.py", line 459, in options # super(SSLContext, SSLContext).options.__set__(self, value) # [Previous line repeated 329 more times] # RecursionError: maximum recursion depth exceeded while calling a Python object

debug

create_urllib3_context代码片段:

... # requests/packages/urllib3/util/ssl_.py#L250 context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) # Setting the default here, as we may have no ssl module on import cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs if options is None: options = 0 # SSLv2 is easily broken and is considered harmful and dangerous options |= OP_NO_SSLv2 # SSLv3 has several problems and is now dangerous options |= OP_NO_SSLv3 # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ # (issue #309) options |= OP_NO_COMPRESSION context.options |= options ...

这里的SSLContext的类型为ssl.SSLContext',而不是gevent._ssl3.SSLContext。 即gevent.monkey.patch_all对其没起作用。

查看SSLContext相关代码:

... # requests/packages/urllib3/util/ssl_.py#L86 try: from ssl import SSLContext # Modern SSL? except ImportError: import sys class SSLContext(object): # Platform-specific: Python 2 & 3.1 supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or (3, 2) <= sys.version_info) ...

from ssl import SSLContext是gevent.monkey.patch_all失效的原因所在。 修正为:

import ssl SSLContext = lambda *args, **kw: ssl.SSLContext(*args, **kw)

当然最好的方案还是在引入gevent库后立即执行monkey patch代码。

analysis

gevent.monkey.patch_all的位置放错导致了create_urllib3_context中的context 的类型为ssl.SSLContext而不是gevent._ssl3.SSLContext。

因此,context.options |= options最后会调用:

# ssl.py#L457 @options.setter def options(self, value): super(SSLContext, SSLContext).options.__set__(self, value)

而不是:

# gevent/_ssl3.py#L67 # In 3.6, these became properties. They want to access the # property __set__ method in the superclass, and they do so by using # super(SSLContext, SSLContext). But we rebind SSLContext when we monkey # patch, which causes infinite recursion. # https://github.com/python/cpython/commit/328067c468f82e4ec1b5c510a4e84509e010f296 # pylint:disable=no-member @orig_SSLContext.options.setter def options(self, value): super(orig_SSLContext, orig_SSLContext).options.__set__(self, value)

但是,由于已经调用过了monkey patch代码,故此时的SSLContext实际上是gevent._ssl3.SSLContext, 是ssl.SSLContext的子类。

所以,super(SSLContext, SSLContext).options实际上是super(gevent._ssl3.SSLContext, gevent._ssl3.SSLContext).options, 得到的结果是ssl.SSLContext.options,而不是我们所希望的_ssl._SSLContext.options。

同时,这也造成了无限递归。