Надежный бесконечный цикл для сервера, написанного на Python

Я пишу сервер, который обрабатывает события и необработанные исключения во время обработки события, не должен завершать сервер.

сервер представляет собой один непоточный процесс python.

Я хочу завершить работу над этими типами ошибок:

  • KeyboardInterrupt
  • MemoryError
  • ...

список встроенных исключений давно: https://docs.python.org/2/library/exceptions.html

Я не хочу заново изобретать эту обработку исключений, так как я думаю, что это было сделано несколько раз раньше.

Как поступить?

  1. есть белый список: список исключений, которые в порядке, и обработка следующего события-правильный выбор
  2. есть черный список: список исключений, которые указывают, что завершение сервера является правильным выбор.

подсказка: этот вопрос не о запуске демона unix в фоновом режиме. Речь идет не о двойной вилке и не о перенаправлении stdin /stdout: -)

2 ответов


самым главным исключением является BaseException. Есть две группы под этим:

  • Exception производные
  • все остальное

такие вещи, как Stopiteration, ValueError, TypeError, etc. все примеры Exception.

такие вещи, как GeneratorExit, SystemExit и KeyboardInterrupt не являются потомками Execption.

Итак, первый шаг-поймать Exception, а не BaseException что позволит вам легко завершить программу. Я рекомендую также ловить GeneratorExit как 1) он никогда не должен быть замечен, если он не поднимается вручную; 2) Вы можете войти в него и перезапустить цикл; и 3) он предназначен для сигнала генератора вышел и может быть очищен, не то, что программа должна выйти.

следующий шаг-зарегистрировать каждое исключение с достаточной детализацией, чтобы у вас была возможность выяснить, что пошло не так (когда вы позже приступите к отладке).

наконец, вы должны решить для себя что, если Exception производные исключения, которые вы хотите завершить: я бы предложил RuntimeError и MemoryError, хотя вы можете обойти их, просто остановив и перезапустив цикл сервера.

так что, на самом деле, это зависит от вас.

если есть какая-то другая ошибка (например,IOError при попытке загрузить файл конфигурации), который достаточно серьезен, чтобы выйти, тогда код, ответственный за загрузку файла конфигурации, должен быть достаточно умен, чтобы поймать это IOError и поднять SystemExit вместо.

что касается белого списка / черного списка - используйте черный список, так как должно быть только несколько, если таковые имеются,Exception-основанные исключения, которые вам нужно фактически завершить работу сервера.


я бы сделал это так же, как вы думаете, используя "Вы не должны пройти" Гендальф обработчик исключений except Exception чтобы поймать все несистемные исключения при создании черного списка set исключений, которые должны проходить и заканчиваться, будут повторно подняты.

С помощью обработчик Гендальф убеждаемся GeneratorExit, SystemExit и KeyboardInterrupt (все исключения выхода из системы) передайте и завершите программу, если в стеке вызовов нет других обработчиков. Вот где вы можете проверить с type(e) и __class__ пойманного исключения e фактически принадлежит к набору исключений из черного списка и 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} держа классов несистемные исключения, которые мы хотим не ручки.

A try-except блок теперь может выглядеть так:

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

An пример, который ловит все исключения, используя 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 агностиком, вы можете try и import exceptions и если это не удается (что он делает в Python 3), откат к импорту builtins, который содержит все Exceptions; мы искать словарь, имя значит нет разница:

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

я не знаю, есть ли более умный способ сделать это, другое решение может быть вместо try-except со знаком 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