监控任务中的gevent异常
我正在用gevent构建一个应用程序。现在我的应用变得越来越大,因为有很多任务在不断地创建和销毁。最近我注意到,当其中一个任务崩溃时,如果这个错误是从一个非主的绿色线程(greenlet)中产生的,我的整个应用程序还是会继续运行,这没问题。但问题是,我必须查看控制台才能看到错误信息。所以我的应用的某些部分可能“死掉”了,而我却没有立即意识到,应用还是在继续运行。
在我的应用中到处加try catch的做法似乎不是个好办法。也许可以考虑写一个自定义的启动函数,来处理一些错误报告?
那么,监控gevent的任务/绿色线程的正确方法是什么呢?怎么捕获异常呢?
在我的情况下,我需要监听来自几个不同来源的事件,并且应该对每个来源进行不同的处理。有大约五个任务是非常重要的:网页服务器的绿色线程、WebSocket的绿色线程、数据库的绿色线程、警报的绿色线程,以及zmq的绿色线程。如果其中任何一个“死掉”,我的应用就应该完全崩溃。其他不那么重要的任务如果崩溃就没那么严重了。例如,WebSocket的绿色线程可能因为某个异常而崩溃,而其他部分的应用却像什么都没发生一样继续运行。这种情况是完全没用的,也很危险,应该直接崩溃掉。
4 个回答
正如@Denis和@lvo所说,link_exception
是可以的,但我觉得有更好的方法,不需要改变你现在的代码去生成greenlet。
一般来说,每当在一个greenlet中抛出异常时,_report_error
方法(在gevent.greenlet.Greenlet
中)会被调用。这个方法会做一些事情,比如调用所有链接的函数,最后会用当前堆栈的异常信息调用self.parent.handle_error
。这里的self.parent
是全局的Hub
对象,这意味着每个greenlet中发生的异常都会集中到一个方法来处理。默认情况下,Hub.handle_error
会区分异常类型,忽略某些类型并打印其他类型(这就是我们在控制台上看到的情况)。
通过修改Hub.handle_error
方法,我们可以轻松注册自己的错误处理程序,这样就不会再丢失任何错误信息。我写了一个辅助函数来实现这个功能:
from gevent.hub import Hub
IGNORE_ERROR = Hub.SYSTEM_ERROR + Hub.NOT_ERROR
def register_error_handler(error_handler):
Hub._origin_handle_error = Hub.handle_error
def custom_handle_error(self, context, type, value, tb):
if not issubclass(type, IGNORE_ERROR):
# print 'Got error from greenlet:', context, type, value, tb
error_handler(context, (type, value, tb))
self._origin_handle_error(context, type, value, tb)
Hub.handle_error = custom_handle_error
使用它很简单,只需在事件循环初始化之前调用它:
def gevent_error_handler(context, exc_info):
"""Here goes your custom error handling logics"""
e = exc_info[1]
if isinstance(e, SomeError):
# do some notify things
pass
sentry_client.captureException(exc_info=exc_info)
register_error_handler(gevent_error_handler)
这个解决方案在gevent 1.0.2和1.1b3下进行了测试,我们用它将greenlet的错误信息发送到sentry(一个异常跟踪系统),到目前为止效果很好。
你想要把所有的绿色线程(greenlets)都连接到一个清理函数(janitor function)上。
这个清理函数会接收到任何死掉的绿色线程,这样它就可以检查这个绿色线程的异常信息,看看发生了什么,如果需要的话可以采取一些措施来处理它。
我觉得最简单的方法就是捕捉你认为致命的异常,然后使用 sys.exit()
来退出程序(你需要使用 gevent 1.0,因为在这之前 SystemExit
并不能让程序退出)。
另一种方法是使用 link_exception,这个方法会在绿色线程因为异常而终止时被调用。
spawn(important_greenlet).link_exception(lambda *args: sys.exit("important_greenlet died"))
请注意,你也需要 gevent 1.0 才能让这个方法有效。
如果你在使用 0.13.6 版本,可以像这样来结束程序:
gevent.get_hub().parent.throw(SystemExit())