Интеграция с фреймворком
Winter Thread порождает по одному PHP CLI-процессу на задачу. Внутри фреймворка или веб-SAPI обычно нужно указать ему, какой PHP запускать, где находится дочерний bootstrap и как подписывать payload’ы. Всё это живёт в одном Engine, который вы привязываете один раз при старте приложения.
Привязка engine при инициализации
Конфигурация проходит через единственную точку: Thread::bindEngine(). Вызовите её один раз, рано —
в boot() сервис-провайдера, в bootstrap контейнера или в любом файле, который загружает каждый процесс
(включая воркеры Swoole). Каждый последующий Thread::start() использует привязанный engine.
Значение по умолчанию без конфигурации — AdaptiveEngine: он определяет
окружение и разрешает transport, PHP-бинарник, путь wRunner и секрет за вас. Если вы ничего не привязываете,
Thread::engine() создаёт его лениво — так что привязывать нужно только тогда, когда требуется
переопределение.
<?php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
// Без конфигурации: определяет бинарник, transport и секрет из окружения.
Thread::bindEngine(new AdaptiveEngine());Чтобы переопределить лишь один аспект, передайте именованный аргумент — AdaptiveEngine неизменяем,
поэтому создайте новый экземпляр:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(
secret: $_ENV['APP_SECRET'],
binaryPath: '/usr/bin/php',
));bindEngine — единственная привязка
Thread::bindEngine() заменяет старые привязчики отдельных настроек. Больше нет ни bindBinaryPath(),
ни bindRunner(), ни bindSerSecurity(), ни bindPayloadMode() — каждая из них теперь является
свойством engine. Повторная привязка свежего engine — это и есть способ «сбросить» настройки к значениям
по умолчанию.
Задание PHP-бинарника под FPM
Под PHP-FPM или CGI запущенный интерпретатор — это веб-обработчик, а не CLI-бинарник, который может
порождать фоновые воркеры. AdaptiveEngine уже справляется с этим: под не-CLI SAPI он разрешает
PHP_BINDIR/php (с откатом на php из PATH), поэтому обычный случай не требует конфигурации.
Переопределяйте его только когда CLI-бинарник находится в другом месте:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(binaryPath: '/usr/bin/php'));Для явной, независимой от окружения конфигурации используйте
ManualEngine — он ничего не определяет и требует задать
каждую часть:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\ManualEngine;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
Thread::bindEngine(
(new ManualEngine())
->withBinaryPath('/usr/bin/php')
->withRunnerPath(__DIR__ . '/vendor/flytachi/winter-thread/wRunner')
->withTransport(new TempFileTransport())
);Признак неверного бинарника
Если задачи молча не запускаются из веб-запроса, обычная причина — неверный путь к бинарнику:
дочерний процесс был запущен под FPM SAPI вместо CLI. Сначала проверьте binaryPath().
Указание на упакованный скрипт runner
Дочерний процесс инициализируется упакованным скриптом wRunner (не wExecutor — этого
имени больше нет). AdaptiveEngine находит его автоматически. Если ваш деплой перемещает vendor/
или вы поставляете собственный bootstrap, задайте путь явно:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
));Собственный bootstrap runner — это забота дочерней стороны, независимая от engine; если вы его заменяете, то обычно заменяете и launcher. См. Жизненный цикл runner.
Подпись payload’ов для всего приложения
opis/closure сериализует и (опционально) подписывает задачи, чтобы замыкания и анонимные классы
могли пересекать границу процесса. Задайте секрет для всего приложения — и поддельные или изменённые
payload’ы будут отклонены в дочернем процессе ещё до создания любого объекта. Три эквивалентных
способа задать его:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Engine\ManualEngine;
// 1. Именованный аргумент AdaptiveEngine
Thread::bindEngine(new AdaptiveEngine(secret: $_ENV['APP_SECRET']));
// 2. Wither ManualEngine
Thread::bindEngine((new ManualEngine())->withSecurity($_ENV['APP_SECRET']));
// 3. Вообще без кода — задайте переменную окружения WINTER_THREAD_SECRET; AdaptiveEngine прочитает её.Секрет достигает дочернего процесса через переменную окружения WINTER_THREAD_SECRET
(доступную только владельцу), никогда через argv — поэтому подпись продолжает работать даже
для ManualEngine с явным секретом. Подробности в Безопасность и производительность.
Swoole: transport выбирается автоматически
Больше не нужно определять Swoole самостоятельно. AdaptiveEngine выбирает
TempFileTransport автоматически всякий раз, когда работает под
активным рантаймом Swoole (внутри корутины или с включёнными runtime-хуками) — потому что сырой канал
stdin не является корутино-безопасным. Вне Swoole он использует PipeTransport. Старый сниппет с ручным
определением Swoole больше не нужен.
При желании вы всё ещё можете принудительно задать конкретный transport:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Payload\ShmTransport;
// Принудительная доставка через разделяемую память (требует ext-shmop).
Thread::bindEngine(new AdaptiveEngine(transport: new ShmTransport()));Компромиссы transport’ов описаны в Swoole и доставка payload и Режимы payload.
Продвинутое: построение пула воркеров без фасада Thread
Код фреймворка, управляющий собственным пулом, может обратиться к launcher и управлять им напрямую —
по одному ProcessHandle на воркер вместо объекта Thread на задачу. Сериализуйте задачу с помощью
провайдера безопасности engine, постройте LaunchSpec и собирайте результаты неблокирующим циклом:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\LaunchSpec;
$launcher = Thread::engine()->launcher();
$payload = \Opis\Closure\serialize($runnable, Thread::engine()->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 —
Engine,AdaptiveEngine,ManualEngine,LaunchSpec,ProcessHandle - Swoole и доставка payload — почему transport переключается автоматически
- Режимы payload — transport’ы Pipe / TempFile / Shm
- Безопасность и производительность — подпись и канал секрета