Перейти к содержимому
getnextpdf.com

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 order
public function maxBatchSize(): int; // int<1, max> backpressure hint
public function isAvailable(): bool;

$manifests — это list<RenderManifest> размером не более maxBatchSize(); $variablesByJobId отображает идентификатор задания на переменные шаблона array<string, scalar>. Сбой отдельного манифеста — это поэлементный результат Failed/Timeout, который никогда не прерывает пакет.

Синхронный, однопроцессный эталон. Проверяет каждый манифест с отказом в закрытое состояние через 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.

final readonly, __construct(RenderUnitExecutorInterface $executor). Оборачивает каждый манифест в индексированную RenderUnit, прогоняет их через исполнителя и пересортировывает завершения по индексу, так что вывод побайтно идентичен последовательной отрисовке. Индекс завершения вне [0, count) бросает RenderEngineException::unknownUnit(); повторный индекс бросает duplicateResult(); пропущенный индекс бросает missingResult(). maxBatchSize() и isAvailable() делегируются исполнителю.

public function execute(array $units): iterable; // iterable<CompletedRenderUnit>, any order
public function maxBatchSize(): int;
public function isAvailable(): bool;

Реализации могут выдавать завершения в любом порядке; ConcurrentRenderEngine восстанавливает порядок по индексу.

final readonly, __construct(RenderEngineInterface $inner). Отрисовывает каждую единицу по порядку через внутренний движок — детерминированный эталон корректности, которому параллельный исполнитель должен соответствовать побайтно. Никакого времени, процессов, потоков или случайности.

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.

final readonlyint<0, max> $index, RenderManifest $manifest, array<string, scalar> $variables. Корреляция — по index, а не по идентификатору задания (идентификаторы заданий не гарантированно уникальны внутри пакета).

final readonlyint $index (недоверенный, проверяется движком), EngineRenderResult $result.

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() сообщает статус. Отрисованный результат несёт байты и дайджест, но никогда зафиксированное местоположение.

Перечисление со строковым бэкендом: Rendered, Failed, Timeout. isRetryable() равно true только для Timeout, поэтому вызывающая сторона классифицирует тайм-аут как временный, не переисследуя ошибку.

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 (неподдерживаемая схема цели).

final readonly, реализует OutputCommitterInterface, DurableCapability. __construct(string $rootDirectory, ?AtomicFileWriter $writer = null, ?ClockInterface $clock = null). Обслуживает только схему file; разрешает каждую цель в пределах одного настроенного корня и пишет через атомарный writer (временный файл O_EXCL → fsync → переименование на том же томе). Вся критическая секция (включая создание родительского каталога) выполняется под эксклюзивной блокировкой flock на файле блокировки на каждый корень, который держится вне пространства ключей вывода, и фиксация с отказом в закрытое состояние, если блокировку не удаётся открыть или захватить. Он отказывается от финальных компонентов по символическим ссылкам и от любого ключа, содержащего двоеточие (вектор альтернативного потока данных NTFS). Межхостовая конкурентная фиксация ровно один раз на тот же ключ требует долговечного фиксатора Enterprise. Корень, который является системным временным каталогом или содержит его, бросает InvalidArgumentException.

final readonlyjobId, OutputObjectKey $target, sha256, int<0, max> $bytesWritten, bool $idempotentReuse, DateTimeImmutable $committedAt. toArray() / fromArray() полностью обратимы по кругу (цель структурирована, а не лишённый данных URI); fromArray() строг и бросает InvalidArgumentException при пропущенных или некорректных полях.

load(string $runId): ?RunCheckpoint и save(RunCheckpoint $checkpoint): void (долговечно и атомарно — читатель никогда не видит наполовину записанную контрольную точку).

final readonlyrunId, 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 и восстанавливает состояние с ключами; состояние, изменённое после последнего барьера, пересчитывается вперёд, а не является ошибкой, потому что долговечное «ровно один раз» исходит из дедупликации по дайджесту в фиксаторе.

final readonly, реализует CheckpointStoreInterface, DurableCapability. Один JSON-файл на прогон, записываемый атомарно. Идентификаторы прогона должны соответствовать [A-Za-z0-9._-]+ и не содержать ..; несуществующий каталог бросает InvalidArgumentException.

isCommitted(IdempotencyKey $key): bool, markCommitted(IdempotencyKey $key, CommitReceipt $receipt): void, receiptFor(IdempotencyKey $key): ?CommitReceipt. Быстрый путь, сокращающий путь до отрисовки повтора; сравнение дайджестов в фиксаторе остаётся долговечной гарантией, поэтому потерянная запись в худшем случае напрасно тратит повторную отрисовку, которую фиксатор дедуплицирует.

  • InMemoryIdempotencyStore — область одного прогона / тестов (теряется при сбое).
  • FilesystemIdempotencyStoreDurableCapability; один атомарный JSON-файл на зафиксированный ключ (сериализованная квитанция), именуемый по хэшу значения ключа. Отметки идемпотентны; конкурентная повторная отметка безвредно состязается на одном файле. Несуществующий каталог бросает InvalidArgumentException.

final readonlypositive-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 (без встроенного джиттера; применяйте его на стороне вызова).

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

final readonlyjobId, idempotencyKeyValue, positive-int $attempts, lastErrorCode, lastErrorMessage, DateTimeImmutable $failedAt, необязательный ?string $runId, необязательный int<1, max> $sourceOffset. dedupKey() равен runId:sourceOffset, когда оба известны, иначе значение ключа идемпотентности. fromArray() разбирает failed_at строго как ATOM (отклоняя относительные или не-ATOM выражения), так что сериализация/десериализация остаётся симметричной.

  • InMemoryDeadLetterStore — область одного прогона / тестов.
  • FilesystemDeadLetterStoreDurableCapability; один атомарный JSON-файл на запись, именуемый по SHA-256-хэшу ключа дедупликации (….dlq.json), так что повторное добавление того же элемента при возобновлении идемпотентно. all() читает записи в детерминированном (отсортированном) порядке и выявляет повреждённую запись броском исключения; count() — это дешёвый подсчёт файлов, а не проверка валидности.

has, get, put, remove, clear, плюс snapshot(): array и restore(array $snapshot): void для границы контрольной точки. Значения должны быть сериализуемыми в JSON. Для нагрузки по умолчанию «отрисовать-и-зафиксировать» состояние с ключами не используется; оно существует для расширений агрегации/оконного анализа. InMemoryKeyedStateStore — это реализация одного прогона; её потеря при восстановлении — семантически no-op для нагрузки по умолчанию, потому что «ровно один раз» исходит из дедупликации по дайджесту в фиксаторе.

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 и префиксы тикетов — вне области рассмотрения.