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

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

Сервер представляет собой один процесс, не связанный с потоком python.

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

  • KeyboardInterrupt
  • MemoryError
  • ...

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

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

Как действовать?

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

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

Ответы

Ответ 1

Самое первое исключение - BaseException. Существует две группы:

  • Exception
  • все остальное

Такие вещи, как Stopiteration, ValueError, TypeError и т.д., являются примерами Exception.

Такие вещи, как GeneratorExit, SystemExit и KeyboardInterrupt, не относятся к Execption.

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

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

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

Итак, действительно, это зависит от вас.

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

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

Ответ 2

Я бы сделал это так же, как вы думаете, используя "вы не пройдете" обработчик исключений Gandalf except Exception, чтобы уловить все исключения, не зависящие от системы, при создании исключенных из черного списка set исключений, которые должны проходить и заканчиваться. /p >

Используя обработчик Gandalf, убедитесь, что GeneratorExit, SystemExit и KeyboardInterrupt (все исключающие систему исключения) проходят и завершают программу, если другие вызовы не присутствуют выше в вызове стек. Здесь вы можете проверить type(e), что __class__ пойманного исключения e действительно принадлежит в наборе исключений с черным списком и re raise it.

В качестве небольшой демонстрации:

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

пример, который ловит все исключения, используя 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