Транспорты 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 нет.
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-методы:
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
вполне подходит. Выбирайте другой, когда:
- Вы работаете под Swoole —
AdaptiveEngineуже автоматически переключается на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 не загружено).
// Под активным рантаймом Swoole это прозрачно использует TempFile:
$thread = new Thread(new MyTask());
$thread->start();Просто установленное, но неактивное расширение не форсирует TempFile — переключение завязано на активный рантайм. Полный каскад утечки fd и оговорка про pipe вывода описаны в Swoole и доставка payload.
Механика, которую стоит знать
TempFileTransport— потомок держит файловый дескриптор открытым; запись в каталоге удаляется сразу после запуска, поэтому на диске ничего не остаётся.ShmTransport— stdin — это/dev/null; потомок читает сегмент и удаляет его, аcleanup()родителя удаляет любой уцелевший сегмент, если потомок упал до чтения. Ключи уникальны для каждогоstart().- Очистка на стороне родителя — это подстраховка.
cleanup()транспорта выполняется, когда дескриптор завершается, чтобы освободить временный файл или сегмент shm, который потомок так и не потребил. Его всегда безопасно вызывать, даже когда ресурс уже пропал.
Как написать свой транспорт
Реализуйте PayloadTransport — stage / receive / cleanup — и привяжите его через
движок. Учитывайте сторону потомка: стандартный AdaptiveRunner принимает из STDIN (или из
shm, когда присутствует --shmkey) и не вызывает receive() кастомного транспорта.
Доставка на stdin потомка работает прозрачно; внеполосный канал (Redis, TCP, FIFO) требует
подходящего runner’а на стороне потомка, поставляемого вместе с ним. См.
Жизненный цикл runner’а.
Смежное
- Swoole и доставка payload — каскад утечки fd и механика доставки
- Справочник API — полные сигнатуры
Engine,PayloadTransportи транспортов - Интеграция с фреймворком — привязка движка под Swoole
- Безопасность и производительность — каналы
0600, компромиссы по стоимости