最近项目上在使用gevent模块的时候频频报错:
AssertionError:Can only use Waiter.switch method from the Hub greenlet
项目中用到了gevent模块的猴子补丁:monkey_patchall()
也就是所有的阻塞模块都支持协程了,但是出现这个报错说明有的协程没有走gevent模块,从源码层面分析,
def switch(self, value=None):
"""Switch to the greenlet if one's available. Otherwise store the value."""
greenlet = self.greenlet
if greenlet is None:
self.value = value
self._exception = None
else:
assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"
switch = greenlet.switch
try:
switch(value)
except:
self.hub.handle_error(switch, *sys.exc_info())
协程在切换的时候,getcurrent()当前hub不对应,也就是项目中存在了两个gevent hub或者hub为None。
通常来说项目import gevent模块再打上猴子补丁是不会出现这种情况,经过排查发现,项目使用了一个flask_uwsgi_websocket模块,这个模块主要是实现uwsgi websocket协议通信功能, 里面的网络通信阻塞方法都通过gevent模块实现了协程异步,因此可以认为这个模块也是使用了gevent hub。
经过排查,发现在通过flask_uwsgi_websocket调用阻塞方法recv()和send()进行协程异步时,就会出现刚刚那个报错。既然flask_uwsgi_websocket用的也是gevent模块实现的协程,也就是用的同一个gevent hub,那么报错就不可能出现两个gevent hub,只可能是hub为None,hub为就只有一种情况:
flask_uwsgi_websocket的配置有问题,未正确开启协程。
如何解决?
首先查看看flask_uwsgi_websocket官方文档:
https://github.com/zeekay/flask-uwsgi-websocket
可以看到这么一栏:
要想使用flask_uwsgi_websocket支持websocket协议在uwsgi服务器上通信,又要使用gevent实现协程异步,需要同时开启–wsgi和–gevent两个参数:
–wsgi 声明该模块支持uwsgi通信规范
–gevent 指定项目运行支持的协程数量
因此,解决这个问题的办法就是在配置文件,如ini中添加如:
wsgi=true
gevent=10
或者在启动命令中如文档所说添加启动参数:
--gevent 100 --wsgi
注:开启协程后会导致uwsgi flask应用无法响应http请求,只能响应websocket协议通信,此时需要开启–threads 或者配置threads=4开启线程即可
其他情况
还有一种情况下会触发这个报错,如:flask_uwsgi_websocket框架下,向外部模块如so注册回调,然后在so回调中执行包含greenlet协程的代码块,就会出现这个报错。
原因:flask_uwsgi_websocket框架下开启协程后只能单线程运行,因此greenlet hub,也只能存在一个对象,运行在主线程中。当从外部模块回调时,相当于另起一个子线程处理回调,此时执行协程代码块,会去寻找注册的greenlet hub,由于两者运行不在同一个线程,因此就无法找到协程代码块所注册的greenlet hub,也就出现了Can only use Waiter.switch method from the Hub greenlet
报错
解决方式:对于回调产生的协程找不到greenlet hub问题,可以采用队列Queue的方式异步解耦,即回调处理协程代码块一开始就运行在主线程中,通过队列元素判断是否执行协程代码块。当有回调发生时,回调事件加入队列Queue中,此时回调线程执行完毕。主线程中的回调处理协程接收到队列元素开始执行协程代码块,由于在整个异步回调过程中,协程代码块始终是运行在主线程中,也就不会出现相应的报错