Пакет · thread · справочник

Транспорты payload

Как сериализованный Runnable доставляется из родительского процесса в дочерний. Доставка — это подключаемый объект-транспорт, реализующий PayloadTransport, выбираемый на Engine. Значение по умолчанию (PipeTransport) подходит для обычного CLI и PHP-FPM; два других создают ноль pipe-дескрипторов на стороне родителя, что и делает их безопасными под циклами событий вроде Swoole. Почему это важно: Swoole и доставка payload.

Три транспорта

Все три доставляют точно те же байты — код вашей задачи никогда не знает, какой из них использовался. Они различаются лишь механизмом и требованиями.

Транспорт Доставка Pipe fd родителя Безопасен для Swoole Требует
PipeTransport сериализованная задача записывается в pipe stdin потомка после запуска да нет
TempFileTransport задача записывается во временный файл 0600, помещённый на stdin, удаляемый (unlink) сразу после запуска нет да доступный для записи sys_get_temp_dir()
ShmTransport задача помещается в System V разделяемую память (0600), ключ передаётся через --shmkey нет да ext-shmop
  • PipeTransport — по умолчанию в обычном CLI: проще всего, ничего на диске, без дополнительных расширений. Родитель записывает payload в pipe после proc_open и закрывает его; потомок читает свой STDIN до EOF. Файловый дескриптор pipe и есть причина, по которой он не безопасен для Swoole.
  • TempFileTransport — полностью избегает pipe-дескрипторов. Файл создаётся с правами 0600 (только для владельца) в системном временном каталоге и удаляется (unlink) сразу после старта процесса — потомок сохраняет открытый fd, поэтому на диске ничего не остаётся, хотя путь уже исчез. Расширений не нужно. Выбрасывает ThreadException, если временный файл не удаётся создать или записать.
  • ShmTransport — тоже избегает pipe’ов, храня payload только в RAM. Родитель выделяет сегмент 0600, записывает payload и передаёт целочисленный ключ как --shmkey; потомок читает сегмент и удаляет его. Stdin потомка — /dev/null. Требует ext-shmop — выбрасывает ThreadException (ShmTransport requires ext-shmop.) как на стороне подготовки, так и на стороне приёма, если расширения нет, а также при сбое выделения.

Как потомок выбирает свою сторону приёма

Выбранный родителем транспорт определяет подготовку, но потомок выбирает, как принимать, исключительно по опциям CLI: если присутствует --shmkey, он читает разделяемую память, иначе читает STDIN. Поскольку и Pipe, и TempFile приходят на STDIN, потомок обрабатывает их одинаково — движку никогда не нужно сериализовать выбор транспорта.

Выбор транспорта

Транспорт выбирается на Engine, затем движок один раз привязывается на весь процесс при инициализации через Thread::bindEngine(). Настройки на уровне отдельного Thread нет.

php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Payload\TempFileTransport;

// Самонастраивающийся движок, переопределяем только транспорт:
Thread::bindEngine(new AdaptiveEngine(transport: new TempFileTransport()));

Для явного движка с чистого листа используйте ManualEngine и wither-методы:

php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\ManualEngine;
use Flytachi\Winter\Thread\Payload\ShmTransport;

Thread::bindEngine(
  (new ManualEngine())
      ->withTransport(new ShmTransport())
      ->withBinaryPath(PHP_BINARY)
      ->withRunnerPath('/path/to/wRunner')
);

В обычном CLI трогать это почти никогда не нужно — транспорт по умолчанию PipeTransport вполне подходит. Выбирайте другой, когда:

  • Вы работаете под SwooleAdaptiveEngine уже автоматически переключается на TempFileTransport (см. ниже); переопределяйте, только если вам конкретно нужен ShmTransport.
  • Вы хотите, чтобы на диске никогда ничего не было — используйте ShmTransport (только RAM, нужен ext-shmop).
  • Ваш временный каталог необычен (крошечный tmpfs, noexec, ограниченный open_basedir) — Pipe или Shm избегают временного файла.

Автоматический выбор под Swoole

Движок по умолчанию AdaptiveEngine сам настраивает свой транспорт. Когда транспорт не передан, он использует TempFileTransport при активном рантайме Swoole — обнаруживаемом внутри корутины (\Swoole\Coroutine::getCid() !== -1) или при включённых хуках рантайма (\Swoole\Runtime::getHookFlags() !== 0) — и PipeTransport в остальных случаях (в том числе когда расширение Swoole не загружено).

php
// Под активным рантаймом Swoole это прозрачно использует TempFile:
$thread = new Thread(new MyTask());
$thread->start();

Просто установленное, но неактивное расширение не форсирует TempFile — переключение завязано на активный рантайм. Полный каскад утечки fd и оговорка про pipe вывода описаны в Swoole и доставка payload.

Механика, которую стоит знать

  • TempFileTransport — потомок держит файловый дескриптор открытым; запись в каталоге удаляется сразу после запуска, поэтому на диске ничего не остаётся.
  • ShmTransport — stdin — это /dev/null; потомок читает сегмент и удаляет его, а cleanup() родителя удаляет любой уцелевший сегмент, если потомок упал до чтения. Ключи уникальны для каждого start().
  • Очистка на стороне родителя — это подстраховка. cleanup() транспорта выполняется, когда дескриптор завершается, чтобы освободить временный файл или сегмент shm, который потомок так и не потребил. Его всегда безопасно вызывать, даже когда ресурс уже пропал.

Как написать свой транспорт

Реализуйте PayloadTransportstage / receive / cleanup — и привяжите его через движок. Учитывайте сторону потомка: стандартный AdaptiveRunner принимает из STDIN (или из shm, когда присутствует --shmkey) и не вызывает receive() кастомного транспорта. Доставка на stdin потомка работает прозрачно; внеполосный канал (Redis, TCP, FIFO) требует подходящего runner’а на стороне потомка, поставляемого вместе с ним. См. Жизненный цикл runner’а.

Смежное