Zum Inhalt springen
getnextpdf.com

Pro Edition

Stream — Ausführliche Referenz

Diese Seite dokumentiert die öffentlichen Verträge, Klassen, Methoden und Fehlermodi des Subsystems NextPDF\Pro\Stream über die Überblicksseite hinaus. Jeder Typ unten ist Teil der dokumentierten öffentlichen Pro-Oberfläche.

Diese Fähigkeit wird in NextPDF Pro (nextpdf/pro) ausgeliefert und aktiviert sich mit einem Lizenz-Envelope der Pro-Stufe. Eine Bereitstellung ohne diese Berechtigung lädt die Klassen der Fähigkeit nicht. Editionen vergleichen und Lizenz erwerben.

Kein per-Feature-Lizenz-Flag gilt; der Code wird mit der Pro-Edition ausgeliefert. Worker-Anzahl, Batch-Größe, Wiederholungsbudget und Store-Backend sind Laufzeitparameter.

NextPDF\Pro\Stream\Engine\RenderEngineInterface ist der Vertrag zwischen der Durchsatz-Engine und dem Document-Job-Stream-Prozessor. Die Engine implementiert ihn (besitzt Nebenläufigkeit, Worker-Pool-Lifecycle, Backpressure, beschränkten Speicher); der Stream-Prozessor konsumiert ihn (besitzt Keyed State, Dedup, Retry, Checkpoint und Exactly-once-Commit). Die Engine gibt Bytes plus sha-256 zurück, niemals einen committeten Ort — genau diese Nebenwirkungsfreiheit erlaubt es dem Prozessor, exactly-once zu stagen, zu committen und zu checkpointen.

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 ist eine list<RenderManifest> der Größe von höchstens maxBatchSize(); $variablesByJobId bildet eine Job-ID auf array<string, scalar>-Template-Variablen ab. Ein Fehler pro Manifest ist ein Failed/Timeout-Ergebnis pro Item und bricht den Batch niemals ab.

Synchrone Single-Process-Basis. Validiert jedes Manifest fail-closed über RenderManifestValidator (16-MiB-Inline-Payload-Obergrenze, Conformance-/Signatur-Allow-Lists, sha-256-Content-Hash-Format, BCP-47-Locale-Syntax), bevor sie über den Core-SingleDocumentRenderer rendert. Ein blockierender Validierungsfehler nimmt einen Kurzschluss zu EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); eine Render-Ausnahme wird zu 'SPEC-RENDER-EXCEPTION'. Konstruktor: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null)maxBatchSize < 1 löst eine InvalidArgumentException aus. isAvailable() ist immer true.

final readonly, __construct(RenderUnitExecutorInterface $executor). Umhüllt jedes Manifest in eine indizierte RenderUnit, lässt sie durch den Executor laufen und sortiert die Abschlüsse nach Index neu, sodass die Ausgabe byte-gleich zu einem sequentiellen Render ist. Ein Abschluss-Index außerhalb von [0, count) löst RenderEngineException::unknownUnit() aus; ein wiederholter Index löst duplicateResult() aus; ein fehlender Index löst missingResult() aus. maxBatchSize() und isAvailable() delegieren an den Executor.

NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface

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

Implementierungen dürfen Abschlüsse in beliebiger Reihenfolge liefern; ConcurrentRenderEngine stellt die Reihenfolge nach Index wieder her.

NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor

Abschnitt betitelt „NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor“

final readonly, __construct(RenderEngineInterface $inner). Rendert jede Unit der Reihe nach über die innere Engine — die deterministische Korrektheitsreferenz, der ein paralleler Executor byte-für-byte entsprechen muss. Keine Zeit, keine Prozesse, keine Threads, keine Zufälligkeit.

NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor

Abschnitt betitelt „NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor“

final readonly. Verteilt einen Batch auf bis zu maxWorkers php-Worker-Subprozesse (je ein Chunk), die parallel rendern; die Ausgabe ist byte-gleich zur Inline-Basis. Konstruktor:

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

Robustheitsvertrag:

  • Deadlock-frei, Windows-sicher. Unit-Payloads und -Ergebnisse reisen über temporäre Dateien, nicht über Pipes; der Parent pollt proc_get_status() und leert eine Pipe erst bis EOF, nachdem ein Worker beendet wurde, sodass ein Worker den Parent nicht festklemmen kann.
  • Beschränkte Wartezeit. timeoutSeconds begrenzt das gesamte parallele Rendern; bei Ablauf wird jeder noch laufende Worker beendet und eine RenderEngineException ausgelöst.
  • Ressourcenhygiene. Ein finally schließt Pipes, unternimmt einen beschränkten Terminate-and-Reap-Versuch bei überlebenden Workern (graceful terminate → force-kill → reap; ein Child, das innerhalb der beschränkten Schonfrist nicht als gestoppt beobachtet wird, wird aufgegeben, statt eine unbestimmte Blockade zu riskieren) und entfernt jede temporäre Datei auf allen Pfaden.
  • Vertrauenswürdige Korrelation. Jeder Worker muss genau seine zugewiesene Index-Menge zurückgeben (kein fehlender, doppelter oder fremder Index); die Bytes jedes gerenderten Ergebnisses werden neu gehasht und gegen den vom Worker gemeldeten sha-256 abgeglichen, und jeder Status außer rendered/failed führt zu einem harten Fehler. Ein Render-Fehler pro Manifest ist ein Failed-Ergebnis pro Unit; nur eine infrastrukturelle Störung (Exit ungleich null, unlesbare/fehlerhafte Ausgabe, Timeout) führt zu einem harten Fehler des Executors.

isAvailable() erfordert, dass sowohl die Autoload-Datei als auch das Worker-Skript existieren. Eine nicht-positive Schranke oder eine negative Timeout-Angabe löst eine InvalidArgumentException aus.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. Die Korrelation erfolgt über index, niemals über die Job-ID (Job-IDs sind innerhalb eines Batches nicht garantiert eindeutig).

final readonlyint $index (nicht vertrauenswürdig, von der Engine validiert), EngineRenderResult $result.

final readonly. Felder: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Factories: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (Code SPEC-ENGINE-TIMEOUT). isRendered() meldet den Status. Ein gerendertes Ergebnis trägt Bytes und einen Digest, niemals einen committeten Ort.

String-backed Enum: Rendered, Failed, Timeout. isRetryable() ist true nur für Timeout, sodass der Aufrufer einen Timeout als transient klassifiziert, ohne den Fehler erneut zu inspizieren.

NextPDF\Pro\Stream\Commit\OutputCommitterInterface

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

Exactly-once-Veröffentlichung: atomar, idempotent (ein byte-gleiches Re-Commit führt keinen Schreibvorgang durch und gibt ein CommitReceipt mit idempotentReuse = true zurück — eine frische Quittung, nicht das Original; ihr committedAt ist die aktuelle Uhrzeit), ohne stilles Überschreiben und integritätsgeprüft (der Committer berechnet den Digest neu). Fehlermodi: CommitIntegrityException (der deklarierte sha-256 stimmt nicht mit den Bytes überein), OutputCommitConflictException (abweichende Bytes an einem belegten Schlüssel mit overwrite = false), UnsupportedTargetException (nicht unterstütztes Zielschema).

NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter

Abschnitt betitelt „NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter“

final readonly, implementiert OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Bedient nur das file-Schema; löst jedes Ziel unter einer konfigurierten Wurzel auf und schreibt über einen atomaren Writer (O_EXCL temp → fsync → Rename auf demselben Volume). Der vollständige kritische Abschnitt (einschließlich der Erstellung des übergeordneten Verzeichnisses) läuft unter einem exklusiven flock auf einer Lock-Datei pro Wurzel, die außerhalb des Ausgabe-Keyspace gehalten wird, und der Commit ist fail-closed, wenn das Lock nicht geöffnet oder erlangt werden kann. Er verweigert symlink-Endkomponenten und jeden Schlüssel, der einen Doppelpunkt enthält (NTFS-Alternate-Data-Stream-Vektor). Cross-Host-nebenläufiges Exactly-once auf denselben Schlüssel erfordert den dauerhaften Enterprise-Committer. Eine Wurzel, die das System-Temp-Verzeichnis ist oder enthält, löst eine InvalidArgumentException aus.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() sind vollständig round-trip-fähig (das Ziel ist strukturiert, keine verlustbehaftete URI); fromArray() ist strikt und löst bei fehlenden oder fehlerhaften Feldern eine InvalidArgumentException aus.

NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface

Abschnitt betitelt „NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface“

load(string $runId): ?RunCheckpoint und save(RunCheckpoint $checkpoint): void (dauerhaft und atomar — ein Leser sieht niemals einen halb geschriebenen Checkpoint).

final readonlyrunId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Factories start(runId, at) und advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() serialisieren ihn; fromArray() erfordert eine nicht-leere Run-ID und ein gültiges updated_at, weist eine inkompatible (nicht-1.x-)schema_version zurück und normalisiert den Keyed State, indem es alle nicht-JSON-serialisierbaren Werte in jeder Tiefe verwirft, sodass der wiederhergestellte Zustand stets re-serialisierbar ist. Bei der Wiederherstellung spult der Prozessor über committedOffset hinaus vor und stellt den Keyed State wieder her; ein Zustand, der nach der letzten Barriere mutiert wurde, wird vorwärts neu berechnet, niemals ein Fehler, weil dauerhaftes Exactly-once aus der Digest-Deduplizierung des Committers stammt.

NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore

Abschnitt betitelt „NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore“

final readonly, implementiert CheckpointStoreInterface, DurableCapability. Eine JSON-Datei pro Lauf, atomar geschrieben. Run-IDs müssen [A-Za-z0-9._-]+ entsprechen und kein .. enthalten; ein nicht existierendes Verzeichnis löst eine InvalidArgumentException aus.

NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface

Abschnitt betitelt „NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface“

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Der schnelle Pfad, der vor dem Rendern eines Replays einen Kurzschluss nimmt; der Digest-Vergleich des Committers bleibt die dauerhafte Garantie, sodass ein verlorener Eintrag schlimmstenfalls ein erneutes Rendern verschwendet, das der Committer dedupliziert.

  • InMemoryIdempotencyStore — Single-Run-/Testumfang (bei Absturz verloren).
  • FilesystemIdempotencyStoreDurableCapability; eine atomare JSON-Datei pro committetem Schlüssel (die serialisierte Quittung), benannt nach einem Hash des Schlüsselwerts. Marks sind idempotent; ein nebenläufiges Re-Mark läuft harmlos auf einer Datei in ein Race. Ein nicht existierendes Verzeichnis löst eine InvalidArgumentException aus.

final readonlypositive-int $maxAttempts, positive-int $baseDelayMs, positive-int $maxDelayMs. __construct(int $maxAttempts = 3, int $baseDelayMs = 100, int $maxDelayMs = 30000) mit den Invarianten maxAttempts >= 1 und 1 <= baseDelayMs <= maxDelayMs <= 7 days (sonst InvalidArgumentException). Factories default() und none() (einzelner Versuch). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> ist ein deterministisches exponentielles Backoff baseDelayMs * 2^(attempt-1), gedeckelt bei maxDelayMs (kein eingebauter Jitter; an der Aufrufstelle anwenden).

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

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, optional ?string $runId, optional int<1, max> $sourceOffset. dedupKey() ist runId:sourceOffset, wenn beide bekannt sind, andernfalls der Idempotenz-Schlüsselwert. fromArray() parst failed_at strikt als ATOM (relative oder nicht-ATOM-Ausdrücke werden zurückgewiesen), sodass Serialisieren/Deserialisieren symmetrisch bleibt.

  • InMemoryDeadLetterStore — Single-Run-/Testumfang.
  • FilesystemDeadLetterStoreDurableCapability; eine atomare JSON-Datei pro Record, benannt nach einem SHA-256-Hash des Dedup-Schlüssels (….dlq.json), sodass das erneute Hinzufügen desselben Items beim Resume idempotent ist. all() liest Records in deterministischer (sortierter) Reihenfolge und legt einen korrupten Record offen, indem es eine Ausnahme auslöst; count() ist eine günstige Dateizählung, keine Gültigkeitsprüfung.

has, get, put, remove, clear sowie snapshot(): array und restore(array $snapshot): void für die Checkpoint-Grenze. Werte müssen JSON-serialisierbar sein. Für die standardmäßige Render-and-Commit-Last wird kein Keyed State verwendet; er existiert für Aggregations-/Windowing-Erweiterungen. InMemoryKeyedStateStore ist die Single-Run-Implementierung; ihn bei der Wiederherstellung zu verlieren ist für die Standardlast ein semantischer No-op, weil Exactly-once aus der Digest-Deduplizierung des Committers stammt.

final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string leitet den Partitionsschlüssel aus den Manifest-Metadaten als rawurlencode(tenant):rawurlencode(document) ab (die Kodierung verhindert, dass ("a:b","c") mit ("a","b:c") kollidiert) und fällt auf die Job-ID zurück, wenn eines der Felder fehlt — sodass jedes Manifest auf einen stabilen, nicht-leeren Schlüssel auflöst.

NextPDF\Pro\Stream\DurableCapability ist ein Marker-Interface für jeden Store/Committer, dessen Zustand einen Prozess-Neustart überlebt. Ein absturzsicherer Lauf erfordert, dass jeder Kollaborateur ihn implementiert, sodass er schnell scheitert, statt ein Exactly-once zu versprechen, das ein in-memory-Store nicht halten kann.

Alle Subsystem-Ausnahmen implementieren NextPDF\Pro\Stream\Exception\StreamException (erweitert Throwable), sodass ein Aufrufer einheitlich catch (StreamException) kann:

  • RenderEngineException (RuntimeException) — der Executor hat den Batch-Vertrag verletzt (unbekannte, doppelte oder fehlende Unit; Worker-Störung; Timeout).
  • CommitIntegrityException (RuntimeException) — der deklarierte sha-256 stimmt nicht mit dem Payload überein; Spec-Code SPEC-COMMIT-422.
  • OutputCommitConflictException (RuntimeException) — abweichende Bytes an einem belegten Schlüssel mit deaktiviertem Overwrite; Spec-Code SPEC-COMMIT-409 (über specCode() exponiert).
  • UnsupportedTargetException (InvalidArgumentException) — Zielschema, das ein Committer nicht bedienen kann.

Die Engine validiert Manifeste gegen das Core-Manifest-Modell und erzeugt deterministische Bytes plus sha-256-Digests; der Committer erzwingt atomare, integritätsgeprüfte Exactly-once-Schreibvorgänge. Das Modul führt keine kryptografischen Operationen über sha-256-Content-Digests hinaus durch und definiert kein FIPS-spezifisches Verhalten.

  • renderBatch() bricht bei einem Fehler pro Manifest niemals ab; inspizieren Sie jedes EngineRenderResult.
  • ProcessPoolRenderUnitExecutor korreliert strikt über den Index und hasht die Worker-Bytes neu; ein fehlerhafter Worker führt zu einem harten Fehler, statt die Ausgabe zu beschädigen.
  • LocalFilesystemCommitter ist Single-Host; Cross-Host-Exactly-once erfordert den dauerhaften Enterprise-Committer.
  • Absturzsichere Läufe müssen durchgängig die DurableCapability-(Dateisystem-)Stores verwenden, nicht die in-memory-Varianten.

Diese Seite dokumentiert ausschließlich extern beobachtbares Verhalten und die unterstützte öffentliche API-Oberfläche. Interne Namespace-Pfade, Hilfsklassen, Mechanismustabellen, Runbook-Dateinamen und Ticket-Präfixe liegen außerhalb des Geltungsbereichs.