用 Python 编写的服务器的强大无限循环

Robust endless loop for server written in Python

我写了一个处理事件的服务器,处理事件期间未捕获的异常不能终止服务器。

服务器是单个非线程 python 进程。

我想终止这些错误类型:

内置异常列表很长:https://docs.python.org/2/library/exceptions.html

我不想重新发明这个异常处理,因为我猜它以前做过几次。

如何进行?

  1. 有一个白名单:一个正常的异常列表,处理下一个事件是正确的选择
  2. 有一个黑名单:一个异常列表,表明终止服务器是正确的选择。

提示:这个问题不是关于 运行 后台的 unix 守护进程。这与双叉无关,也与重定向无关 stdin/stdout :-)

我会以与您所想的类似的方式执行此操作,使用 'you shall not pass' Gandalf exception handler except Exception to catch all non-system-exiting exceptions 创建 black-listed set 应该通过并结束的异常是 re-raised.

使用 Gandalf 处理程序 将确保 GeneratorExit, SystemExit and KeyboardInterrupt (all system-exiting exceptions) pass and terminate the program if no other handlers are present higher in the call stack. Here is where you can check with type(e) that a __class__ of a caught exception e actually belongs in the set of black-listed exceptions and re-raise 它。

作为一个小演示:

import exceptions  # Py2.x only

# dictionary holding {exception_name: exception_class}
excptDict = vars(exceptions)

exceptionNames = ['MemoryError', 'OSError', 'SystemError'] # and others

# set containing black-listed exceptions
blackSet = {excptDict[exception] for exception in exceptionNames}

现在 blackSet = {OSError, SystemError, MemoryError} 持有我们想要 处理的 non-system-exiting 异常的 类。

一个 try-except 块现在看起来像这样:

try:
    # calls that raise exceptions:
except Exception as e:
    if type(e) in blackSet: raise e # re-raise
    # else just handle it

使用 BaseException 捕获所有异常的 示例 可以帮助说明我的意思。 (这样做 仅用于演示目的 ,以便了解这种加注最终将如何终止您的程序)。 请注意建议您使用BaseException;我使用它是为了 演示 什么异常实际上 'pass through' 并导致终止 (即 BaseException 捕获的所有内容):

for i, j in excptDict.iteritems():
    if i.startswith('__'): continue  # __doc__ and other dunders
    try:
        try:
            raise j
        except Exception as ex:
            # print "Handler 'Exception' caught " + str(i)
            if type(ex) in blackSet:
                raise ex           
    except BaseException:
        print "Handler 'BaseException' caught " + str(i)

# prints exceptions that would cause the system to exit     
Handler 'BaseException' caught GeneratorExit
Handler 'BaseException' caught OSError
Handler 'BaseException' caught SystemExit
Handler 'BaseException' caught SystemError
Handler 'BaseException' caught KeyboardInterrupt
Handler 'BaseException' caught MemoryError
Handler 'BaseException' caught BaseException

最后,为了使这个 Python 2/3 不可知,你可以 tryimport exceptions 如果失败(它在 Python 3 ), fall-back 导入包含所有 Exceptionsbuiltins;我们按名称搜索字典,因此没有区别:

try:
    import exceptions
    excDict = vars(exceptions)
except ImportError:
    import builtins 
    excDict = vars(builtins)

我不知道是否有更聪明的方法来真正做到这一点,另一种解决方案可能不是 try-except 和 signle except,有 2 个处理程序,一个用于 black-listed 例外情况和其他一般情况:

try:
    # calls that raise exceptions:
except tuple(blackSet) as be:  # Must go first, of course.
    raise be
except Exception as e:
    # handle the rest

top-most 例外是 BaseException。下面有两组:

  • Exception 派生
  • 其他一切

StopiterationValueErrorTypeError等都是Exception的例子。

GeneratorExitSystemExitKeyboardInterrupt 之类的东西不是 Execption 的后代。

所以第一步是捕获 Exception 而不是 BaseException 这将使您可以轻松地终止程序。我还建议将 GeneratorExit 捕获为 1) 除非手动引发,否则不应实际看到它; 2)您可以记录它并重新启动循环;和 3) 它旨在表示生成器已经退出并且可以被清理,而不是程序应该退出。

下一步是记录每个异常的详细信息,以便您有可能找出问题所在(当您稍后开始调试时)。

最后,您必须自己决定要终止哪些 Exception 派生异常:我建议 RuntimeErrorMemoryError,尽管您可以通过简单地停止并重新启动服务器循环来解决这些问题。

所以,真的,这取决于你。

如果有其他错误(例如尝试加载配置文件时的 IOError)严重到足以退出,那么负责加载配置文件的代码应该足够聪明以捕捉IOError 并改为加注 SystemExit

至于 whitelist/blacklist -- 使用黑名单,因为只有少数(如果有的话)基于 Exception 的异常需要实际终止服务器。