【问题标题】:Robust endless loop for server written in Python用 Python 编写的强大的服务器无限循环
【发布时间】:2016-04-29 07:31:09
【问题描述】:

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

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

我想终止这些错误类型:

  • 键盘中断
  • 内存错误
  • ...

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

我不想重新发明这种异常处理,因为我猜它之前已经做过好几次了。

如何进行?

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

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

【问题讨论】:

    标签: python exception-handling server infinite-loop robustness


    【解决方案1】:

    我会以与您想的类似的方式执行此操作,在创建一个应通过并重新引发的异常将set 列入黑名单。

    使用 Gandalf 处理程序 将确保 GeneratorExitSystemExitKeyboardInterrupt(所有系统退出异常)通过并在调用中没有其他处理程序存在时终止程序堆。在这里您可以使用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 987654350@;我们按名称搜索字典,所以没有区别:

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

    我不知道是否有更聪明的方法来实际执行此操作,另一种解决方案可能不是使用带有信号 excepttry-except,而是使用 2 个处理程序,一个用于黑名单异常,另一个用于对于一般情况:

    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 的异常需要实际终止服务器。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-02
      • 2012-03-20
      • 1970-01-01
      • 2014-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-23
      相关资源
      最近更新 更多