
# 工作流截止时间

可以为工作流设置截止时间。如果正在运行的工作流未能在指定时长内完成，Queuety 会将其标记为失败，并可选择调用处理程序。工作流暂停或等待信号、其他工作流时，不会因等待而过期。截止时间由长期运行的 `wp queuety work` 进程大约每分钟检查一次，而不是由 `--once` worker 或 WP-Cron 备用机制检查。这相当于为不应无限期运行的长时间工作流设置了一个失效保护开关。

## 设置截止时间

在工作流构建器上使用 `must_complete_within()` 设置允许的最长时长。使用 `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` 方法。该方法会收到工作流的公开状态（所有不以 `_` 开头的键）。

```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'] );
        }
    }
}
```

截止时间处理程序是可选的。如果只想让工作流失败而不运行自定义逻辑，请使用 `must_complete_within()`，不带 `on_deadline()`。

## 截止时间检查的工作方式

worker 会在处理循环中每 60 秒检查一次是否有截止时间已过期的工作流。如果找到某个工作流，其 `status` 仍为 `running` 且 `deadline_at` 已早于当前时间，则会在已注册处理程序的情况下调用 `on_deadline`，将工作流标记为失败并设置错误消息 `Deadline exceeded`，同时记录一条 `workflow_deadline_exceeded` 日志事件。截止时间作为 `deadline_at` 时间戳存储在工作流记录中，并根据配置时长在分派时计算得出。

## 超时后会发生什么

工作流超过截止时间后，其状态会变为 `failed`，错误消息会设为 `Deadline exceeded`，如果已注册截止时间处理程序，则会同步运行该处理程序，并记录一条 `workflow_deadline_exceeded` 日志条目。正在运行的作业不会在执行过程中被中断，但工作流不会越过已过期的步骤边界继续前进。如果截止时间处理程序本身抛出异常，该异常会被静默捕获，工作流最终仍会失败。

## WorkflowBuilder 方法

| 方法 | 说明 |
|---|---|
| `must_complete_within( int $seconds = 0, int $minutes = 0, int $hours = 0, int $days = 0 )` | 设置允许的最长时长。所有参数会相加。 |
| `on_deadline( string $handler_class )` | 注册一个在超过截止时间时调用的处理程序。 |

## 示例：截止时间与重试

很少有必要重试因截止时间而失败的工作流。`retry_workflow()` 会将其重置为 `running` 并重新入队失败的步骤，但不会清除或延长截止时间，因此下一次截止时间检查（大约一分钟内）会再次使其失败。请改为分派一个新工作流。

```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 );
}
```

## 截止时间与休眠延迟的区别

截止时间适用于整个工作流的持续时间。休眠延迟（`->delay()`）会在步骤序列中暂停工作流一段固定时长。如果工作流包含 1 小时的休眠延迟和 2 小时的截止时间，那么非休眠步骤的总耗时超过 1 小时就会导致工作流失败。
