Ошибки и повторное выполнение

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

Для того чтобы это сделать существует несколько способов.

Опции повторного выполнения

Первый способ реализован глобальными настройками компонента:

'components' => [
    'queue' => [
        'class' => \yii\queue\<driver>\Queue::class,
        'ttr' => 5 * 60, // Максимальное время выполнения задания 
        'attempts' => 3, // Максимальное кол-во попыток
    ],
],

Опция ttr устанавливает резервное время для выполнения заданий. Перед выполнением задание поподает в резерв и будет находиться там не дольше чем задано в ttr. Если задание не выполнилось успешно, и требуется повторная попытка, оно вернется назад в очередь. Если выполнилось - будет удалено из резерва. Опция attempts устанавливает максимальное кол-во попыток. Если попытки закончились, и задание не выполнилось удачно, оно так же будет удалено из резерва.

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

Описанные опции действуют глобально на все задания в очереди, а, чтобы для отдельных заданий это поведение переопределить, существует дополнительные возможности.

RetryableJobInterface

Индивидуальный контроль повторного выполнения реализован интерфейсом RetryableJobInterface, код такого job-объекта может выглядеть так:

class SomeJob extends BaseObject implements RetryableJobInterface
{
    public function execute($queue)
    {
        //...
    }

    public function getTtr()
    {
        return 15 * 60;
    }

    public function canRetry($attempt, $error)
    {
        return ($attempt < 5) && ($error instanceof TemporaryException);
    }
}

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

Обработчики событий

Еще один способ задать резервное время и необходимость повторного запуска невыполненной задачи предполагает использовать события Queue::EVENT_BEFORE_PUSH и Queue::EVENT_AFTER_ERROR.

Событие Queue::EVENT_BEFORE_PUSH можно использовать, чтобы задать резервное время:

Yii::$app->queue->on(Queue::EVENT_BEFORE_PUSH, function (PushEvent $event) {
    if ($event->job instanceof SomeJob) {
        $event->ttr = 300;
    }
});

А событие Queue::EVENT_AFTER_ERROR — чтобы определить задание на повторную попытку:

Yii::$app->queue->on(Queue::EVENT_AFTER_ERROR, function (ErrorEvent $event) {
    if ($event->job instanceof SomeJob) {
        $event->retry = ($event->attempt < 5) && ($event->error instanceof TemporaryException);
    }
});

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

Ограничения

Не все драйверы поддерживают повторное выполнение одинаково хорошо. Полнеценную поддержку обеспечивают драйвера: Beanstalk, DB, File и Redis. Синхронный драйвер, как отладочный, не будет повторять невыполненные задания. Gearman не поддерживает повторное выполнение вообще. А RabbitMQ имеет только свою базовую поддержку повторов, при которой номер попытки узнать не получится.