Salta ai contenuti
getnextpdf.com

Pro edizione

Stream — Riferimento approfondito

Questa pagina documenta i contratti pubblici, le classi, i metodi e le modalità di fallimento del sottosistema NextPDF\Pro\Stream oltre a quanto illustrato nella pagina di panoramica. Ogni tipo elencato di seguito fa parte della superficie pubblica documentata di Pro.

Questa capacità è distribuita in NextPDF Pro (nextpdf/pro) e si attiva con un envelope di licenza di livello Pro. Un deployment privo di tale entitlement non carica le classi della capacità. Confronta le edizioni e ottieni una licenza.

Non si applica alcun flag di licenza per singola funzionalità; il codice è distribuito con l’edizione Pro. Il numero di worker, la dimensione del batch, il budget dei retry e il backend di store sono parametri di runtime.

NextPDF\Pro\Stream\Engine\RenderEngineInterface è il contratto tra il motore di throughput e il processore di stream per i job documentali. Il motore lo implementa (possiede la concorrenza, il ciclo di vita del worker pool, il backpressure, la memoria limitata); il processore di stream lo consuma (possiede lo stato con chiave, la deduplica, i retry, il checkpoint e il commit esattamente-una-volta). Il motore restituisce byte più sha-256, mai una posizione committata — è quella libertà da effetti collaterali a permettere al processore di mettere in staging, committare e fare checkpoint esattamente una volta.

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 è una list<RenderManifest> di dimensione al massimo maxBatchSize(); $variablesByJobId mappa l’id del job su variabili di template array<string, scalar>. Un fallimento per singolo manifest è un risultato Failed/Timeout per singolo elemento e non interrompe mai il batch.

Baseline sincrona e a processo singolo. Convalida ogni manifest fail-closed tramite RenderManifestValidator (cap di payload inline di 16 MiB, allow-list di conformità/firma, formato content-hash sha-256, sintassi locale BCP-47) prima di eseguire il rendering tramite il SingleDocumentRenderer del Core. Un errore di convalida bloccante prende la scorciatoia verso EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); un’eccezione di rendering diventa 'SPEC-RENDER-EXCEPTION'. Costruttore: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1 solleva InvalidArgumentException. isAvailable() è sempre true.

final readonly, __construct(RenderUnitExecutorInterface $executor). Avvolge ogni manifest in una RenderUnit indicizzata, li esegue attraverso l’esecutore e riordina i completamenti per indice così che l’output sia identico byte per byte a un rendering sequenziale. Un indice di completamento al di fuori di [0, count) solleva RenderEngineException::unknownUnit(); un indice ripetuto solleva duplicateResult(); un indice mancante solleva missingResult(). maxBatchSize() e isAvailable() delegano all’esecutore.

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

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

Le implementazioni possono restituire i completamenti in qualsiasi ordine; ConcurrentRenderEngine ripristina l’ordine per indice.

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

Sezione intitolata “NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor”

final readonly, __construct(RenderEngineInterface $inner). Esegue il rendering di ogni unità in ordine attraverso il motore interno — il riferimento deterministico di correttezza che un esecutore parallelo deve eguagliare byte per byte. Nessun tempo, processo, thread o casualità.

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

Sezione intitolata “NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor”

final readonly. Distribuisce un batch su un massimo di maxWorkers sottoprocessi worker php (un chunk ciascuno) che eseguono il rendering in parallelo; l’output è identico byte per byte alla baseline inline. Costruttore:

__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
)

Contratto di robustezza:

  • Privo di deadlock, sicuro su Windows. I payload e i risultati delle unità transitano tramite file temporanei, non tramite pipe; il genitore esegue il polling di proc_get_status() e drena una pipe fino a EOF solo dopo che un worker è terminato, così un worker non può incastrare il genitore.
  • Attesa limitata. timeoutSeconds limita l’intero rendering parallelo; alla scadenza ogni worker ancora in esecuzione viene terminato e viene sollevata una RenderEngineException.
  • Igiene delle risorse. Un finally chiude le pipe, effettua un tentativo limitato di terminate-and-reap sui worker sopravvissuti (graceful terminate → force-kill → reap; un figlio di cui non si osserva l’arresto entro il grace limitato viene abbandonato anziché rischiare un blocco indefinito) e fa l’unlink di ogni file temporaneo su tutti i percorsi.
  • Correlazione fidata. Ogni worker deve restituire esattamente l’insieme di indici assegnatigli (nessun indice mancante, duplicato o estraneo); i byte di ogni risultato renderizzato vengono ri-hashati e confrontati con lo sha-256 riportato dal worker, e qualsiasi stato diverso da rendered/failed fallisce in modo netto. Un fallimento di rendering per singolo manifest è un risultato Failed per singola unità; solo un guasto infrastrutturale (uscita non-zero, output illeggibile/corrotto, timeout) fa fallire in modo netto l’esecutore.

isAvailable() richiede che esistano sia il file di autoload sia lo script del worker. Un limite non positivo o un timeout negativo solleva InvalidArgumentException.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. La correlazione avviene per index, mai per id del job (gli id dei job non sono garantiti univoci all’interno di un batch).

final readonlyint $index (non attendibile, convalidato dal motore), EngineRenderResult $result.

final readonly. Campi: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Factory: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (codice SPEC-ENGINE-TIMEOUT). isRendered() riporta lo stato. Un risultato renderizzato trasporta byte e un digest, mai una posizione committata.

Enum backed da stringa: Rendered, Failed, Timeout. isRetryable() è true solo per Timeout, così il chiamante classifica un timeout come transitorio senza riesaminare l’errore.

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

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

Pubblicazione esattamente-una-volta: atomica, idempotente (un re-commit identico byte per byte non esegue alcuna scrittura e restituisce un CommitReceipt con idempotentReuse = true — una ricevuta nuova, non quella originale; il suo committedAt è l’orologio corrente), senza sovrascritture silenziose e con verifica di integrità (il committer ricalcola il digest). Modalità di fallimento: CommitIntegrityException (lo sha-256 dichiarato non corrisponde ai byte), OutputCommitConflictException (byte divergenti su una chiave occupata con overwrite = false), UnsupportedTargetException (schema di destinazione non supportato).

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

Sezione intitolata “NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter”

final readonly, implementa OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Serve solo lo schema file; risolve ogni destinazione sotto un’unica radice configurata e scrive tramite un writer atomico (temp O_EXCL → fsync → rename sullo stesso volume). L’intera sezione critica (inclusa la creazione della directory padre) viene eseguita sotto un flock esclusivo su un file di lock per radice tenuto al di fuori dello spazio delle chiavi di output, e il commit è fail-closed se il lock non può essere aperto o acquisito. Rifiuta i componenti finali con symlink e qualsiasi chiave contenente due punti (vettore NTFS alternate-data-stream). L’esattamente-una-volta concorrente cross-host verso la stessa chiave richiede il committer durevole di Enterprise. Una radice che è o contiene la directory temporanea di sistema solleva InvalidArgumentException.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() sono completamente round-trippable (la destinazione è strutturata, non un URI con perdita); fromArray() è strict e solleva InvalidArgumentException su campi mancanti o malformati.

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

Sezione intitolata “NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface”

load(string $runId): ?RunCheckpoint e save(RunCheckpoint $checkpoint): void (durevole e atomico — un lettore non vede mai un checkpoint scritto a metà).

final readonlyrunId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Factory start(runId, at) e advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() lo serializzano; fromArray() richiede un run id non vuoto e un updated_at valido, rifiuta uno schema_version incompatibile (non-1.x) e normalizza lo stato con chiave scartando a ogni livello di profondità qualsiasi valore non serializzabile in JSON, così lo stato ripristinato è sempre ri-serializzabile. Al ripristino il processore avanza rapidamente oltre committedOffset e ripristina lo stato con chiave; lo stato mutato dopo l’ultima barriera viene ricalcolato in avanti, mai un errore, perché l’esattamente-una-volta durevole proviene dalla deduplica del digest da parte del committer.

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

Sezione intitolata “NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore”

final readonly, implementa CheckpointStoreInterface, DurableCapability. Un file JSON per esecuzione, scritto in modo atomico. I run id devono corrispondere a [A-Za-z0-9._-]+ e non contenere ..; una directory inesistente solleva InvalidArgumentException.

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

Sezione intitolata “NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface”

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Il percorso rapido che prende una scorciatoia prima di eseguire il rendering di un replay; il confronto del digest da parte del committer rimane la garanzia durevole, quindi un record perso nel peggiore dei casi spreca un re-rendering che il committer deduplica.

  • InMemoryIdempotencyStore — ambito di esecuzione singola / test (perso in caso di crash).
  • FilesystemIdempotencyStoreDurableCapability; un file JSON atomico per chiave committata (la ricevuta serializzata), denominato in base a un hash del valore della chiave. I mark sono idempotenti; un re-mark concorrente compete in modo innocuo su un unico file. Una directory inesistente solleva InvalidArgumentException.

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) con invarianti maxAttempts >= 1 e 1 <= baseDelayMs <= maxDelayMs <= 7 days (altrimenti InvalidArgumentException). Factory default() e none() (singolo tentativo). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> è un backoff esponenziale deterministico baseDelayMs * 2^(attempt-1) limitato a maxDelayMs (nessun jitter integrato; applicarlo nel punto di chiamata).

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

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, opzionale ?string $runId, opzionale int<1, max> $sourceOffset. dedupKey() è runId:sourceOffset quando entrambi sono noti, altrimenti il valore della chiave di idempotenza. fromArray() analizza failed_at rigorosamente come ATOM (rifiutando espressioni relative o non-ATOM) così che serializzazione/deserializzazione restino simmetriche.

  • InMemoryDeadLetterStore — ambito di esecuzione singola / test.
  • FilesystemDeadLetterStoreDurableCapability; un file JSON atomico per record, denominato in base a un hash SHA-256 della chiave di deduplica (….dlq.json), così che ri-aggiungere lo stesso elemento alla ripresa sia idempotente. all() legge i record in ordine deterministico (ordinato) e segnala un record corrotto sollevando un’eccezione; count() è un conteggio di file economico, non una verifica di validità.

has, get, put, remove, clear, più snapshot(): array e restore(array $snapshot): void per il confine del checkpoint. I valori devono essere serializzabili in JSON. Per il workload predefinito di render-and-commit non viene usato alcuno stato con chiave; esiste per estensioni di aggregazione/windowing. InMemoryKeyedStateStore è l’implementazione a esecuzione singola; perderlo al ripristino è un no-op semantico per il workload predefinito, perché l’esattamente-una-volta proviene dalla deduplica del digest da parte del committer.

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string deriva la chiave di partizione dai metadati del manifest come rawurlencode(tenant):rawurlencode(document) (la codifica impedisce a ("a:b","c") di collidere con ("a","b:c")), con fallback all’id del job quando uno dei due campi è assente — così ogni manifest si risolve in una chiave stabile e non vuota.

NextPDF\Pro\Stream\DurableCapability è un’interfaccia marcatore per qualsiasi store/committer il cui stato sopravvive al riavvio di un processo. Un’esecuzione a prova di crash richiede che ogni collaboratore la implementi, così fallisce rapidamente anziché promettere un esattamente-una-volta che uno store in memoria non può mantenere.

Tutte le eccezioni del sottosistema implementano NextPDF\Pro\Stream\Exception\StreamException (estende Throwable), così un chiamante può fare catch (StreamException) in modo uniforme:

  • RenderEngineException (RuntimeException) — l’esecutore ha violato il contratto del batch (unità sconosciuta, duplicata o mancante; guasto del worker; timeout).
  • CommitIntegrityException (RuntimeException) — lo sha-256 dichiarato non corrisponde al payload; codice spec SPEC-COMMIT-422.
  • OutputCommitConflictException (RuntimeException) — byte divergenti su una chiave occupata con la sovrascrittura disabilitata; codice spec SPEC-COMMIT-409 (esposto tramite specCode()).
  • UnsupportedTargetException (InvalidArgumentException) — schema di destinazione che un committer non può servire.

Il motore convalida i manifest rispetto al modello di manifest del Core e produce byte deterministici più digest sha-256; il committer impone scritture atomiche, con verifica di integrità ed esattamente-una-volta. Il modulo non esegue alcuna operazione crittografica oltre ai digest di contenuto sha-256 e non definisce alcun comportamento specifico per FIPS.

  • renderBatch() non interrompe mai su un fallimento per singolo manifest; ispezionare ogni EngineRenderResult.
  • ProcessPoolRenderUnitExecutor correla rigorosamente per indice e ri-hasha i byte del worker; un worker con bug fallisce in modo netto anziché alterare l’output.
  • LocalFilesystemCommitter è a host singolo; l’esattamente-una-volta cross-host richiede il committer durevole di Enterprise.
  • Le esecuzioni a prova di crash devono usare ovunque gli store DurableCapability (filesystem), non le varianti in memoria.

Questa pagina documenta solo il comportamento osservabile dall’esterno e la superficie di API pubblica supportata. I percorsi di namespace interni, le classi di supporto, le tabelle dei meccanismi, i nomi dei file di runbook e i prefissi dei ticket sono fuori ambito.