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 the launcher.
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 launcher never has to serialize the transport choice.
Selecting a transport
A transport is set on a CliLauncher, which is bound process-wide once at bootstrap with
Thread::bindLauncher(). There is no per-Thread setting.
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
// Self-configuring launcher, override just the transport:
Thread::bindLauncher(CliLauncher::adaptive(transport: new TempFileTransport()));Or build the launcher outright, setting every part:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\ShmTransport;
Thread::bindLauncher(new CliLauncher(
binaryPath: PHP_BINARY,
runnerPath: '/path/to/wRunner',
transport: new ShmTransport(),
));This choice does not apply under Swoole
The transport belongs to CliLauncher — the CLI and FPM path. With an active Swoole runtime the
launch goes to SwooleLauncher, which picks delivery itself (shared memory with ext-shmop, a
temp file otherwise) and takes no transport from outside.
In normal CLI you rarely need to touch this — the default PipeTransport is fine. Pick a
different one when:
- You run under Swoole — the choice is already made for you (see the callout above and the section below); there is no transport to set here.
- 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
CliLauncher resolves the transport itself when one is not given. Left null, 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
Launcher,PayloadTransport, and transport signatures - Framework integration — binding an engine under Swoole
- Security & performance —
0600channels, cost trade-offs