Интеграция с фреймворком
Winter Thread порождает по одному PHP CLI-процессу на задачу. Внутри фреймворка или веб-SAPI обычно нужно указать ему, какой PHP запускать, где находится дочерний bootstrap и как подписывать payload’ы. Всё это живёт в Launcher, который вы привязываете один раз при старте приложения.
Привязка лаунчера при инициализации
Конфигурация проходит через единственную точку: Thread::bindLauncher(). Вызовите её один раз, рано —
в boot() сервис-провайдера, в bootstrap контейнера или в любом файле, который загружает каждый процесс
(включая воркеры Swoole). Каждый последующий Thread::start() использует привязанный лаунчер.
Значение по умолчанию без конфигурации — AdaptiveLauncher: на
каждый запуск он смотрит на рантайм и направляет вызов в SwooleLauncher (внутри корутины) или
CliLauncher (обычный CLI, FPM), разрешая бинарник, путь wRunner и секрет за вас. Если вы ничего не
привязываете, Thread::launcher() создаёт его лениво — так что привязка нужна только там, где требуется
переопределение.
<?php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
// Явно то же самое, что происходит без конфигурации.
Thread::bindLauncher(AdaptiveLauncher::adaptive());Чтобы переопределить лишь один аспект, передайте именованный аргумент — лаунчеры неизменяемы
(final readonly), поэтому собирается новый экземпляр:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(
secret: $_ENV['APP_SECRET'],
binaryPath: '/usr/bin/php',
));bindLauncher — единственная привязка
Thread::bindLauncher() заменяет собой привязчики отдельных настроек: нет ни bindBinaryPath(), ни
bindRunner(), ни bindSerSecurity(), ни bindPayloadMode() — каждая из них теперь свойство лаунчера.
Повторная привязка свежего лаунчера — это и есть способ «сбросить» настройки к умолчаниям.
Транспорт задаётся только у CliLauncher
У AdaptiveLauncher::adaptive() аргумента transport нет: под Swoole доставку выбирает
SwooleLauncher сам (разделяемая память при ext-shmop, иначе временный файл). Явный транспорт
имеет смысл на пути CLI/FPM — привязывайте тогда CliLauncher::adaptive(transport: …).
Задание PHP-бинарника под FPM
Под PHP-FPM или CGI запущенный интерпретатор — это веб-обработчик, а не CLI-бинарник, который может
порождать фоновые воркеры. CliLauncher уже справляется с этим: под не-CLI SAPI он разрешает
PHP_BINDIR/php (с откатом на php из PATH), поэтому обычный случай не требует конфигурации.
Переопределяйте его только когда CLI-бинарник находится в другом месте:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(binaryPath: '/usr/bin/php'));Для явной, независимой от окружения конфигурации соберите
CliLauncher конструктором, задав каждую часть:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
Thread::bindLauncher(new CliLauncher(
binaryPath: '/usr/bin/php',
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
transport: new TempFileTransport(),
));Признак неверного бинарника
Если задачи молча не запускаются из веб-запроса, обычная причина — неверный путь к бинарнику: дочерний процесс был запущен под FPM SAPI вместо CLI. Сначала проверьте путь к бинарнику.
Указание на упакованный скрипт runner
Дочерний процесс инициализируется упакованным скриптом wRunner (не wExecutor — этого
имени больше нет). Лаунчер находит его автоматически. Если ваш деплой перемещает vendor/
или вы поставляете собственный bootstrap, задайте путь явно:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
));Собственный bootstrap runner — это забота дочерней стороны, независимая от лаунчера; если вы его заменяете, то обычно заменяете и лаунчер. См. Жизненный цикл runner.
Подпись payload’ов для всего приложения
opis/closure сериализует и (опционально) подписывает задачи, чтобы замыкания и анонимные классы
могли пересекать границу процесса. Задайте секрет для всего приложения — и поддельные или изменённые
payload’ы будут отклонены в дочернем процессе ещё до создания любого объекта. Два эквивалентных
способа задать его:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
// 1. Именованный аргумент лаунчера
Thread::bindLauncher(AdaptiveLauncher::adaptive(secret: $_ENV['APP_SECRET']));
// 2. Вообще без кода — задайте переменную окружения WINTER_THREAD_SECRET, лаунчер прочитает её.Секрет достигает дочернего процесса через переменную окружения WINTER_THREAD_SECRET
(доступную только владельцу), никогда через argv — поэтому подпись работает и тогда, когда
секрет задан аргументом явно. Подробности в Безопасность и производительность.
Swoole: transport выбирается автоматически
Определять Swoole самостоятельно не нужно. Под активным рантаймом (внутри корутины или с
включёнными runtime-хуками) AdaptiveLauncher направляет запуск в SwooleLauncher, а тот доставляет
payload без канала — разделяемой памятью при ext-shmop, иначе временным файлом. Сырой канал stdin
там не годится: он не корутино-безопасен.
Явно задать транспорт можно только на пути CLI/FPM, у CliLauncher:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\ShmTransport;
// Принудительная доставка через разделяемую память (требует ext-shmop).
Thread::bindLauncher(CliLauncher::adaptive(transport: new ShmTransport()));Компромиссы transport’ов описаны в Swoole и доставка payload и Режимы payload.
Продвинутое: построение пула воркеров без фасада Thread
Код фреймворка, управляющий собственным пулом, может обратиться к launcher и управлять им напрямую —
по одному ProcessHandle на воркер вместо объекта Thread на задачу. Сериализуйте задачу с помощью
провайдера безопасности лаунчера, постройте LaunchSpec и собирайте результаты неблокирующим циклом:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\LaunchSpec;
$launcher = Thread::launcher();
$payload = \Opis\Closure\serialize($runnable, $launcher->security());
$handle = $launcher->launch(new LaunchSpec(
payload: $payload,
namespace: 'Pool',
name: 'MyTask',
));
// неблокирующе: true, когда завершён (и реапнут), false пока ещё выполняется
if ($handle->reap()) {
$exit = $handle->getExitCode();
}Поскольку reap() и detach() никогда не блокируются на живом процессе, один цикл может управлять
сотнями handle без зомби. Полные сигнатуры LaunchSpec, Launcher и ProcessHandle — в
справочнике API.
Связанное
- Справочник API —
Launcher, три лаунчера,LaunchSpec,ProcessHandle - Swoole и доставка payload — почему доставка идёт без канала
- Режимы payload — transport’ы Pipe / TempFile / Shm
- Безопасность и производительность — подпись и канал секрета