
# Sinais de atividade

Jobs de longa duração e etapas de streaming podem enviar sinais de atividade para comprovar que ainda estão em execução. Sem esses sinais, o detector de jobs obsoletos pode recuperar um job que ainda está sendo processado porque seu registro de data e hora `reserved_at` não foi atualizado.

## Como funciona

Quando um worker começa a processar um job, ele chama `Heartbeat::init()` para definir o contexto do job atual. Dentro do seu handler, chamar `Heartbeat::beat()` atualiza a coluna `reserved_at` do job para `NOW()`. O detector de obsolescência usa `reserved_at` para determinar se um job está travado; atualizá-lo mantém o job seguro.

Quando o job termina, o worker chama `Heartbeat::clear()` para redefinir o contexto.

## Enviar sinais de atividade

Chame `Heartbeat::beat()` dentro de qualquer handler de job ou etapa de streaming:

```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.
    }
}
```

## Dados de progresso

O parâmetro `$progress` é um array associativo opcional. Quando fornecido, ele é codificado em JSON e armazenado na coluna `heartbeat_data` da linha do job. Isso permite acompanhar o progresso de fora do worker:

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

Quando chamado sem argumentos, somente `reserved_at` é atualizado:

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

## API de sinais de atividade

A classe `Queuety\Heartbeat` é totalmente estática. Você não precisa instanciá-la.

| Método | Descrição |
|---|---|
| `Heartbeat::init( int $job_id, Connection $conn )` | Define o contexto do job. Chamado automaticamente pelo worker antes do processamento. |
| `Heartbeat::beat( array $progress = [] )` | Envia um sinal de atividade. Atualiza `reserved_at` e, opcionalmente, armazena dados de progresso. |
| `Heartbeat::clear()` | Limpa o contexto do job. Chamado automaticamente pelo worker após o processamento. |
| `Heartbeat::current_job_id()` | Obtém o ID do job atual (útil para testes). |

## Uso em etapas de streaming

O worker já envia um sinal de atividade após cada trecho que persiste, com `heartbeat_data` definido como `{"streaming_chunks": n}`; assim, um fluxo que gera conteúdo regularmente se mantém atualizado por conta própria. Chame `Heartbeat::beat()` você mesmo quando houver longos intervalos entre os trechos, por exemplo, enquanto aguarda o primeiro token. Os dados que você passar serão sobrescritos pelo próximo sinal por trecho do worker:

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

## Quando usar sinais de atividade

Use sinais de atividade quando um único job ou etapa durar mais de `QUEUETY_STALE_TIMEOUT` segundos (padrão: 600). Casos comuns:

- Chamadas de API com respostas em streaming
- Downloads ou uploads de arquivos grandes
- Repetições de processamento em lote dentro de uma única etapa
- Qualquer chamada externa de API que possa levar minutos para responder

Se sua etapa terminar em menos de um minuto, não será necessário enviar sinais de atividade. O detector de obsolescência só entra em ação após o tempo limite configurado.
