Skip to content

feat(#92): grouped future waits (awaitAll/onReady) and range read-ahead - #111

Merged
s2x merged 1 commit into
masterfrom
feat/92-awaitall-range-readahead
Sep 9, 2026
Merged

feat(#92): grouped future waits (awaitAll/onReady) and range read-ahead#111
s2x merged 1 commit into
masterfrom
feat/92-awaitall-range-readahead

Conversation

@s2x

@s2x s2x commented Sep 9, 2026

Copy link
Copy Markdown
Member

Closes #92

What

  1. Future::awaitAll(array $futures): array — awaits N futures as a group. All requests are already in flight, so total wait is driven by the slowest future (roughly one round trip) instead of the sum of N round trips. Implemented by polling the non-blocking fdb_future_is_ready() for every future at once (1 ms spin), then resolving results in input order — so errors still throw from await() in input order, and a failure in an early future never cancels the wait for the rest. Keys of the input array are preserved.

  2. Future::onReady(callable $fn): void — registers a completion hook that fires exactly once, just before the future's payload is read. Hooks always execute on the PHP thread that resolved the future — never on the FDB network thread. Hooks registered after resolution fire immediately; exceptions propagate to the caller.

  3. Range read-aheadRangeResult::paginate() now issues the request for page N+1 before yielding the current page's rows, so page N+1's network round trip overlaps with the consumer processing page N. getIterator() no longer awaits inside its fetcher closure — it hands the un-awaited future to the paginator. New Future\KvsFuture interface (await(): FutureKvResult) is implemented by both FutureKeyValueArray (real FDB future) and FutureKvResult (already-resolved value), so existing test stubs and custom fetchers keep working unchanged.

Deliberately deferred

The fdb_future_set_callback binding itself. The callback fires on the FDB network thread, where PHP code cannot safely run (the Zend engine is not thread-safe), and a correct design needs a C-level self-pipe/flag + PHP-side pump. Grouped waits + read-ahead recover most of the practical parallelism without it; the docs state this limit explicitly. The issue itself flagged this as the risky part and suggested landing the read-ahead first.

Tests

  • tests/Unit/FutureAwaitAllTest.php (new): compiles a stub C library (same technique as FutureUInt64Test) where each future becomes ready only after N polls, and records the global poll count at first await — proving all futures are resolved only after the slowest one is ready (grouped wait, not serialized). Also covers input-order/keyed results, already-resolved futures, and all onReady() semantics.
  • tests/Unit/RangeResultTest.php: new nextBatchIsPrefetchedBeforeTheCurrentBatchIsConsumed (2 fetches already made at the first yielded row; exactly 10 round trips for 10 pages) and limitIsNotExceededByPrefetching (prefetch never requests more than the remaining limit). The fake server now honors a positive limit like the real one.

Docs / changelog

  • docs/advanced.md: new section under "Future Objects" covering awaitAll(), onReady(), range read-ahead, and the limits of the current async model (no callback binding, no Fiber suspension yet).
  • CHANGELOG.md: [#92] entry under Added.

- Future::awaitAll() resolves N futures with one grouped wait: all requests
  are already in flight, so total latency is the slowest future instead of
  the sum of all of them. Results and errors are resolved in input order.
- Future::onReady() registers a completion hook that always fires on the
  PHP thread (never on the FDB network thread), once, just before the
  future's payload is read.
- RangeResult pagination now prefetches the next page before the consumer
  finishes the current one (page N+1's round trip overlaps page N), via the
  new KvsFuture abstraction; FutureKeyValueArray and FutureKvResult both
  implement it so existing fetchers keep working.
- The fdb_future_set_callback binding itself is deliberately deferred:
  PHP code cannot safely run on the FDB network thread, and grouped waits
  plus read-ahead recover most of the practical parallelism without it.
- Documented in docs/advanced.md; covered by FutureAwaitAllTest and new
  read-ahead tests in RangeResultTest.
@s2x
s2x merged commit df4e86e into master Sep 9, 2026
6 checks passed
@s2x
s2x deleted the feat/92-awaitall-range-readahead branch September 9, 2026 09:52
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.

Add non-blocking future support (fdb_future_set_callback, awaitAll, range read-ahead)

1 participant