Payload transports
How the serialized Runnable is delivered from the parent to the child
process. Delivery is a pluggable transport object implementing
PayloadTransport, selected on an Engine.
The default (PipeTransport) is fine for plain CLI and PHP-FPM; the other
two create zero parent-side pipe descriptors, which is what makes them safe under
event loops like Swoole. Why that matters:
Swoole & payload delivery.
The three transports
All three deliver the exact same bytes — your task code never knows which was used. They differ only in mechanism and prerequisites.
| Transport | Delivery | Parent pipe fd | Swoole-safe | Requires |
|---|---|---|---|---|
PipeTransport |
serialized task written to the child’s stdin pipe after launch | yes | no | — |
TempFileTransport |
task written to a 0600 temp file placed on stdin, unlinked right after launch |
none | yes | writable sys_get_temp_dir() |
ShmTransport |
task placed in System V shared memory (0600), key passed via --shmkey |
none | yes | ext-shmop |
PipeTransport— the default in plain CLI: simplest, nothing on disk, no extra extension. The parent writes the payload into the pipe afterproc_openand closes it; the child reads its STDIN to EOF. The pipe file descriptor is why it is not Swoole-safe.TempFileTransport— avoids pipe descriptors entirely. The file is created0600(owner-only) in the system temp dir and unlinked immediately after the process starts — the child keeps its open fd, so nothing lingers on disk even though the path is gone. No extension needed. ThrowsThreadExceptionif the temp file can’t be created or written.ShmTransport— also avoids pipes, keeping the payload in RAM only. The parent allocates a0600segment, writes the payload, and passes the integer key as--shmkey; the child reads the segment and deletes it. The child’s stdin is/dev/null. Requiresext-shmop— throwsThreadException(ShmTransport requires ext-shmop.) on both the staging and receiving side if the extension is missing, and on allocation failure.
How the child chooses its receiving side
The parent’s chosen transport determines staging, but the child picks how to receive
purely from CLI options: if --shmkey is present it reads shared memory, otherwise it
reads STDIN. Because Pipe and TempFile both arrive on STDIN, the child treats them
identically — the engine never has to serialize the transport choice.
Selecting a transport
A transport is chosen on an Engine, then the engine is bound process-wide once at
bootstrap with Thread::bindEngine(). There is no per-Thread setting.
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
// Self-configuring engine, override just the transport:
Thread::bindEngine(new AdaptiveEngine(transport: new TempFileTransport()));For an explicit clean-slate engine, use ManualEngine and wither methods:
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')
);In normal CLI you rarely need to touch this — the default PipeTransport is fine. Pick a
different one when:
- You run under Swoole — the
AdaptiveEnginealready switches toTempFileTransportautomatically (below); override only if you specifically wantShmTransport. - You want nothing on disk, ever — use
ShmTransport(RAM only, needsext-shmop). - Your temp dir is unusual (tiny tmpfs,
noexec, restrictedopen_basedir) — Pipe or Shm avoid the temp file.
Automatic selection under Swoole
The default AdaptiveEngine self-configures its transport. When no transport is passed, it
uses TempFileTransport under an active Swoole runtime — detected inside a coroutine
(\Swoole\Coroutine::getCid() !== -1) or with runtime hooks enabled
(\Swoole\Runtime::getHookFlags() !== 0) — and PipeTransport otherwise (including when
the Swoole extension is not loaded).
// Under an active Swoole runtime, this transparently uses TempFile:
$thread = new Thread(new MyTask());
$thread->start();Merely having the extension installed but dormant does not force TempFile — the switch keys on an active runtime. The full fd-leak cascade and the output-pipe caveat live in Swoole & payload delivery.
Mechanics to know
TempFileTransport— the child holds the file descriptor open; the directory entry is removed immediately after launch, so nothing lingers on disk.ShmTransport— stdin is/dev/null; the child reads the segment and deletes it, and the parent’scleanup()deletes any survivor if the child crashed before reading. Keys are unique perstart().- Parent-side cleanup is a fallback. A transport’s
cleanup()runs when the handle finishes to release a temp file or shm segment the child never consumed. It is always safe to call, even when the resource is already gone.
Writing your own transport
Implement PayloadTransport — stage / receive / cleanup — and bind it via an
engine. Mind the child side: the default AdaptiveRunner receives from STDIN (or shm
when --shmkey is present) and does not call a custom transport’s receive().
Delivering on the child’s stdin works transparently; an out-of-band channel (Redis, TCP, a
FIFO) needs a matching child runner shipped alongside it. See
Runner lifecycle.
Related
- Swoole & payload delivery — the fd-leak cascade and delivery mechanics
- API reference — full
Engine,PayloadTransport, and transport signatures - Framework integration — binding an engine under Swoole
- Security & performance —
0600channels, cost trade-offs