Почему пользовательское исключение должно иметь параметр по умолчанию при его вызове в ProcessPoolExecutor? - PullRequest
1 голос
/ 09 июня 2019

У меня есть ProcessPoolExecutor и я хочу вызвать в нем пользовательское исключение.Но он работает только без нарушения пула процессов, когда у него есть параметр по умолчанию, или он используется в super init, или просто обходит эту функцию super init.Я понятия не имею об этом поведении.На Py3.7 и 3.8 это показано: A process in the process pool was terminated abruptly while the future was running or pending. Есть идеи?

from concurrent.futures.process import ProcessPoolExecutor


class PoolBreaker(Exception):
    def __init__(self, num):
        super().__init__()
        self.num = num


class NoPoolBreaker(Exception):
    def __init__(self, num=0):
        super().__init__()
        self.num = num


class NoPoolBreaker2(Exception):
    def __init__(self, num):
        super().__init__(num)
        self.num = num


class NoPoolBreaker3(Exception):
    def __init__(self, num):
        self.num = num


def get_result(job):
    exc = job.exception()
    if type(exc) is PoolBreaker:
        print("PoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker:
        print("NoPoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker2:
        print("NoPoolBreaker2", exc.num)
    elif type(exc) is NoPoolBreaker3:
        print("NoPoolBreaker3", exc.num)
    else:
        print(f"Exception: {str(exc)}")


def work(x: int):
    if x == 0:
        raise PoolBreaker(x)
    elif x == 1:
        raise NoPoolBreaker(x)
    elif x == 2:
        raise NoPoolBreaker2(x)
    elif x == 3:
        raise NoPoolBreaker3(x)


if __name__ == '__main__':
    for num in range(0, 4):
        with ProcessPoolExecutor() as executor:
            job = executor.submit(work, num)
        get_result(job)

1 Ответ

0 голосов
/ 20 июня 2019

Я тоже подошел к проблеме, и я очень смущен этим.Это происходит только с ProcessPoolExecutor, а не с ThreadPoolExecutor.

Проблема заключается в том, что ProcessPoolExecutor пытается создать копию исключения посредством создания второго раза.Для этого ProcessPoolExecutor, кажется, проверяет «верхний» класс - например, исключение, за исключением NoPoolBreaker3, где верхним классом является сам NoPoolBreaker3 - на предмет его атрибутов.Поскольку исключение не всегда содержит все атрибуты всего объекта, оно завершается ошибкой.

Вы увидите, что если вы добавите печать в конструктор:

from concurrent.futures.process import ProcessPoolExecutor

class PoolBreaker(Exception):
    def __init__(self, num):
        super().__init__()
        print(f"PoolBreaker Constructor with num={num}")
        self.num = num


class NoPoolBreaker1(Exception):
    def __init__(self, num=-9):
        super().__init__()
        print(f"NoPoolBreaker1 Constructor with num={num}")
        self.num = num


class NoPoolBreaker2(Exception):
    def __init__(self, num):
        super().__init__(num)
        print(f"NoPoolBreaker2 Constructor with num={num}")
        self.num = num


class NoPoolBreaker3(Exception):
    def __init__(self, num):
        print(f"NoPoolBreaker3 Constructor with num={num}")
        self.num = num


def get_result(job):
    exc = job.exception()
    if type(exc) is PoolBreaker:
        print("PoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker1:
        print("NoPoolBreaker1", exc.num)
    elif type(exc) is NoPoolBreaker2:
        print("NoPoolBreaker2", exc.num)
    elif type(exc) is NoPoolBreaker3:
        print("NoPoolBreaker3", exc.num)
    else:
        print(f"Exception: {str(exc)}")


def work(x: int):
    if x == 0:
        raise PoolBreaker(x)
    elif x == 1:
        raise NoPoolBreaker1(x)
    elif x == 2:
        raise NoPoolBreaker2(x)
    elif x == 3:
        raise NoPoolBreaker3(x)


if __name__ == '__main__':
    for num in range(0, 4):
        with ProcessPoolExecutor() as executor:
            job = executor.submit(work, num)
        get_result(job)


и ее вывод (python 3.7.3):

PoolBreaker Constructor with num=0
Exception: A process in the process pool was terminated abruptly while the future was running or pending.
NoPoolBreaker1 Constructor with num=1
NoPoolBreaker1 Constructor with num=-9
NoPoolBreaker1 1
NoPoolBreaker2 Constructor with num=2
NoPoolBreaker2 Constructor with num=2
NoPoolBreaker2 2
NoPoolBreaker3 Constructor with num=3
NoPoolBreaker3 Constructor with num=3
NoPoolBreaker3 3

Как видите, конструктор исключений вызывается два раза, но давайте рассмотрим случаи:

  • PoolBreaker: Исключение PoolBreaker успешносоздано.После этого библиотека проверяет этот объект Exception и не находит никаких атрибутов.Конструктор вызывается второй раз, но параметры не передаются, поэтому он не работает.Это не сделано в MWE, но если вы напечатаете трассировку стека, вы увидите, что он жалуется на TypeError: __init__() missing 1 required positional argument: 'num'
  • NoPoolBreaker1: он также не находит никаких атрибутов, но когда конструктор вызывается с нулемаргументы, это не ошибка, потому что у него есть значение по умолчанию (вы видите в выводе, что num = -9)
  • NoPoolBreaker2: Поскольку super () вызывается с параметром, исключение имеет атрибут num.Таким образом, второй вызов конструктора получает требуемый атрибут num с правильным значением, поэтому он работает.
  • NoPoolBreaker3: не вызывается super () и проверяется NoPoolBreaker3, и он находит numприписывать.Таким образом, второй вызов конструктора получает требуемый атрибут num с правильным значением, поэтому он работает.

Фактически возвращаемое исключение всегда первое, второе кажется выброшенным.

Для переноса объектов между процессами их нужно протравить и скопировать, что выполняется ProcessPoolExecutor.Это может быть причиной повторного создания исключения.

Конечно, это не решение проблемы, к сожалению, я еще никого не нашел.

Я тем временем думаю, что это может быть ошибкой в ​​библиотеке, потому что:

  • Второе создание объекта не предназначено, или
  • Проверка атрибутов неверна

Просто замечание: у меня сейчас проблема в том, что в определенной программе NoPoolBreaker3 больше не работает и также вызывает исключение «внезапно завершено».Я понятия не имею, почему, но это, вероятно, означает, что поведение зависит от большего количества вещей, чем показано в MWE.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...