Pro edição
Stream — Referência Profunda
Visão geral
Seção intitulada “Visão geral”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.
Disponibilidade e licenciamento
Seção intitulada “Disponibilidade e licenciamento”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.
Arquitetura: a costura congelada do motor
Seção intitulada “Arquitetura: a costura congelada do motor”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 orderpublic function maxBatchSize(): int; // int<1, max> backpressure hintpublic 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.
Motores
Seção intitulada “Motores”NextPDF\Pro\Stream\Engine\InProcessRenderEngine
Seção intitulada “NextPDF\Pro\Stream\Engine\InProcessRenderEngine”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.
NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine
Seção intitulada “NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine”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.
Executores
Seção intitulada “Executores”NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface
Seção intitulada “NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface”public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any orderpublic 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.
timeoutSecondslimita toda a renderização paralela; ao expirar, todo worker ainda em execução é encerrado e umaRenderEngineExceptioné lançada. - Higiene de recursos. Um
finallyfecha 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/failedfalha de forma grave. Uma falha de renderização por manifesto é um resultadoFailedpor 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.
Unidades de renderização e resultados
Seção intitulada “Unidades de renderização e resultados”NextPDF\Pro\Stream\Engine\RenderUnit
Seção intitulada “NextPDF\Pro\Stream\Engine\RenderUnit”final readonly — int<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).
NextPDF\Pro\Stream\Engine\CompletedRenderUnit
Seção intitulada “NextPDF\Pro\Stream\Engine\CompletedRenderUnit”final readonly — int $index (não confiável, validado pelo motor), EngineRenderResult $result.
NextPDF\Pro\Stream\Engine\EngineRenderResult
Seção intitulada “NextPDF\Pro\Stream\Engine\EngineRenderResult”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.
NextPDF\Pro\Stream\Engine\EngineRenderStatus
Seção intitulada “NextPDF\Pro\Stream\Engine\EngineRenderStatus”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.
NextPDF\Pro\Stream\Commit\CommitReceipt
Seção intitulada “NextPDF\Pro\Stream\Commit\CommitReceipt”final readonly — jobId, 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.
Checkpoint
Seção intitulada “Checkpoint”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).
NextPDF\Pro\Stream\Checkpoint\RunCheckpoint
Seção intitulada “NextPDF\Pro\Stream\Checkpoint\RunCheckpoint”final readonly — runId, 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.
Deduplicação por idempotência
Seção intitulada “Deduplicação por idempotência”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).FilesystemIdempotencyStore—DurableCapability; 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çaInvalidArgumentException.
Retentativa e dead-letters
Seção intitulada “Retentativa e dead-letters”NextPDF\Pro\Stream\Retry\RetryPolicy
Seção intitulada “NextPDF\Pro\Stream\Retry\RetryPolicy”final readonly — positive-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).
NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface
Seção intitulada “NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface”add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.
NextPDF\Pro\Stream\Retry\DeadLetterRecord
Seção intitulada “NextPDF\Pro\Stream\Retry\DeadLetterRecord”final readonly — jobId, 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.FilesystemDeadLetterStore—DurableCapability; 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.
Estado com chave
Seção intitulada “Estado com chave”NextPDF\Pro\Stream\State\KeyedStateStoreInterface
Seção intitulada “NextPDF\Pro\Stream\State\KeyedStateStoreInterface”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.
NextPDF\Pro\Stream\State\KeySelector
Seção intitulada “NextPDF\Pro\Stream\State\KeySelector”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.
Marcador de durabilidade e exceções
Seção intitulada “Marcador de durabilidade e exceções”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 specSPEC-COMMIT-422.OutputCommitConflictException(RuntimeException) — bytes divergentes em uma chave ocupada com sobrescrita desabilitada; código de specSPEC-COMMIT-409(exposto viaspecCode()).UnsupportedTargetException(InvalidArgumentException) — esquema de destino que um committer não consegue atender.
Conformidade
Seção intitulada “Conformidade”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.
Casos extremos e armadilhas
Seção intitulada “Casos extremos e armadilhas”renderBatch()nunca aborta em uma falha por manifesto; inspecione cadaEngineRenderResult.ProcessPoolRenderUnitExecutorcorrelaciona 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.
Fronteira de publicação
Seção intitulada “Fronteira de publicação”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.