Skip to content

Fix Async\runtime_stats() abort when called before the scheduler starts#165

Merged
EdmondDantes merged 3 commits into
mainfrom
fix/runtime-stats-uninit-buffers
Jun 14, 2026
Merged

Fix Async\runtime_stats() abort when called before the scheduler starts#165
EdmondDantes merged 3 commits into
mainfrom
fix/runtime-stats-uninit-buffers

Conversation

@EdmondDantes

Copy link
Copy Markdown
Contributor

Fixes #164.

Async\runtime_stats() aborted on a debug build (and returned a bogus result on a release build) when called before the scheduler started — e.g. at the top level of a script, before the first spawn/await. The four global queue buffers (fiber_context_pool, microtasks, coroutine_queue, resumed_coroutines) are allocated lazily at scheduler launch; before that they are zero-initialized (capacity == 0), and circular_buffer_count() asserted head < capacity (0 < 0).

Fixed in depth, three layers:

  1. circular_buffer_count() returns 0 for an uninitialized buffer (capacity == 0) before the bounds asserts — protects every caller of the primitive.
  2. runtime_stats() clamps the queue counts with capacity > 0 ? count : 0, matching the capacity clamp already there (the count reads had been left out — that inconsistency was the bug).
  3. runtime_stats() short-circuits before the scheduler starts (ZEND_ASYNC_SCHEDULER == NULL), returning a zeroed snapshot with the same keys (static fiber_pool_min / fiber_stack_size still reported).

Adds tests/common/runtime_stats.phpt: at the top level it returns a sane zeroed array (the case that used to abort), and inside a coroutine it reports live values.

…alized buffer

A buffer with capacity 0 — a scheduler queue read before the scheduler has
allocated it — holds nothing, but circular_buffer_count() asserted
`head < capacity` (0 < 0) and aborted on a debug build. Treat capacity 0 as
empty and return 0 before the bounds asserts.

Foundational part of the fix for #164: protects every caller of the primitive.
…t safety)

The pool capacity was already clamped with `capacity > 0 ? ... : 0` against the
pre-scheduler state, but the four circular_buffer_count() reads were not — so
runtime_stats() read uninitialized buffers when called before the scheduler
started. Clamp the counts the same way, consistent with the capacity read.

Part of the fix for #164.
…on test

Short-circuit runtime_stats() when ZEND_ASYNC_SCHEDULER is NULL (the scheduler
has not launched yet, e.g. a top-level call before the first spawn) and return a
zeroed snapshot with the same keys, instead of reading the unallocated queue
buffers. Add tests/common/runtime_stats.phpt covering the top-level case (which
used to abort the process) and the in-coroutine case.

Completes the fix for #164.
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@EdmondDantes EdmondDantes merged commit 4ca0215 into main Jun 14, 2026
9 checks passed
@EdmondDantes EdmondDantes deleted the fix/runtime-stats-uninit-buffers branch June 14, 2026 16:21
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.

Async\runtime_stats() aborts (assert) when called outside a running scheduler

1 participant