Pro редакция
Stream — глубокий справочник
На этой странице документируются публичные контракты, классы, методы и режимы сбоев подсистемы NextPDF\Pro\Stream за рамками обзорной страницы. Каждый тип ниже входит в документированную публичную поверхность Pro.
Доступность и лицензирование
Заголовок раздела «Доступность и лицензирование»Эта возможность поставляется в NextPDF Pro (nextpdf/pro) и активируется с лицензионным конвертом уровня Pro. Развёртывание без этого права не загружает классы возможности. Сравнить редакции и получить лицензию.
Пофункционального лицензионного флага нет; код поставляется вместе с редакцией Pro. Число воркеров, размер пакета, бюджет повторов и бэкенд хранилища — это параметры времени выполнения.
Архитектура: замороженный шов движка
Заголовок раздела «Архитектура: замороженный шов движка»NextPDF\Pro\Stream\Engine\RenderEngineInterface — это контракт между движком пропускной способности и процессором потока заданий-документов. Движок реализует его (владея конкурентностью, жизненным циклом пула воркеров, обратным давлением, ограниченной памятью); процессор потока его потребляет (владея состоянием с ключами, дедупликацией, повторами, контрольными точками и фиксацией ровно один раз). Движок возвращает байты плюс sha-256, но никогда зафиксированное местоположение — именно эта свобода от побочных эффектов позволяет процессору подготавливать, фиксировать и ставить контрольную точку ровно один раз.
public function renderBatch(array $manifests, array $variablesByJobId = []): array; // list<EngineRenderResult>, input orderpublic function maxBatchSize(): int; // int<1, max> backpressure hintpublic function isAvailable(): bool;$manifests — это list<RenderManifest> размером не более maxBatchSize(); $variablesByJobId отображает идентификатор задания на переменные шаблона array<string, scalar>. Сбой отдельного манифеста — это поэлементный результат Failed/Timeout, который никогда не прерывает пакет.
NextPDF\Pro\Stream\Engine\InProcessRenderEngine
Заголовок раздела «NextPDF\Pro\Stream\Engine\InProcessRenderEngine»Синхронный, однопроцессный эталон. Проверяет каждый манифест с отказом в закрытое состояние через RenderManifestValidator (предел встроенной полезной нагрузки 16 MiB, списки разрешений для соответствия/подписи, формат хэша содержимого sha-256, синтаксис локали BCP-47), прежде чем отрисовать через SingleDocumentRenderer из Core. Блокирующая ошибка валидации сокращает путь к EngineRenderResult::failed(jobId, 'SPEC-MANIFEST-INVALID', ...); исключение отрисовки становится 'SPEC-RENDER-EXCEPTION'. Конструктор: __construct(SingleDocumentRenderer $renderer, int $maxBatchSize = 64, ?RenderManifestValidator $validator = null) — maxBatchSize < 1 бросает InvalidArgumentException. isAvailable() всегда true.
NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine
Заголовок раздела «NextPDF\Pro\Stream\Engine\ConcurrentRenderEngine»final readonly, __construct(RenderUnitExecutorInterface $executor). Оборачивает каждый манифест в индексированную RenderUnit, прогоняет их через исполнителя и пересортировывает завершения по индексу, так что вывод побайтно идентичен последовательной отрисовке. Индекс завершения вне [0, count) бросает RenderEngineException::unknownUnit(); повторный индекс бросает duplicateResult(); пропущенный индекс бросает missingResult(). maxBatchSize() и isAvailable() делегируются исполнителю.
Исполнители
Заголовок раздела «Исполнители»NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface
Заголовок раздела «NextPDF\Pro\Stream\Engine\RenderUnitExecutorInterface»public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any orderpublic function maxBatchSize(): int;public function isAvailable(): bool;Реализации могут выдавать завершения в любом порядке; ConcurrentRenderEngine восстанавливает порядок по индексу.
NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor
Заголовок раздела «NextPDF\Pro\Stream\Engine\InlineRenderUnitExecutor»final readonly, __construct(RenderEngineInterface $inner). Отрисовывает каждую единицу по порядку через внутренний движок — детерминированный эталон корректности, которому параллельный исполнитель должен соответствовать побайтно. Никакого времени, процессов, потоков или случайности.
NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor
Заголовок раздела «NextPDF\Pro\Stream\Engine\ProcessPoolRenderUnitExecutor»final readonly. Распределяет пакет между не более чем maxWorkers подпроцессами-воркерами php (по одному фрагменту каждому), которые отрисовывают параллельно; вывод побайтно идентичен встроенному эталону. Конструктор:
__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)Контракт устойчивости:
- Без взаимоблокировок, безопасно под Windows. Полезные нагрузки единиц и результаты ходят через временные файлы, а не каналы; родитель опрашивает
proc_get_status()и опустошает канал до EOF лишь после того, как воркер завершился, поэтому воркер не может заклинить родителя. - Ограниченное ожидание.
timeoutSecondsограничивает всю параллельную отрисовку; по истечении каждый ещё работающий воркер завершается и бросаетсяRenderEngineException. - Гигиена ресурсов. Блок
finallyзакрывает каналы, делает ограниченную попытку завершить-и-пожать выживших воркеров (мягкое завершение → принудительное убийство → пожинание; дочерний процесс, остановку которого не удалось наблюдать в пределах ограниченной отсрочки, бросается, а не рискует бесконечной блокировкой) и удаляет каждый временный файл на всех путях. - Доверенная корреляция. Каждый воркер должен вернуть ровно назначенный ему набор индексов (без пропущенных, дублирующихся или чужих индексов); байты каждого отрисованного результата перехэшируются и сверяются с sha-256, сообщённым воркером, а любой статус, отличный от
rendered/failed, жёстко проваливается. Сбой отрисовки отдельного манифеста — это поэлементный результатFailed; только инфраструктурная неисправность (ненулевой код выхода, нечитаемый/искажённый вывод, тайм-аут) жёстко проваливает исполнителя.
isAvailable() требует существования и файла автозагрузки, и скрипта воркера. Неположительная граница или отрицательный тайм-аут бросают InvalidArgumentException.
Единицы отрисовки и результаты
Заголовок раздела «Единицы отрисовки и результаты»NextPDF\Pro\Stream\Engine\RenderUnit
Заголовок раздела «NextPDF\Pro\Stream\Engine\RenderUnit»final readonly — int<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. Корреляция — по index, а не по идентификатору задания (идентификаторы заданий не гарантированно уникальны внутри пакета).
NextPDF\Pro\Stream\Engine\CompletedRenderUnit
Заголовок раздела «NextPDF\Pro\Stream\Engine\CompletedRenderUnit»final readonly — int $index (недоверенный, проверяется движком), EngineRenderResult $result.
NextPDF\Pro\Stream\Engine\EngineRenderResult
Заголовок раздела «NextPDF\Pro\Stream\Engine\EngineRenderResult»final readonly. Поля: jobId, EngineRenderStatus $status, ?string $bytes, ?string $sha256, int $pageCount, ?string $errorCode, ?string $errorMessage, array<non-empty-string, float> $timings. Фабрики: rendered(jobId, bytes, sha256, pageCount, timings = []), failed(jobId, errorCode, errorMessage), timedOut(jobId, errorMessage) (код SPEC-ENGINE-TIMEOUT). isRendered() сообщает статус. Отрисованный результат несёт байты и дайджест, но никогда зафиксированное местоположение.
NextPDF\Pro\Stream\Engine\EngineRenderStatus
Заголовок раздела «NextPDF\Pro\Stream\Engine\EngineRenderStatus»Перечисление со строковым бэкендом: Rendered, Failed, Timeout. isRetryable() равно true только для Timeout, поэтому вызывающая сторона классифицирует тайм-аут как временный, не переисследуя ошибку.
Фиксация
Заголовок раздела «Фиксация»NextPDF\Pro\Stream\Commit\OutputCommitterInterface
Заголовок раздела «NextPDF\Pro\Stream\Commit\OutputCommitterInterface»public function commit( string $jobId, OutputObjectKey $target, string $bytes, string $sha256, bool $overwrite = false,): CommitReceipt;Публикация ровно один раз: атомарная, идемпотентная (побайтно идентичная повторная фиксация не выполняет записи и возвращает CommitReceipt с idempotentReuse = true — свежую квитанцию, а не исходную; её committedAt — текущие часы), без тихой перезаписи и с проверкой целостности (фиксатор пересчитывает дайджест). Режимы сбоев: CommitIntegrityException (объявленный sha-256 не совпадает с байтами), OutputCommitConflictException (расходящиеся байты на занятом ключе при overwrite = false), UnsupportedTargetException (неподдерживаемая схема цели).
NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter
Заголовок раздела «NextPDF\Pro\Stream\Commit\LocalFilesystemCommitter»final readonly, реализует OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Обслуживает только схему file; разрешает каждую цель в пределах одного настроенного корня и пишет через атомарный writer (временный файл O_EXCL → fsync → переименование на том же томе). Вся критическая секция (включая создание родительского каталога) выполняется под эксклюзивной блокировкой flock на файле блокировки на каждый корень, который держится вне пространства ключей вывода, и фиксация с отказом в закрытое состояние, если блокировку не удаётся открыть или захватить. Он отказывается от финальных компонентов по символическим ссылкам и от любого ключа, содержащего двоеточие (вектор альтернативного потока данных NTFS). Межхостовая конкурентная фиксация ровно один раз на тот же ключ требует долговечного фиксатора Enterprise. Корень, который является системным временным каталогом или содержит его, бросает InvalidArgumentException.
NextPDF\Pro\Stream\Commit\CommitReceipt
Заголовок раздела «NextPDF\Pro\Stream\Commit\CommitReceipt»final readonly — jobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() полностью обратимы по кругу (цель структурирована, а не лишённый данных URI); fromArray() строг и бросает InvalidArgumentException при пропущенных или некорректных полях.
Контрольная точка
Заголовок раздела «Контрольная точка»NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface
Заголовок раздела «NextPDF\Pro\Stream\Checkpoint\CheckpointStoreInterface»load(string $runId): ?RunCheckpoint и save(RunCheckpoint $checkpoint): void (долговечно и атомарно — читатель никогда не видит наполовину записанную контрольную точку).
NextPDF\Pro\Stream\Checkpoint\RunCheckpoint
Заголовок раздела «NextPDF\Pro\Stream\Checkpoint\RunCheckpoint»final readonly — runId, int<0, max> $committedOffset, array $keyedState, DateTimeImmutable $updatedAt; SCHEMA_VERSION = '1.0'. Фабрики start(runId, at) и advancedTo(committedOffset, keyedState, at). toArray()/toJson()/fromArray()/fromJson() сериализуют её; fromArray() требует непустого идентификатора прогона и валидного updated_at, отклоняет несовместимую (не-1.x) schema_version и нормализует состояние с ключами, отбрасывая любые несериализуемые в JSON значения на любой глубине, так что восстановленное состояние всегда переподлежит сериализации. При восстановлении процессор перематывает вперёд за committedOffset и восстанавливает состояние с ключами; состояние, изменённое после последнего барьера, пересчитывается вперёд, а не является ошибкой, потому что долговечное «ровно один раз» исходит из дедупликации по дайджесту в фиксаторе.
NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore
Заголовок раздела «NextPDF\Pro\Stream\Checkpoint\FilesystemCheckpointStore»final readonly, реализует CheckpointStoreInterface, DurableCapability. Один JSON-файл на прогон, записываемый атомарно. Идентификаторы прогона должны соответствовать [A-Za-z0-9._-]+ и не содержать ..; несуществующий каталог бросает InvalidArgumentException.
Дедупликация по идемпотентности
Заголовок раздела «Дедупликация по идемпотентности»NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface
Заголовок раздела «NextPDF\Pro\Stream\Dedup\IdempotencyStoreInterface»isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Быстрый путь, сокращающий путь до отрисовки повтора; сравнение дайджестов в фиксаторе остаётся долговечной гарантией, поэтому потерянная запись в худшем случае напрасно тратит повторную отрисовку, которую фиксатор дедуплицирует.
InMemoryIdempotencyStore— область одного прогона / тестов (теряется при сбое).FilesystemIdempotencyStore—DurableCapability; один атомарный JSON-файл на зафиксированный ключ (сериализованная квитанция), именуемый по хэшу значения ключа. Отметки идемпотентны; конкурентная повторная отметка безвредно состязается на одном файле. Несуществующий каталог бросаетInvalidArgumentException.
Повторы и недоставленные сообщения
Заголовок раздела «Повторы и недоставленные сообщения»NextPDF\Pro\Stream\Retry\RetryPolicy
Заголовок раздела «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) с инвариантами maxAttempts >= 1 и 1 <= baseDelayMs <= maxDelayMs <= 7 days (иначе InvalidArgumentException). Фабрики default() и none() (одна попытка). shouldRetry(int $attempt): bool. delayMsForAttempt(int $attempt): int<0, max> — детерминированная экспоненциальная задержка baseDelayMs * 2^(attempt-1), ограниченная maxDelayMs (без встроенного джиттера; применяйте его на стороне вызова).
NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface
Заголовок раздела «NextPDF\Pro\Stream\Retry\DeadLetterStoreInterface»add(DeadLetterRecord $record): void, all(): list<DeadLetterRecord>, count(): int<0, max>.
NextPDF\Pro\Stream\Retry\DeadLetterRecord
Заголовок раздела «NextPDF\Pro\Stream\Retry\DeadLetterRecord»final readonly — jobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, необязательный ?string $runId, необязательный int<1, max> $sourceOffset. dedupKey() равен runId:sourceOffset, когда оба известны, иначе значение ключа идемпотентности. fromArray() разбирает failed_at строго как ATOM (отклоняя относительные или не-ATOM выражения), так что сериализация/десериализация остаётся симметричной.
InMemoryDeadLetterStore— область одного прогона / тестов.FilesystemDeadLetterStore—DurableCapability; один атомарный JSON-файл на запись, именуемый по SHA-256-хэшу ключа дедупликации (….dlq.json), так что повторное добавление того же элемента при возобновлении идемпотентно.all()читает записи в детерминированном (отсортированном) порядке и выявляет повреждённую запись броском исключения;count()— это дешёвый подсчёт файлов, а не проверка валидности.
Состояние с ключами
Заголовок раздела «Состояние с ключами»NextPDF\Pro\Stream\State\KeyedStateStoreInterface
Заголовок раздела «NextPDF\Pro\Stream\State\KeyedStateStoreInterface»has, get, put, remove, clear, плюс snapshot(): array и restore(array $snapshot): void для границы контрольной точки. Значения должны быть сериализуемыми в JSON. Для нагрузки по умолчанию «отрисовать-и-зафиксировать» состояние с ключами не используется; оно существует для расширений агрегации/оконного анализа. InMemoryKeyedStateStore — это реализация одного прогона; её потеря при восстановлении — семантически no-op для нагрузки по умолчанию, потому что «ровно один раз» исходит из дедупликации по дайджесту в фиксаторе.
NextPDF\Pro\Stream\State\KeySelector
Заголовок раздела «NextPDF\Pro\Stream\State\KeySelector»final readonly, __construct(string $tenantField = 'tenant_id', string $documentField = 'document_id'). keyFor(RenderManifest $manifest): non-empty-string выводит ключ раздела из метаданных манифеста как rawurlencode(tenant):rawurlencode(document) (кодирование не даёт ("a:b","c") столкнуться с ("a","b:c")), откатываясь к идентификатору задания, когда любое из полей отсутствует, — так что каждый манифест разрешается в стабильный, непустой ключ.
Маркер долговечности и исключения
Заголовок раздела «Маркер долговечности и исключения»NextPDF\Pro\Stream\DurableCapability — это маркерный интерфейс для любого хранилища/фиксатора, состояние которого переживает перезапуск процесса. Устойчивый к сбоям прогон требует, чтобы каждый коллаборатор его реализовывал, поэтому он падает быстро, а не обещает «ровно один раз», которое хранилище в памяти не может сохранить.
Все исключения подсистемы реализуют NextPDF\Pro\Stream\Exception\StreamException (расширяет Throwable), так что вызывающая сторона может единообразно catch (StreamException):
RenderEngineException(RuntimeException) — исполнитель нарушил контракт пакета (неизвестная, дублирующаяся или пропущенная единица; неисправность воркера; тайм-аут).CommitIntegrityException(RuntimeException) — объявленный sha-256 не совпадает с полезной нагрузкой; spec-кодSPEC-COMMIT-422.OutputCommitConflictException(RuntimeException) — расходящиеся байты на занятом ключе при выключенной перезаписи; spec-кодSPEC-COMMIT-409(выставляется черезspecCode()).UnsupportedTargetException(InvalidArgumentException) — схема цели, которую фиксатор не может обслужить.
Соответствие
Заголовок раздела «Соответствие»Движок проверяет манифесты по модели манифеста из Core и производит детерминированные байты плюс дайджесты sha-256; фиксатор обеспечивает атомарные, проверенные на целостность записи ровно один раз. Модуль не выполняет криптографических операций сверх дайджестов содержимого sha-256 и не определяет поведения, специфичного для FIPS.
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»renderBatch()никогда не прерывается на сбое отдельного манифеста; исследуйте каждыйEngineRenderResult.ProcessPoolRenderUnitExecutorкоррелирует строго по индексу и перехэширует байты воркера; ошибочный воркер жёстко проваливается, а не портит вывод.LocalFilesystemCommitterпривязан к одному хосту; межхостовая фиксация ровно один раз требует долговечного фиксатора Enterprise.- Устойчивые к сбоям прогоны должны повсюду использовать хранилища
DurableCapability(файловая система), а не варианты в памяти.
Граница публикации
Заголовок раздела «Граница публикации»Эта страница документирует только внешне наблюдаемое поведение и поддерживаемую публичную поверхность API. Внутренние пути пространств имён, вспомогательные классы, таблицы механизмов, имена файлов runbook и префиксы тикетов — вне области рассмотрения.