Пакет · thread

API Reference

Полный справочник по каждому публичному типу в Flytachi\Winter\Thread. Сигнатуры точно соответствуют исходному коду; для каждого элемента перечислены параметры, возвращаемые значения, исключения и неочевидная семантика.

API с высоты птичьего полёта

Поверхность делится на два уровня. Большинству приложений нужен только публичный API — три типа. Низкоуровневый API нужен для построения собственного пула, планировщика или супервизора прямо на сырых примитивах.

Публичный API — то, что вы используете

Тип Вид Для чего используется
Runnable интерфейс описать задачу — поместите свою логику в run()
Thread класс запустить и управлять одной задачей как фоновым процессом
Engine интерфейс настроить доставку/исполнение один раз при инициализации
AdaptiveEngine · ManualEngine класс два движка — самонастраивающийся / явный

Низкоуровневый и расширяющий API — то, на чём вы строите

Тип Вид Роль
Launcher · CliLauncher интерфейс / класс породить процесс → вернуть дескриптор
ProcessHandle класс управлять одним процессом (reap/join/detach/сигнал/чтение)
LaunchSpec DTO все параметры запуска в одном объекте-значении
PayloadTransport · Pipe / TempFile / Shm интерфейс / класс стратегия доставки payload
StagedPayload DTO результат подготовки, который потребляет launcher
Runner · AdaptiveRunner интерфейс / класс исполнение на стороне потомка
Signal класс POSIX-хелперы по сырому PID (учитывают зомби)
ThreadException класс единственное исключение библиотеки

Четыре интерфейса (Engine, Launcher, Runner, PayloadTransport) — это точки расширения, принимающие вашу собственную реализацию; всё остальное — конкретные типы, которые вы потребляете.


Публичный API

Runnable (interface)

Flytachi\Winter\Thread\Runnable — контракт задачи.

php
public function run(array $args): void

Поместите свою логику в run(); она выполняется в рабочем процессе.

Параметр Тип Описание
$args array<string, string|bool> значения конкретного запуска из start() — флаги приходят как булево true, все остальные значения как строки

Возвращает ничего. Сигнализируйте о результате через код возврата (return/нормальное завершение → 0; выброс исключения → ненулевой) или побочные эффекты (файл/БД/очередь).

Контракт: реализующий объект должен быть сериализуемым — никаких живых ресурсов (PDO, сокеты, потоки) в его свойствах; открывайте их внутри run(). Непойманное исключение перехватывается runner’ом, логируется в STDERR со стеком трассировки и превращается в ненулевой код возврата.


Thread (final class)

Flytachi\Winter\Thread\Thread — высокоуровневый фасад над одним процессом. Изменяемый; защищает от конкурентных запусков.

__construct

php
new Thread(
  Runnable $runnable,
  string   $namespace = '',
  ?string  $name = null,
  ?string  $tag = null,
)
Параметр Тип По умолчанию Описание
$runnable Runnable задача для выполнения
$namespace string '' логическая группировка, отображается в заголовке процесса ОС
$name ?string null имя задачи; при null — короткое имя класса Runnable (или 'anonymous' для анонимных классов)
$tag ?string null различитель экземпляра; показывается как @tag (по умолчанию @runnable в заголовке)

Конструирование ничего не запускает. Заголовок процесса — WinterThread <namespace> -> <name>@<tag> (только там, где существует cli_set_process_title()).

Статические — привязка движка

Метод Возвращает Описание
Thread::bindEngine(Engine $engine) void задать движок на весь процесс (вызвать один раз при инициализации)
Thread::engine() Engine текущий движок; лениво создаёт AdaptiveEngine по умолчанию, если ни один не привязан

start

php
start(
  array   $arguments = [],
  bool    $debugMode = false,
  ?string $outputTarget = '/dev/null',
  bool    $detached = false,
): int

Сериализует задачу, запускает процесс и немедленно возвращает управление.

Параметр Тип По умолчанию Описание
$arguments array<string, scalar|null> [] аргументы конкретного запуска, доступные в $args внутри run(). true → флаг без значения; false/null → отбрасывается; прочие скаляры → строки; нескаляры игнорируются
$debugMode bool false включить E_ALL + display_errors в потомке
$outputTarget ?string '/dev/null' '/dev/null' отбрасывает (без pipe); путь дозаписывает (режим a); null направляет в pipe родителю для readOutput()/readError()
$detached bool false демонизировать (fork+setsid) для fire-and-forget без зомби

Возвращает int — PID запущенного процесса (в detached-режиме — эфемерный PID launcher’а). Выбрасывает ThreadException, если поток уже жив или процесс не удаётся запустить.

Broken pipe

Передача null направляет stdout/stderr в pipe родителю. Если родитель их никогда не вычитывает, буфер pipe в ОС (~64 КБ) заполняется, потомок блокируется на write(), что в итоге приводит к Broken pipe. Передавайте null, только если активно вызываете readOutput()/readError(); иначе оставляйте значение по умолчанию '/dev/null'. См. Вывод и Broken Pipe.

Жизненный цикл и ожидание

Метод Возвращает Описание
join(int $timeout = 0) ?int блокирует (опрос каждые 50 мс) до завершения; возвращает код возврата, null по таймауту (секунды; 0 = бесконечно) или -1, если не был запущен или воркер убит сигналом. Пожинает по завершении
reap() bool неблокирующий: true, если завершён/отсутствует (и пожат, код возврата установлен), false, если ещё выполняется
detach() void прекратить отслеживание (неблокирующий; без proc_close). После этого isAlive()false, reap()true, сигналы → false, код возврата никогда не собирается. Внимание: detach() ≠ detached-режим

Состояние

Метод Возвращает Описание
getPid() ?int PID запущенного процесса или null до start(); PID launcher’а в detached-режиме
isAlive() bool выполняется ли процесс сейчас? false до запуска / после завершения / после detach()
getExitCode() ?int код возврата после пожинания (join/reap); -1, если воркер убит сигналом; null до пожинания и null навсегда после detach()

Сигналы (требуют ext-posix; каждый возвращает false, если не выполняется)

Метод Сигнал Описание
pause() SIGSTOP приостановить (неблокируемый)
resume() SIGCONT возобновить приостановленный воркер
interrupt() SIGINT эквивалент Ctrl+C (перехватываемый)
terminate() SIGTERM запрос корректной остановки (перехватываемый)
kill() SIGKILL принудительное убийство (неблокируемое)

Каждый возвращает booltrue, если сигнал был отправлен. Чтобы отреагировать корректно, задача должна установить обработчик; см. Корректное завершение.

Вывод (непустой только при запуске с outputTarget: null)

Метод Возвращает Описание
readOutput() string доступный прямо сейчас STDOUT (неблокирующий); '', если ничего / не в pipe
readError() string доступный прямо сейчас STDERR (неблокирующий); '', если ничего / не в pipe

Метаданные

Метод Возвращает Описание
getNamespace() string пространство имён
getName() string разрешённое имя задачи
getTag() ?string тег или null

Engine (interface)

Flytachi\Winter\Thread\Engine\Engine — корень конфигурации/стратегии, используется только на стороне родителя. У него нет метода для стороны потомка: воркер запускает отдельный AdaptiveRunner. security() родителя передаётся потомку через переменную окружения WINTER_THREAD_SECRET.

Метод Возвращает Описание
transport() PayloadTransport как доставляется payload
launcher() Launcher как порождается процесс
binaryPath() string абсолютный путь к бинарнику PHP CLI
runnerPath() string абсолютный путь к скрипту wRunner
security() ?DefaultSecurityProvider Opis\Closure\Security\DefaultSecurityProvider для подписи или null, если секрета нет

AdaptiveEngine (final readonly class, default)

Flytachi\Winter\Thread\Engine\AdaptiveEngine — самонастраивающийся; неизменяемый.

php
new AdaptiveEngine(
  ?string           $secret = null,
  ?PayloadTransport $transport = null,
  ?string           $binaryPath = null,
  ?string           $runnerPath = null,
  ?Launcher         $launcher = null,
)
Параметр Тип Разрешаемое значение по умолчанию при null
$secret ?string переменная окружения WINTER_THREAD_SECRET, иначе null (без подписи)
$transport ?PayloadTransport TempFileTransport при активном рантайме Swoole (выполняющаяся корутина или включённые хуки рантайма), иначе PipeTransport
$binaryPath ?string CLI SAPI → PHP_BINARY; не-CLI (FPM/CGI) → PHP_BINDIR/php, если исполняемый, иначе 'php'
$runnerPath ?string упакованный wRunner
$launcher ?Launcher CliLauncher, собранный из разрешённых binary/runner/transport + окружение потомка (несущее секрет); переданный launcher используется как есть

Неизменяемый — чтобы изменить один аспект, создайте новый экземпляр.

ManualEngine (final class)

Flytachi\Winter\Thread\Engine\ManualEngine — явный, с чистого листа. Неизменяемые wither-методы (каждый возвращает клон); незаданная обязательная часть выбрасывает ThreadException при обращении ("ManualEngine: <part> is not configured.").

Метод Возвращает Описание
withTransport(PayloadTransport $t) static задать транспорт
withBinaryPath(string $path) static задать путь к бинарнику PHP
withRunnerPath(string $path) static задать путь к wRunner
withSecurity(string $secret) static включить подпись этим секретом
withLauncher(Launcher $l) static использовать свой launcher (в обход умолчания)

При своём withLauncher(...) transport/binaryPath/runnerPath не обязательны — launcher сам владеет обвязкой. Иначе все три обязательны. security() возвращает null, если секрет не был задан.


Низкоуровневый и расширяющий API

Для пулов, планировщиков и собственных бэкендов. Thread построен ровно на этих частях — вы можете управлять ими напрямую.

Launcher (interface)

Flytachi\Winter\Thread\Launch\Launcher — стратегия порождения процесса на стороне родителя.

php
public function launch(LaunchSpec $spec): ProcessHandle
Параметр Тип Описание
$spec LaunchSpec всё необходимое для запуска процесса

Возвращает ProcessHandle. Выбрасывает ThreadException при сбое. Реализуйте его, чтобы запускать по SSH, в контейнере или на удалённом узле.

CliLauncher (final readonly class)

Flytachi\Winter\Thread\Launch\CliLauncher — по умолчанию, на основе proc_open.

php
new CliLauncher(
  string           $binaryPath,
  string           $runnerPath,
  PayloadTransport $transport,
  array            $childEnv = [],
)
Параметр Тип По умолчанию Описание
$binaryPath string бинарник PHP CLI
$runnerPath string скрипт wRunner
$transport PayloadTransport как payload подготавливается/принимается
$childEnv array<string,string> [] дополнительное окружение потомка (например, ['WINTER_THREAD_SECRET' => '…'])

Собирает команду, полностью экранированную через escapeshellarg. Пустой $childEnv → потомок наследует окружение родителя; непустой → сливается поверх унаследованного окружения.


ProcessHandle (final class)

Flytachi\Winter\Thread\Launch\ProcessHandle — низкоуровневый примитив процесса, возвращаемый launcher’ом. Изменяемый; отслеживает состояние процесса/pipe’ов/кода возврата. Не конструируется напрямую — получается из Launcher::launch().

Метод Возвращает Описание
getPid() int PID процесса
isAlive() bool выполняется ли процесс сейчас?
join(int $timeout = 0) ?int блокирует до завершения; код возврата, null по таймауту (секунды) или записанный код / -1, если ресурс уже пропал
reap() bool неблокирующий; true, если завершён (и пожат)
detach() void прекратить отслеживание; закрывает pipe’ы, без proc_close, без очистки транспорта (ей владеет потомок)
getExitCode() ?int код возврата после пожинания; null после detach()
readOutput() string неблокирующий STDOUT; '', если нет pipe вывода
readError() string неблокирующий STDERR; '', если нет pipe вывода
signal(int $signal) bool posix_kill(pid, signal) — только если жив; иначе false

reap(), detach() и __destruct() неблокирующие на живом процессе; блокирующий proc_close выполняется только на уже мёртвом процессе. __destruct() пожинает завершённый процесс либо отсоединяет ещё работающий — он никогда не блокирует родителя.


LaunchSpec (final readonly class)

Flytachi\Winter\Thread\LaunchSpec — неизменяемый набор всех параметров запуска. Постройте его напрямую, чтобы управлять launcher’ом без Thread.

php
new LaunchSpec(
  string  $payload,
  string  $namespace = '',
  string  $name = 'anonymous',
  ?string $tag = null,
  array   $arguments = [],
  bool    $debug = false,
  ?string $output = '/dev/null',
  bool    $detached = false,
)
Параметр Тип По умолчанию Описание
$payload string уже сериализованный Runnable
$namespace string '' группировка для заголовка процесса
$name string 'anonymous' имя для заголовка процесса
$tag ?string null тег экземпляра
$arguments array<string, scalar|null> [] передаётся задаче как --arg-*
$debug bool false отчёт об ошибках на стороне потомка
$output ?string '/dev/null' '/dev/null' | путь к файлу | null (pipe родителю)
$detached bool false демонизировать потомка

Все свойства публичные и readonly.


PayloadTransport (interface)

Flytachi\Winter\Thread\Payload\PayloadTransport — доставляет сериализованный payload через границу процессов в две части плюс очистка.

Метод Возвращает Выполняется в Описание
stage(string $payload) StagedPayload родитель подготовить доставку (спецификация fd-0, аргументы CLI, ссылка для очистки)
receive(array $options) string потомок прочитать payload обратно (STDIN или shm через shmkey)
cleanup(StagedPayload $staged) void родитель освободить подготовленные ресурсы; безопасно, даже если они уже пропали

$options в receive() — это array<string, mixed> — разобранные опции CLI потомка.

Кастомным транспортам нужен подходящий runner

Стандартный AdaptiveRunner принимает только из STDIN или shm — он не вызывает receive() кастомного транспорта. Поэтому транспорт, доставляющий вне полосы (Redis/TCP/FIFO), также требует подходящего runner’а на стороне потомка. См. Swoole и доставка payload.

PipeTransport

…\Payload\PipeTransport — payload через pipe stdin потомка (записывается после запуска). Без расширений. Не безопасен для Swoole (использует pipe fd). По умолчанию в обычном CLI.

TempFileTransport

…\Payload\TempFileTransport — payload через временный файл 0600, помещённый на stdin, удаляемый (unlink) сразу после запуска (потомок сохраняет свой fd). Без расширений. Безопасен для Swoole. Выбрасывает ThreadException, если временный файл не удаётся создать/записать.

ShmTransport

…\Payload\ShmTransport — payload через сегмент System V разделяемой памяти 0600; целочисленный ключ передаётся как --shmkey, а потомок читает, затем удаляет его. Без диска, безопасен для Swoole. Требует ext-shmop — выбрасывает ThreadException ("ShmTransport requires ext-shmop.") как в stage(), так и в receive(), если расширения нет, а также при сбое выделения.

StagedPayload (final readonly class)

Flytachi\Winter\Thread\Payload\StagedPayload — результат stage(); launcher читает его обобщённо.

php
new StagedPayload(
  array   $stdinSpec,
  array   $cliArgs = [],
  ?string $pipePayload = null,
  ?string $unlinkAfterOpen = null,
  mixed   $ref = null,
)
Параметр Тип По умолчанию Описание
$stdinSpec array<int,string> дескриптор fd-0 для proc_open (['pipe','r'] или ['file',$path,'r'])
$cliArgs array<int,string> [] дополнительные, уже безопасные аргументы запуска (например, ['--shmkey=123'])
$pipePayload ?string null записывается в pipe после запуска (транспорт pipe)
$unlinkAfterOpen ?string null удаляется (unlink) после запуска, когда потомок держит свой fd (транспорт temp-file)
$ref mixed null непрозрачный дескриптор очистки (путь temp / ключ shm), передаваемый в cleanup()

Runner (interface)

Flytachi\Winter\Thread\Runner\Runner — стратегия исполнения на стороне потомка.

php
public function execute(array $options): int
Параметр Тип Описание
$options array<string, mixed> разобранные опции CLI wRunner (namespace, name, tag, debug, detach, shmkey)

Возвращает int — код возврата процесса (0 — успех, ненулевой — сбой).

AdaptiveRunner (final readonly class)

Flytachi\Winter\Thread\Runner\AdaptiveRunner — стандартный runner на стороне потомка (управляется wRunner). Он зависит только от провайдера безопасности — не от Engine — так что обе стороны остаются независимыми.

php
new AdaptiveRunner(
  ?DefaultSecurityProvider $security = null,
  mixed                    $errStream = null,
)
Параметр Тип По умолчанию Описание
$security ?DefaultSecurityProvider null проверяет подпись payload; постройте его из того же секрета, которым подписал родитель (null = без подписи). wRunner строит его из WINTER_THREAD_SECRET
$errStream resource|null null куда пишутся диагностические сообщения; по умолчанию STDERR (внедряемо для тестов)

Ход execute(): принять payload (--shmkey → shm, иначе STDIN) → проверить + десериализовать через opis/closure (отклоняя пустые, не-Runnable или неподписанные/подделанные payload’ы ненулевым кодом возврата) → опционально fork+setsid (detached) → установить заголовок процесса → run() → код возврата.

Замена runner'а

Чтобы использовать свой Runner, вы заменяете bootstrap потомка: напишите собственный скрипт вроде wRunner, который конструирует ваш runner (обычно запускается кастомным Launcher). Для runner’а нет шва bindEngine — по замыслу сторона потомка независима от Engine родителя. См. Жизненный цикл runner’а.


Signal (final class)

Flytachi\Winter\Thread\Signal — статические POSIX-хелперы по сырому PID. isProcessRunning() трактует зомби (состояние Z) как не выполняющиеся, как на Linux (/proc/<pid>/status), так и на macOS (ps).

Метод Возвращает Описание
isProcessRunning(int $pid) bool существует ли живой (не-зомби) процесс с этим PID?
interrupt(int $pid) bool отправить SIGINT (если выполняется)
termination(int $pid) bool отправить SIGTERM (если выполняется)
close(int $pid) bool отправить SIGHUP (если выполняется)
kill(int $pid) bool отправить SIGKILL (если выполняется)
wait(int $pid, int $timeout = 10) bool опрашивать, пока не пропадёт; false по таймауту (секунды)
interruptAndWait(int $pid, int $timeout = 10) bool SIGINT, затем ожидание
terminationAndWait(int $pid, int $timeout = 10) bool SIGTERM, затем ожидание
closeAndWait(int $pid, int $timeout = 10) bool SIGHUP, затем ожидание

Нет pause/resume в Signal

Signal покрывает только сигналы остановки/завершения. Приостановка/возобновление по сырому PID не предоставляются — используйте posix_kill($pid, SIGSTOP) / posix_kill($pid, SIGCONT) напрямую либо API Thread::pause()/resume().

PID reuse

Сырые PID подвержены переиспользованию PID (PID reuse) в ОС — PID может быть переназначен после завершения исходного процесса. Предпочитайте Thread/ProcessHandle для надёжного отслеживания; используйте Signal только со свежеполученными PID (например, самостоятельно сообщённый PID detached-воркера). См. PID reuse и сигналы.


ThreadException (class)

Flytachi\Winter\Thread\ThreadException extends \RuntimeException — единственный тип исключения библиотеки.

Выбрасывается при: сбое запуска (proc_open отклонён или процесс сразу умер), запуске уже работающего Thread, некорректной конфигурации (незаданная часть ManualEngine или ShmTransport без ext-shmop) и сбоях подготовки транспорта (временный файл / выделение shm). Ловите его вокруг start() и настройки движка.