Skip to content

Replace Events polling with notifications - #392

Open
realFlowControl wants to merge 10 commits into
developfrom
florian/notification-driven-events-v2
Open

Replace Events polling with notifications#392
realFlowControl wants to merge 10 commits into
developfrom
florian/notification-driven-events-v2

Conversation

@realFlowControl

@realFlowControl realFlowControl commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Events::poll() currently waits by repeatedly checking targets and calling usleep(1) every tenth unsuccessful attempt. A user waiting two seconds for a Channel or Future still spends significant CPU time polling. This change implements the pipe-backed wakeup approach suggested in this discussion, allowing the kernel to suspend the polling thread until a target changes state.

Summary

  • add lazy pipe-backed readiness notifications for Channels and Futures
  • wait for notification with select() on POSIX
  • retain the existing busy-polling on Windows and as a fallback
  • represent readiness as a level: one byte means ready, an empty pipe means not ready
  • use separate Channel notifications for readable and writable state

How it works

Each Channel link owns separate readable and writable notification objects, while each Future monitor owns one notification for its READY state. A notification lazily creates a non-blocking, close-on-exec pipe when Events::poll() first needs to wait for that target. Creating pipes lazily avoids consuming file descriptors for targets that are already ready, are removed before polling, are only used by non-blocking Events, or never require a wait.

Readiness changes are synchronized while holding the target's existing mutex. When a target changes from not ready to ready, one dummy byte is written to its notification pipe. When a Channel becomes unready again, that byte is drained. Repeated updates while the readiness level is unchanged do not add more bytes.

Events::poll() first scans the actual Channel and Future states. If nothing is ready, it collects their notification descriptors and blocks in select(). After select() wakes, it scans the actual states again under their locks and returns one event. The descriptors are only wakeup hints.

Future readiness is terminal, so its notification remains raised after completion. This allows multiple independent Events instances to observe the same completed Future. Channel readability is lowered when its value is consumed, ensuring one channel value is delivered to one consumer. Closed Channels remain raised so all observers can see the close state.

Performance check

I ran a small demonstration with eight Channels. A Runtime sleeps for two seconds and then sends a value to one Channel while the main thread measures wall and process CPU time around Events::poll(). Both versions were built locally against the same PHP 8.4 ZTS build on Darwin arm64 and run once.

Build Wall time CPU time CPU / wall
develop (6447fd2) 2.005s 1.000s 49.9%
this PR 2.004s <0.001s 0.0%

This is intentionally a small, single-run demonstration, but it exercises the exact idle-wait behavior being changed. develop does not consume a full two CPU-seconds because its polling loop periodically calls usleep(1), but it still uses approximately half of one core while waiting. The notification path spends the same two seconds blocked in select() and uses effectively no CPU.

Limitations

Native waiting currently uses POSIX pipes and select(). The existing polling behavior remains as a fallback on Windows, when pipe creation fails, when a descriptor is outside FD_SETSIZE, or when select() fails.
When Events::setBlocker() is configured, poll() continues invoking that callback while no target is ready instead of entering select(), preserving the callback’s existing waiting and interruption semantics.

@realFlowControl realFlowControl changed the title Florian/notification driven events v2 WIP Aug 4, 2026
@realFlowControl realFlowControl changed the title WIP Replace Events polling with notifications Aug 4, 2026
@realFlowControl realFlowControl changed the title Replace Events polling with notifications Replace Events polling with notifications Aug 4, 2026
@realFlowControl
realFlowControl force-pushed the florian/notification-driven-events-v2 branch from 2dac736 to eca6fca Compare August 4, 2026 17:03
@realFlowControl
realFlowControl marked this pull request as ready for review August 5, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant