Pular para o conteúdo
getnextpdf.com

Pro edição

Stream — Referência Profunda

Esta página documenta os contratos públicos, classes, métodos e modos de falha do subsistema NextPDF\Pro\Stream além da página de visão geral. Cada tipo abaixo faz parte da superfície pública documentada do Pro.

Este recurso é distribuído no NextPDF Pro (nextpdf/pro) e é ativado com um envelope de licença de nível Pro. Uma implantação sem essa autorização não carrega as classes do recurso. Compare edições e obtenha uma licença.

Não se aplica nenhum sinalizador de licença por recurso; o código é distribuído com a edição Pro. O número de workers, o tamanho do lote, o orçamento de retentativas e o backend dos stores são parâmetros de runtime.

NextPDF\Pro\Stream\Engine\RenderEngineInterface é o contrato entre o motor de throughput e o processador de stream de jobs de documentos. O motor o implementa (sendo dono da concorrência, do ciclo de vida do worker-pool, do backpressure, da memória limitada); o processador de stream o consome (sendo dono do estado com chave, da deduplicação, da retentativa, do checkpoint e do commit exatamente-uma-vez). O motor retorna bytes mais sha-256, nunca um local confirmado — é essa ausência de efeitos colaterais que permite ao processador preparar (stage), confirmar e fazer checkpoint exatamente uma vez.

public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input order
public function maxBatchSize(): int; // int<1, max> backpressure hint
public function isAvailable(): bool;

$manifests é uma list<RenderManifest> de tamanho no máximo maxBatchSize(); $variablesByJobId mapeia o id do job para variáveis de template array<string, scalar>. Uma falha por manifesto é um resultado Failed/Timeout por item e nunca aborta o lote.

Linha de base síncrona e de processo único. Valida cada manifesto de forma fail-closed por meio do RenderManifestValidator (limite de 16 MiB para payload inline, allow-lists de conformidade/assinatura, formato de hash de conteúdo sha-256, sintaxe de locale BCP-47) antes de renderizar por meio do SingleDocumentRenderer do Core. Um erro de validação bloqueante faz um curto-circuito para EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); uma exceção de renderização se torna 'SPEC-RENDER-EXCEPTION'. Construtor: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1 lança InvalidArgumentException. isAvailable() é sempre true.

final readonly, __construct(RenderUnitExecutorInterface $executor). Encapsula cada manifesto em uma RenderUnit indexada, executa-os por meio do executor e reordena as conclusões pelo índice, de modo que a saída seja idêntica byte a byte a uma renderização sequencial. Um índice de conclusão fora de [0, count) lança RenderEngineException::unknownUnit(); um índice repetido lança duplicateResult(); um índice ausente lança missingResult(). maxBatchSize() e isAvailable() delegam ao executor.

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

Seção intitulada “NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface”
public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

As implementações podem produzir as conclusões em qualquer ordem; ConcurrentRenderEngine restaura a ordem pelo índice.

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

Seção intitulada “NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor”

final readonly, __construct(RenderEngineInterface $inner). Renderiza cada unidade em ordem por meio do motor interno — a referência de correção determinística que um executor paralelo deve igualar byte a byte. Sem tempo, processos, threads ou aleatoriedade.

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

Seção intitulada “NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor”

final readonly. Distribui um lote entre até maxWorkers subprocessos worker php (um chunk cada) que renderizam em paralelo; a saída é idêntica byte a byte à linha de base inline. Construtor:

__construct(
int $maxWorkers = 4,
int $maxBatchSize = 64,
?string $phpBinary = null,
?string $workerScript = null,
?string $autoload = null,
?int $timeoutSeconds = 300, // null disables the wall-clock watchdog
)

Contrato de robustez:

  • Livre de deadlock, seguro no Windows. Os payloads e resultados das unidades trafegam por arquivos temporários, não por pipes; o pai sonda proc_get_status() e só drena um pipe até EOF depois que um worker tiver saído, de modo que um worker não possa travar o pai.
  • Espera limitada. timeoutSeconds limita toda a renderização paralela; ao expirar, todo worker ainda em execução é encerrado e uma RenderEngineException é lançada.
  • Higiene de recursos. Um finally fecha os pipes, faz uma tentativa limitada de encerrar-e-coletar (terminate-and-reap) os workers sobreviventes (encerramento gracioso → kill forçado → coleta; um filho que não for observado parando dentro do período de tolerância limitado é abandonado em vez de arriscar um bloqueio indefinido) e desvincula (unlink) cada arquivo temporário em todos os caminhos.
  • Correlação confiável. Cada worker deve retornar exatamente o conjunto de índices que lhe foi atribuído (sem índice ausente, duplicado ou estranho); os bytes de cada resultado renderizado são re-hasheados e comparados com o sha-256 informado pelo worker, e qualquer status diferente de rendered/failed falha de forma grave. Uma falha de renderização por manifesto é um resultado Failed por unidade; apenas uma falha de infraestrutura (saída diferente de zero, saída ilegível/corrompida, timeout) faz o executor falhar de forma grave.

isAvailable() requer que tanto o arquivo de autoload quanto o script do worker existam. Um limite não positivo ou um timeout negativo lança InvalidArgumentException.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. A correlação é pelo index, nunca pelo id do job (não há garantia de que os ids de job sejam únicos dentro de um lote).

final readonlyint $index (não confiável, validado pelo motor), EngineRenderResult $result.

final readonly. Campos: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Fábricas: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (código SPEC-ENGINE-TIMEOUT). isRendered() informa o status. Um resultado renderizado carrega bytes e um digest, nunca um local confirmado.

Enum respaldado por string: Rendered, Failed, Timeout. isRetryable() é true apenas para Timeout, de modo que o chamador classifica um timeout como transitório sem reinspecionar o erro.

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

Seção intitulada “NextPDF\Pro\Stream\Commit\OutputCommitterInterface”
public function commit(
string $jobId,
OutputObjectKey $target,
string $bytes,
string $sha256,
bool $overwrite = false,
): CommitReceipt;

Publicação exatamente-uma-vez: atômica, idempotente (uma reconfirmação idêntica byte a byte não realiza nenhuma escrita e retorna um CommitReceipt com idempotentReuse = true — um recibo novo, não o original; seu committedAt é o relógio atual), sem sobrescrita silenciosa e com verificação de integridade (o committer recalcula o digest). Modos de falha: CommitIntegrityException (o sha-256 declarado não corresponde aos bytes), OutputCommitConflictException (bytes divergentes em uma chave ocupada com overwrite = false), UnsupportedTargetException (esquema de destino não suportado).

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

Seção intitulada “NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter”

final readonly, implementa OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Atende apenas o esquema file; resolve cada destino sob uma única raiz configurada e escreve por meio de um writer atômico (temp O_EXCL → fsync → rename no mesmo volume). A seção crítica completa (incluindo a criação do diretório pai) é executada sob um flock exclusivo em um arquivo de lock por raiz mantido fora do espaço de chaves de saída, e o commit é fail-closed se o lock não puder ser aberto ou adquirido. Ele recusa componentes finais com link simbólico e qualquer chave que contenha dois-pontos (vetor de fluxo de dados alternativo NTFS). O exatamente-uma-vez concorrente entre hosts para a mesma chave requer o committer durável do Enterprise. Uma raiz que seja ou contenha o diretório temporário do sistema lança InvalidArgumentException.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() são totalmente reversíveis (round-trippable) (o destino é estruturado, não uma URI com perdas); fromArray() é estrito e lança InvalidArgumentException em campos ausentes ou malformados.

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

Seção intitulada “NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface”

load(string $runId): ?RunCheckpoint e save(RunCheckpoint $checkpoint): void (durável e atômico — um leitor nunca vê um checkpoint escrito pela metade).

final readonlyrunId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Fábricas start(runId, at) e advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() o serializam; fromArray() requer um run id não vazio e um updated_at válido, rejeita um schema_version incompatível (não-1.x) e normaliza o estado com chave descartando quaisquer valores não serializáveis em JSON em todas as profundidades, de modo que o estado recuperado seja sempre re-serializável. Na recuperação, o processador avança rapidamente além de committedOffset e restaura o estado com chave; o estado mutado após a última barreira é recomputado adiante, nunca um erro, porque o exatamente-uma-vez durável vem da deduplicação por digest do committer.

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

Seção intitulada “NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore”

final readonly, implementa CheckpointStoreInterface, DurableCapability. Um arquivo JSON por execução, escrito de forma atômica. Os run ids devem corresponder a [A-Za-z0-9._-]+ e não conter ..; um diretório inexistente lança InvalidArgumentException.

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

Seção intitulada “NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface”

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. O caminho rápido que faz curto-circuito antes de renderizar um replay; a comparação de digest do committer continua sendo a garantia durável, de modo que um registro perdido, no pior caso, desperdiça uma re-renderização que o committer deduplica.

  • InMemoryIdempotencyStore — escopo de execução única / de teste (perdido em caso de falha).
  • FilesystemIdempotencyStoreDurableCapability; um arquivo JSON atômico por chave confirmada (o recibo serializado), nomeado por um hash do valor da chave. As marcações são idempotentes; uma remarcação concorrente disputa de forma inofensiva sobre um único arquivo. Um diretório inexistente lança InvalidArgumentException.

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) com os invariantes maxAttempts >= 1 e 1 <= baseDelayMs <= maxDelayMs <= 7 days (caso contrário InvalidArgumentException). Fábricas default() e none() (tentativa única). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> é um backoff exponencial determinístico baseDelayMs * 2^(attempt-1) limitado a maxDelayMs (sem jitter integrado; aplique-o no ponto de chamada).

add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, ?string $runId opcional, int<1, max> $sourceOffset opcional. dedupKey() é runId:sourceOffset quando ambos são conhecidos, caso contrário o valor da chave de idempotência. fromArray() analisa failed_at estritamente como ATOM (rejeitando expressões relativas ou não-ATOM), de modo que serializar/desserializar permaneça simétrico.

  • InMemoryDeadLetterStore — escopo de execução única / de teste.
  • FilesystemDeadLetterStoreDurableCapability; um arquivo JSON atômico por registro, nomeado por um hash SHA-256 da chave de deduplicação (….dlq.json), de modo que readicionar o mesmo item na retomada seja idempotente. all() lê os registros em ordem determinística (ordenada) e expõe um registro corrompido lançando uma exceção; count() é uma contagem barata de arquivos, não uma verificação de validade.

has, get, put, remove, clear, mais snapshot(): array e restore(array $snapshot): void para o limite do checkpoint. Os valores devem ser serializáveis em JSON. Para a carga de trabalho padrão de renderizar-e-confirmar, nenhum estado com chave é usado; ele existe para extensões de agregação/janelamento. InMemoryKeyedStateStore é a implementação de execução única; perdê-lo na recuperação é um no-op semântico para a carga de trabalho padrão, porque o exatamente-uma-vez vem da deduplicação por digest do committer.

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string deriva a chave de partição dos metadados do manifesto como rawurlencode(tenant):rawurlencode(document) (a codificação impede que ("a:b","c") colida com ("a","b:c")), recorrendo ao id do job quando qualquer um dos campos estiver ausente — de modo que cada manifesto resolva para uma chave estável e não vazia.

NextPDF\Pro\Stream\DurableCapability é uma interface marcadora para qualquer store/committer cujo estado sobreviva a um reinício de processo. Uma execução segura contra falhas requer que cada colaborador a implemente, de modo que ela falhe rapidamente em vez de prometer um exatamente-uma-vez que um store em memória não consegue manter.

Todas as exceções do subsistema implementam NextPDF\Pro\Stream\Exception\StreamException (estende Throwable), de modo que um chamador possa fazer catch (StreamException) de maneira uniforme:

  • RenderEngineException (RuntimeException) — o executor violou o contrato do lote (unidade desconhecida, duplicada ou ausente; falha de worker; timeout).
  • CommitIntegrityException (RuntimeException) — o sha-256 declarado não corresponde ao payload; código de spec SPEC-COMMIT-422.
  • OutputCommitConflictException (RuntimeException) — bytes divergentes em uma chave ocupada com sobrescrita desabilitada; código de spec SPEC-COMMIT-409 (exposto via specCode()).
  • UnsupportedTargetException (InvalidArgumentException) — esquema de destino que um committer não consegue atender.

O motor valida os manifestos contra o modelo de manifesto do Core e produz bytes determinísticos mais digests sha-256; o committer impõe escritas atômicas, com verificação de integridade e exatamente-uma-vez. O módulo não realiza nenhuma operação criptográfica além dos digests de conteúdo sha-256 e não define nenhum comportamento específico de FIPS.

  • renderBatch() nunca aborta em uma falha por manifesto; inspecione cada EngineRenderResult.
  • ProcessPoolRenderUnitExecutor correlaciona estritamente pelo índice e re-hashea os bytes do worker; um worker com bug falha de forma grave em vez de corromper a saída.
  • LocalFilesystemCommitter é de host único; o exatamente-uma-vez entre hosts precisa do committer durável do Enterprise.
  • As execuções seguras contra falhas devem usar os stores DurableCapability (sistema de arquivos) em toda a cadeia, não as variantes em memória.

Esta página documenta apenas o comportamento observável externamente e a superfície de API pública suportada. Caminhos de namespace internos, classes auxiliares, tabelas de mecanismos, nomes de arquivos de runbook e prefixos de ticket estão fora do escopo.