
# Сигнали активності

Завдання, що виконуються довго, і потокові етапи можуть надсилати сигнали активності, щоб підтвердити, що вони досі працюють. Без них детектор застарілих завдань може повторно отримати завдання, яке все ще обробляється, оскільки його мітка часу `reserved_at` не оновлювалася.

## Як це працює

Коли воркер починає обробляти завдання, він викликає `Heartbeat::init()`, щоб встановити контекст поточного завдання. Виклик `Heartbeat::beat()` у вашому обробнику оновлює стовпець `reserved_at` завдання до `NOW()`. Детектор застарілих завдань використовує `reserved_at`, щоб визначити, чи зависло завдання, тож оновлення цього значення захищає завдання.

Коли завдання завершується, воркер викликає `Heartbeat::clear()`, щоб скинути контекст.

## Надсилання сигналів активності

Викликайте `Heartbeat::beat()` у будь-якому обробнику завдань або потоковому етапі:

```php
use Queuety\Heartbeat;

class LongRunningStep implements \Queuety\Step {

    public function handle( array $state ): array {
        $items = $state['items'];

        foreach ( $items as $i => $item ) {
            $this->process( $item );
            Heartbeat::beat( [ 'processed' => $i + 1, 'total' => count( $items ) ] );
        }

        return [ 'done' => true ];
    }

    public function config(): array {
        return [];
    }

    private function process( $item ): void {
        // Expensive work here.
    }
}
```

## Дані про поступ

Параметр `$progress` — необов’язковий асоціативний масив. Якщо його передано, він кодується у форматі JSON і зберігається у стовпці `heartbeat_data` рядка завдання. Це дає змогу відстежувати поступ поза межами воркера:

```php
Heartbeat::beat( [
    'processed' => 450,
    'total'     => 1000,
    'percent'   => 45,
] );
```

Якщо викликати без аргументів, оновлюється лише `reserved_at`:

```php
Heartbeat::beat();
```

## API сигналу активності

Клас `Queuety\Heartbeat` повністю статичний. Створювати його екземпляр не потрібно.

| Метод | Опис |
|---|---|
| `Heartbeat::init( int $job_id, Connection $conn )` | Задає контекст завдання. Воркер викликає автоматично перед обробкою. |
| `Heartbeat::beat( array $progress = [] )` | Надсилає сигнал активності. Оновлює `reserved_at` і за потреби зберігає дані про поступ. |
| `Heartbeat::clear()` | Очищає контекст завдання. Воркер викликає автоматично після обробки. |
| `Heartbeat::current_job_id()` | Отримує поточний ID завдання (корисно для тестування). |

## Використання в потокових етапах

Воркер уже надсилає сигнал активності після збереження кожного фрагмента, встановлюючи `heartbeat_data` у значення `{"streaming_chunks": n}`, тож потік, який регулярно повертає дані, самостійно підтримує актуальність. Викликайте `Heartbeat::beat()` самостійно, якщо між поверненням даних можливі тривалі паузи, наприклад під час очікування першого токена. Дані, які ви передаєте, буде перезаписано під час наступного сигналу після обробки фрагмента:

```php
use Queuety\Contracts\StreamingStep;
use Queuety\Heartbeat;

class StreamAPIResponse implements StreamingStep {

    public function stream( array $state, array $existing_chunks = [] ): \Generator {
        $response = $this->client->stream( $state['prompt'] );

        foreach ( $response as $i => $chunk ) {
            Heartbeat::beat( [ 'chunks' => $i + 1 ] );
            yield $chunk;
        }
    }

    public function on_complete( array $chunks, array $state ): array {
        return [ 'response' => implode( '', $chunks ) ];
    }

    public function config(): array {
        return [];
    }
}
```

## Коли використовувати сигнали активності

Використовуйте сигнали активності, коли одне завдання або етап триває довше ніж `QUEUETY_STALE_TIMEOUT` секунд (за замовчуванням: 600). Типові випадки:

- Виклики API із потоковими відповідями
- Завантаження великих файлів або надсилання їх на сервер
- Повторна пакетна обробка в межах одного етапу
- Будь-який зовнішній виклик API, відповідь на який може затриматися на кілька хвилин

Якщо ваш етап завершується менш ніж за хвилину, сигнали активності не потрібні. Детектор застарілих завдань спрацьовує лише після завершення налаштованого часу очікування.
