Darkwood · PHP · Flow · RxJS · stream_select

The Fiber
Illusion

What RxJS taught us about
real async pipelines in PHP

Fibers
≠
Overlapping I/O
@matyo91

The initial assumption

Flow already looked asynchronous.

Fibers
Drivers
MaxIpStrategy
await()

But what exactly was “async”?

Multiple Fibers do not automatically mean overlapping I/O.

01 | assumption @matyo91

Why study RxJS?

Not for pipe(). Not to clone Observables.

Interesting because of the execution lifecycle.

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.

02 | why-rxjs @matyo91

What RxJS really teaches

Lifecycle under the operators

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

03 | rxjs-pipeline @matyo91

Flow vocabulary

Packets through stages

Fetch
→
Hash
→
Save
→
Collector

Ip

Instruction packet

Job

Stage work

IpStrategy

Admission control

MaxIpStrategy

Bounded concurrency

Driver

Schedule & wait

await()

Completion barrier

04 | flow-model @matyo91

The Fiber illusion

A Fiber can suspend PHP.
It cannot unblock a socket.

file_get_contents($url); // still blocks the process

Concurrent PHP

Many Fibers scheduled

Overlapping I/O

Non-blocking + stream_select

Fake async

Blocking calls hidden inside Fibers

05 | fiber-illusion @matyo91

The missing stack

Async is a runtime contract

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.

06 | stack @matyo91

The concrete experiment

Real sockets. Real concurrency.

URLs
→
fetch
→
checksum
→
save
→
summary
php bin/console app:fetch-images --concurrency=4 --timeout=5
Local fixture server
One deliberate 404
Per-item success / failure
content/rxjs-flow
07 | experiment @matyo91

What we shipped

StreamSelectDriver

$driver->waitReadable($stream, $timeout);
$driver->waitWritable($stream, $timeout);

Driver

Schedule · wait · resume · deadlines

Job

HTTP / protocol / application

IpStrategy

Admission & concurrency

Collector

Aggregate results

The Driver is not an HTTP client.

08 | stream-select-driver @matyo91

What concurrency=4 means

A concurrency limit is only meaningful
if the runtime can say exactly what it limits.

Queued packets
Admitted jobs
Active Fibers
Open sockets
In-flight requests
MaxIpStrategy(4)
+ non-blocking fetch job
+ StreamSelectDriver
→ ≤ 4 fetch packets admitted
→ ≤ 4 fetch Fibers active
→ ≤ 4 sockets waiting on stream_select
09 | concurrency @matyo91

Errors · completion · cleanup

Teardown is part of the run

Batch

Collect per-item errors
(demo: soft DTO errors)

Critical

Fail fast via errorJob

  • await() → no queued packets, no active jobs, no pending waits
  • Cleanup on success, error, timeout, cancellation

Flow’s version of teardown / finalize discipline.

10 | teardown @matyo91

What Flow keeps from RxJS

Steal the guarantees

Lifecycle discipline

Start → track → complete → tear down

Lazy compose / explicit run

FlowFactory then await()

Bounded concurrency

MaxIpStrategy + yielding jobs

Deterministic completion

Wait for active work

Cleanup

Every terminal path

Per-run isolation

+ observability

11 | keep @matyo91

What Flow rejects

Steal the guarantees,
not the API

Observable / Observer / Subscriber

As public app types

Subjects · operator zoo

JS method names in PHP

Custom Promise type

Not required for pipelines

HTTP inside Drivers

Or a second demo engine

12 | reject @matyo91

Where Flow goes next

Prove the waits further

01 · Multi-connection benchmark

Next acceptance test — not measured yet

02 · Cancellation tokens

For StreamSelectDriver waits

03 · Richer result API

If collectors become painful

04 · Production examples

Beyond the teaching fetch Job

13 | next @matyo91

Closing

When a job waits on I/O,
does the runtime actually wait with it—

or does it only pretend?

Jobs yield
→
Drivers wait
→
I/O overlaps
@matyo91

Resources

Go deeper

  • Article — The Fiber Illusion
  • content/rxjs-flow — app:fetch-images
  • darkwood/flow — StreamSelectDriver
  • RxJS 8.0.0-alpha.14 — lifecycle study source

Steal the guarantees. Keep jobs, packets, Drivers.

14 | resources @matyo91