Darkwood · PHP · Flow · RxJS · stream_select
The initial assumption
But what exactly was “async”?
Multiple Fibers do not automatically mean overlapping I/O.
Why study RxJS?
pipe(). Not to clone Observables.flowchart LR A[subscribe] --> B[values] B --> C[track work] C --> D[completion] D --> E[errors / cancel] E --> F[teardown]
Architectural mirror — not a port.
What RxJS really teaches
from(urls).pipe(
mergeMap(fetchImage, 4),
map(hashBinary),
retry(2),
finalize(cleanup),
);
pipe()Composes only
subscribe()Starts execution
mergeMap(..., 4)Bounds active work
finalize()Cleanup on terminal paths
Flow vocabulary
Instruction packet
Stage work
Admission control
Bounded concurrency
Schedule & wait
Completion barrier
The Fiber illusion
file_get_contents($url); // still blocks the process
Many Fibers scheduled
Non-blocking + stream_select
Blocking calls hidden inside Fibers
The missing stack
flowchart TB S[PHP streams] NB[non-blocking mode] SEL[stream_select] FIB[Fiber suspension] RES[readiness resume] DRV[Driver] FL[Flow] S --> NB --> SEL --> FIB --> RES --> DRV --> FL
Not a naming convention. A yield-on-readiness story.
The concrete experiment
php bin/console app:fetch-images --concurrency=4 --timeout=5
content/rxjs-flowWhat we shipped
$driver->waitReadable($stream, $timeout);
$driver->waitWritable($stream, $timeout);
Schedule · wait · resume · deadlines
HTTP / protocol / application
Admission & concurrency
Aggregate results
The Driver is not an HTTP client.
What concurrency=4 means
MaxIpStrategy(4)
+ non-blocking fetch job
+ StreamSelectDriver
→ ≤ 4 fetch packets admitted
→ ≤ 4 fetch Fibers active
→ ≤ 4 sockets waiting on stream_select
Errors · completion · cleanup
Collect per-item errors
(demo: soft DTO errors)
Fail fast via errorJob
await() → no queued packets, no active jobs, no pending waitsFlow’s version of teardown / finalize discipline.
What Flow keeps from RxJS
Start → track → complete → tear down
FlowFactory then await()
MaxIpStrategy + yielding jobs
Wait for active work
Every terminal path
+ observability
What Flow rejects
As public app types
JS method names in PHP
Not required for pipelines
Or a second demo engine
Where Flow goes next
Next acceptance test — not measured yet
For StreamSelectDriver waits
If collectors become painful
Beyond the teaching fetch Job
Closing
Resources
content/rxjs-flow — app:fetch-imagesdarkwood/flow — StreamSelectDriverSteal the guarantees. Keep jobs, packets, Drivers.