
# Крайние сроки workflow

Для workflow можно задать крайний срок. Если выполняющийся workflow не завершится за указанное время, Queuety отметит его как завершившийся сбоем и при необходимости вызовет обработчик. Срок не истекает, пока workflow приостановлен или ожидает сигнал либо другие workflow. Длительно работающий процесс `wp queuety work` проверяет крайние сроки примерно раз в минуту, а `--once` workers и резервный механизм WP-Cron этого не делают. Это служит аварийным выключателем для длительных workflow, которым нельзя работать бесконечно.

## Установка крайнего срока

Используйте `must_complete_within()` в конструкторе workflow, чтобы задать максимально допустимую длительность. Используйте `on_deadline()`, чтобы зарегистрировать обработчик, запускаемый при превышении крайнего срока.

```php
use Queuety\Queuety;

Queuety::workflow( 'data_import' )
    ->then( FetchDataHandler::class )
    ->then( TransformDataHandler::class )
    ->then( LoadDataHandler::class )
    ->must_complete_within( hours: 24 )
    ->on_deadline( ImportTimeoutHandler::class )
    ->dispatch( [ 'source' => $url ] );
```

Метод `must_complete_within()` принимает именованные параметры, значения которых суммируются:

```php
->must_complete_within( hours: 2, minutes: 30 )  // 2.5 hours
->must_complete_within( days: 1 )                 // 24 hours
->must_complete_within( seconds: 3600 )           // 1 hour
->must_complete_within( days: 1, hours: 12 )      // 36 hours
```

## Обработчик крайнего срока

Класс обработчика `on_deadline()` должен реализовывать метод `handle( array $state ): void`. Он получает публичное состояние workflow (все ключи, не начинающиеся с `_`).

```php
class ImportTimeoutHandler {

    public function handle( array $state ): void {
        // Alert the team.
        wp_mail(
            'ops@example.com',
            'Import workflow timed out',
            'Workflow for source ' . ( $state['source'] ?? 'unknown' ) . ' exceeded its deadline.'
        );

        // Clean up partial work.
        if ( isset( $state['temp_file'] ) ) {
            unlink( $state['temp_file'] );
        }
    }
}
```

Обработчик крайнего срока необязателен. Если нужно только отметить workflow как завершившийся сбоем без запуска пользовательской логики, используйте `must_complete_within()` без `on_deadline()`.

## Как работает проверка крайнего срока

Worker проверяет истекшие крайние сроки каждые 60 секунд в рамках цикла обработки. Когда он обнаруживает workflow, для которого `status` по-прежнему имеет значение `running`, а `deadline_at` находится в прошлом, он вызывает зарегистрированный обработчик `on_deadline`, отмечает workflow как завершившийся сбоем с сообщением об ошибке `Deadline exceeded` и записывает в журнал событие `workflow_deadline_exceeded`. Крайний срок хранится как метка времени `deadline_at` в строке workflow и рассчитывается во время запуска на основе заданной длительности.

## Что происходит по истечении срока

Когда workflow превышает крайний срок, его статус меняется на `failed`, в качестве сообщения об ошибке указывается `Deadline exceeded`, зарегистрированный обработчик крайнего срока запускается синхронно, а в журнал добавляется запись `workflow_deadline_exceeded`. Выполняющиеся задания не прерываются в процессе работы, но workflow не перейдет за границу просроченного шага. Если обработчик крайнего срока выбрасывает исключение, оно незаметно перехватывается, а workflow все равно завершается сбоем.

## Методы WorkflowBuilder

| Метод | Описание |
|---|---|
| `must_complete_within( int $seconds = 0, int $minutes = 0, int $hours = 0, int $days = 0 )` | Задает максимально допустимую длительность. Значения всех параметров суммируются. |
| `on_deadline( string $handler_class )` | Регистрирует обработчик, который вызывается при превышении крайнего срока. |

## Пример: крайний срок с повторной попыткой

Повторять workflow, завершившийся сбоем из-за крайнего срока, обычно бессмысленно. `retry_workflow()` сбрасывает его в `running` и повторно ставит неудачный шаг в очередь, но не очищает крайний срок и не продлевает его, поэтому при следующей проверке примерно через минуту workflow снова завершится сбоем. Вместо этого запустите новый workflow.

```php
$status = Queuety::workflow_status( $workflow_id );

if ( 'failed' === $status->status->value ) {
    // Check if it was a deadline failure.
    // You may want to dispatch a new workflow instead of retrying.
    Queuety::retry_workflow( $workflow_id );
}
```

## Крайний срок и задержки сна

Крайний срок распространяется на всю длительность workflow. Задержки сна (`->delay()`) приостанавливают workflow на заданное время в рамках последовательности шагов. Workflow с часовой задержкой сна и крайним сроком в 2 часа завершится сбоем, если шаги вне сна в сумме займут больше 1 часа.
