Python编写的服务器鲁棒的无限循环

15

我编写了一个处理事件的服务器,而在处理事件期间遇到未捕获异常时不应终止服务器。

该服务器是单个非线程化的Python进程。

我希望在出现以下错误类型时终止:

  • KeyboardInterrupt
  • MemoryError
  • ......

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

我不想重新发明异常处理,因为我猜以前可能已经做过了。

如何继续操作?

  1. 建立白名单:允许处理下一个事件的异常列表
  2. 建立黑名单:指示终止服务器的异常列表。

提示:这个问题不是关于在后台运行Unix守护程序的问题。它与双重fork和重定向stdin/stdout无关 :-)

2个回答

4
我会以类似你所思考的方式进行,使用“你不能通过”甘道夫异常处理程序except Exception来捕获所有非系统退出异常,同时创建一个黑名单set,其中包含应该通过并重新引发的异常。
使用 Gandalf 处理程序 可以确保 GeneratorExit, SystemExitKeyboardInterrupt(所有系统退出异常)会传递并在没有更高层调用堆栈中存在其他处理程序时终止程序。在这里,您可以通过 type(e) 检查捕获到的异常 e__class__ 是否实际属于黑名单异常集合,并重新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}持有我们不想处理的非系统退出异常类。
一个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;我使用它来演示实际会“通过”的异常是什么,并导致终止(即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中会失败),就回退到导入builtins,其中包含所有的Exceptions;我们通过名称搜索字典,所以没有任何区别。
try:
    import exceptions
    excDict = vars(exceptions)
except ImportError:
    import builtins 
    excDict = vars(builtins)

我不知道是否有更聪明的做法,另一个解决方案可能是,不使用带有单一excepttry-except,而是使用两个处理程序,一个用于黑名单异常,另一个用于一般情况:

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

2
最高级别的异常是BaseException。在此下有两个组:
  • Exception派生的
  • 其他所有
StopiterationValueErrorTypeError等,都是Exception的例子。 GeneratorExitSystemExitKeyboardInterrupt等不是从Execption继承而来的。
因此,第一步是捕获Exception而不是BaseException,这将允许您轻松终止程序。我建议还要捕获GeneratorExit,原因如下: 1)它实际上应该永远不会被看到,除非它是手动引发的;2)您可以记录它并重新启动循环;3)它旨在信号生成器已退出,并可以被清理,而不是程序应该退出。
接下来的步骤是记录每个异常的足够详细的信息,以便您有可能找出出了什么问题(当您稍后开始调试时)。
最后,您必须自行决定是否要终止任何一个Exception派生的异常: 我建议使用RuntimeErrorMemoryError,但是您可以通过简单地停止和重新启动服务器循环来避免这些异常。
所以,真的取决于你。
如果有其他错误(例如在尝试加载配置文件时出现IOError),足以退出,则负责加载配置文件的代码应该足够聪明,以捕获该IOError并引发SystemExit
关于白名单/黑名单--使用黑名单,因为只应该有少数(如果有的话)基于Exception的异常需要实际上终止服务器。

1
这也可能有助于决定要处理哪个“异常”:内置异常层次结构:https://docs.python.org/3.5/library/exceptions.html#exception-hierarchy - Tim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接