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

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

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

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

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

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

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

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

RetryableJobInterface
---------------------

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

```php
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` можно использовать, чтобы задать резервное время:

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

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

```php
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] имеет только свою базовую поддержку повторов, при которой номер попытки узнать
не получится.    

[Beanstalk]: driver-beanstalk.md
[DB]: driver-db.md
[File]: driver-file.md
[Redis]: driver-redis.md
[Синхронный драйвер]: driver-sync.md
[Gearman]: driver-gearman.md
[RabbitMQ]: driver-amqp.md