Skip to content

Optimize Container and Telescope coroutine state - #556

Merged
binaryfire merged 4 commits into
0.4from
feature/container-context-bag
Sep 2, 2026
Merged

Optimize Container and Telescope coroutine state#556
binaryfire merged 4 commits into
0.4from
feature/container-context-bag

Conversation

@binaryfire

@binaryfire binaryfire commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Container resolution kept depth, build-stack, resolving-stack, and parameter-override state in four coroutine-context values. Stack updates repeatedly read and rewrote arrays through CoroutineContext, adding measurable work to every transient build, nested resolution, parameter override, and method injection.

Telescope used the same pattern for its recording queues, with array_merge() rebuilding the complete queue for every entry. Its recording guard and deferred-store marker could also cross into a child coroutine even though the child must own its recording lifecycle.

This change gives each subsystem one coroutine-owned state object:

  • ContainerResolutionState holds all mutable state for one resolution chain. It is cloned when coroutine context is copied, so parent and child stacks remain independent.
  • Container stack operations mutate that state directly. Idle inspection, cached singleton hits, and anonymous closure calls avoid allocating it when resolution bookkeeping is unnecessary.
  • Constructor recipes read parameter overrides once, and empty contextual-binding registries return before reading the build stack.
  • RecordingState holds Telescope's queues, recursion guard, and deferred-store marker. It is non-copyable, so a child begins with its own recording lifecycle while retaining the separately managed batch ID.
  • Telescope now appends to its queues directly instead of copying the growing arrays.
  • ReplicableContext now documents that each implementation chooses whether copied context inherits, projects, or resets its state. Existing implementations document their concrete choice.

Container extension point

Hypervel no longer exposes Laravel's protected per-parameter override helpers. Container subclasses that customize parameter resolution should override resolveRecipeParameters(), which receives the current ContainerResolutionState and resolves the complete parameter list in one pass. This deliberate difference is documented in the Container README.

All named Container APIs and their behavior remain unchanged.

Benchmarks

Measurements compare 0.4 with this branch on the same machine. Results are medians from alternating baseline and candidate samples with OPcache disabled. The singleton-hit control remains flat, showing that the improvement comes from resolution bookkeeping rather than unrelated cache behavior.

Scenario Execution 0.4 This branch Change
Build transient Outside coroutine 877.49 ns 424.41 ns -51.6%
Build transient Inside coroutine 906.29 ns 396.89 ns -56.2%
Singleton hit Outside coroutine 243.41 ns 245.59 ns noise
Singleton hit Inside coroutine 260.99 ns 255.90 ns noise
Make transient Outside coroutine 4,455.17 ns 2,388.77 ns -46.4%
Make transient Inside coroutine 5,352.63 ns 3,260.84 ns -39.1%
Bound closure Outside coroutine 3,971.58 ns 1,658.78 ns -58.2%
Bound closure Inside coroutine 4,077.94 ns 2,000.79 ns -50.9%
Nested resolution, 5 levels Outside coroutine 30.334 us 16.719 us -44.9%
Nested resolution, 5 levels Inside coroutine 36.240 us 21.578 us -40.5%
makeWith() primitive Outside coroutine 3.867 us 2.283 us -41.0%
makeWith() primitive Inside coroutine 3.847 us 2.223 us -42.2%
buildWith() primitive Outside coroutine 2.238 us 0.970 us -56.7%
buildWith() primitive Inside coroutine 2.217 us 0.986 us -55.5%
Method injection Outside coroutine 24.730 us 14.043 us -43.2%
Method injection Inside coroutine 29.218 us 17.388 us -40.5%

OPcache-enabled runs confirmed the same direction, with measured resolution-path improvements between 38.3% and 65.7% and a flat singleton control.

The final anonymous-closure and empty-contextual-registry guards were measured separately against the state-object implementation before those guards:

Scenario Before guard Final Change
Parameterized anonymous call, supplied scalar 1,216.44 ns 1,189.11 ns -2.2%
Parameterized anonymous call, defaulted scalar 1,448.87 ns 1,362.16 ns -6.0%
Parameterized anonymous call, injected class 1,862.47 ns 1,795.27 ns -3.6%
Empty registry, defaulted primitive constructor 4,952.65 ns 4,715.73 ns -4.8%
Empty registry, defaulted class constructor 5,088.01 ns 4,798.04 ns -5.7%
Empty registry, variadic class constructor 5,281.12 ns 5,136.19 ns -2.7%

The state object adds a small bounded amount of memory while a resolving coroutine remains active:

Measurement 0.4 This branch Difference
Active memory across 2,000 suspended coroutines 21,116,440 B 21,568,248 B +451,808 B
Per active coroutine 10,558.22 B 10,784.12 B +225.90 B (+2.1%)
Retained after exit 76,440 B 112,248 B +35,808 B

Telescope queue recording changes from repeated full-array copies to direct append:

Entries Previous queue append Mutable state append Change
10 5.96 us 1.30 us -78.2%
100 65.52 us 10.96 us -83.3%
500 455.96 us 54.43 us -88.1%
1,000 1,285.88 us 121.25 us -90.6%

Verification

  • Ran composer fix, including formatting, both PHPStan configurations, parallel tests, Testbench, and dogfood tests.
  • Ran the focused Container, Context, Telescope, Inertia, and Translation suites.
  • Covered state-free Container paths, nested cleanup, copied-context isolation, Telescope child queue ownership, independent deferred storage, recursion-guard isolation, and inherited batch identity.
  • Checked the final diff for stale context keys, removed helper references, formatting issues, and unintended API changes.

Summary by CodeRabbit

  • Bug Fixes

    • Improved coroutine isolation for dependency resolution state, preventing child coroutine changes from affecting the parent.
    • Anonymous closure calls no longer create unnecessary resolution state.
    • Improved Telescope recording isolation so forked coroutines maintain independent queues and deferred storage behavior.
  • Documentation

    • Clarified guidance for customizing container parameter resolution.
    • Updated documentation describing how coroutine context state is copied and replicated.

Describe ReplicableContext as the hook that chooses what is installed in a copied coroutine context, rather than requiring every implementation to preserve all state. This covers full inheritance, projected state, and deliberate reset behavior without changing the API.

Document the concrete copy behavior on InertiaState and MissingTranslationGroups so each first-party implementation states exactly what crosses the coroutine boundary.
Replace the separate coroutine-local depth, build-stack, resolving-stack, and parameter-override values with one ContainerResolutionState object. Mutate its bounded stacks in place, clone it when coroutine context is copied, and keep parent and child resolution chains independent.

Avoid allocating resolution state for idle inspection, cached singleton hits, and parameterized anonymous closure calls. Skip contextual lookup work when no contextual bindings exist, and resolve parameter overrides from the current state once per constructor recipe.

Remove the protected per-parameter override helpers in favor of the complete resolveRecipeParameters() extension point. Add focused coverage for state-free paths, depth cleanup, and copied-context isolation.
Group entry queues, update queues, the recursion guard, and deferred-store scheduling in a coroutine-owned RecordingState. Mark the state non-copyable so child coroutines begin with independent queues and schedule their own store while still inheriting the intended batch identity.

Append entries and updates directly in constant time instead of rebuilding each queue for every record. Keep empty reads and flushes allocation-free, and remove propagation and request-context filtering tied to the retired scalar Container and Telescope keys.

Cover forked and explicitly created children, independent deferred storage, recursion-guard isolation, shared batch identity, and the existing provider behavior.
Record the intentional difference from Laravel's protected per-parameter override helpers. Direct Container subclasses to resolve the complete constructor recipe through resolveRecipeParameters(), which receives the active coroutine resolution state and avoids repeated context reads.
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: d461938a-9552-48da-ac0b-0b61195c2a57

📥 Commits

Reviewing files that changed from the base of the PR and between 60d9b6f and be21e87.

📒 Files selected for processing (16)
  • src/container/README.md
  • src/container/src/Container.php
  • src/container/src/ContainerResolutionState.php
  • src/context/src/ReplicableContext.php
  • src/inertia/src/InertiaState.php
  • src/telescope/src/RecordingState.php
  • src/telescope/src/Telescope.php
  • src/telescope/src/TelescopeServiceProvider.php
  • src/telescope/src/Watchers/RequestWatcher.php
  • src/translation/src/MissingTranslationGroups.php
  • tests/Container/ContainerCallTest.php
  • tests/Container/ContainerTest.php
  • tests/Container/CoroutineSafetyTest.php
  • tests/Telescope/FeatureTestCase.php
  • tests/Telescope/Telescope/TelescopeTest.php
  • tests/Telescope/TelescopeServiceProviderTest.php
💤 Files with no reviewable changes (2)
  • src/telescope/src/TelescopeServiceProvider.php
  • src/telescope/src/Watchers/RequestWatcher.php

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The container now uses one replicated coroutine-local resolution state. Telescope now uses one coroutine-local recording state. Tests cover closure calls, cached resolution, depth restoration, fork isolation, recording queues, deferred storage, and entry guards.

Changes

Container resolution state

Layer / File(s) Summary
Resolution state contract
src/container/src/ContainerResolutionState.php, src/context/src/ReplicableContext.php, src/inertia/src/InertiaState.php, src/translation/src/MissingTranslationGroups.php
The container adds a replicated state object for resolution data. Context replication documentation now describes inherited and snapshot state behavior.
Resolution and parameter resolution
src/container/src/Container.php
Container depth, build stacks, resolving stacks, and parameter overrides use one coroutine-context state object. Parameter overrides are read once during recipe parameter resolution.
Callable behavior and state validation
src/container/src/Container.php, src/container/README.md, tests/Container/*
Anonymous closures bypass resolution-state creation. Container tests cover state creation, cleanup, depth restoration, and fork isolation. The README documents the parameter-resolution extension point.

Telescope recording state

Layer / File(s) Summary
Recording state and deferred storage
src/telescope/src/RecordingState.php, src/telescope/src/Telescope.php
Telescope stores entries, updates, recursion guards, and deferred-store state in one coroutine-context object.
Coroutine recording isolation
src/telescope/src/TelescopeServiceProvider.php, tests/Telescope/*
The coroutine recording-control key is renamed. Tests verify independent child queues, deferred batches, entry guards, and shared batch identifiers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to be21e

This change consolidates coroutine-local Container and Telescope state while preserving cleanup, child isolation, queue ordering, and dependency-resolution behavior. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Container
  participant ContainerResolutionState
  participant BoundMethod
  Container->>ContainerResolutionState: create or read resolution state
  Container->>ContainerResolutionState: track resolution stacks and depth
  Container->>BoundMethod: call anonymous closure directly
  Container->>ContainerResolutionState: resolve parameters
  Container->>ContainerResolutionState: restore state in finally block
Loading
sequenceDiagram
  participant ParentCoroutine
  participant ChildCoroutine
  participant Telescope
  participant RecordingState
  participant EntriesRepository
  ParentCoroutine->>Telescope: record parent entry
  Telescope->>RecordingState: append parent entry
  ParentCoroutine->>ChildCoroutine: fork coroutine
  ChildCoroutine->>Telescope: record child entry
  Telescope->>RecordingState: append child entry
  Telescope->>EntriesRepository: store separate batches
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 13 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary changes: optimizing coroutine state handling in both the Container and Telescope subsystems.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 48.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 13 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/container-context-bag

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR consolidates Container and Telescope coroutine bookkeeping into mutable, coroutine-owned state objects to reduce repeated context-array copying.

  • Container resolution state is independently replicated into copied coroutine contexts.
  • Telescope recording state is omitted from copied contexts so child coroutines own their recording lifecycle.
  • Container resolution and Telescope queue operations now mutate their state objects directly.
  • Documentation and tests clarify replication semantics, extension points, cleanup, and child-coroutine isolation.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking defects established.

The consolidated state objects follow the repository’s coroutine copy semantics, and the changed resolution and recording paths retain balanced cleanup and explicit parent-child ownership.

Important Files Changed

Filename Overview
src/container/src/Container.php Consolidates resolution bookkeeping, preserves balanced cleanup through finally blocks, and introduces the documented whole-recipe parameter-resolution extension point.
src/container/src/ContainerResolutionState.php Adds independently replicable mutable state for depth, build stacks, resolving stacks, and parameter overrides.
src/telescope/src/Telescope.php Replaces copied context queues and flags with direct mutation of coroutine-owned recording state.
src/telescope/src/RecordingState.php Adds non-copyable queue and lifecycle state so copied child contexts start with independent Telescope recording ownership.
src/telescope/src/TelescopeServiceProvider.php Retains selected recording and batch context inheritance while allowing non-copyable recording state to reset.
src/context/src/ReplicableContext.php Clarifies that implementations define whether replication inherits, projects, or resets state.
src/telescope/src/Watchers/RequestWatcher.php Removes filtering for the obsolete standalone container depth context key.
tests/Container/CoroutineSafetyTest.php Adds coverage for copied-context resolution-state isolation and cleanup behavior.
tests/Telescope/Telescope/TelescopeTest.php Expands coverage for child queue ownership, deferred storage, recursion guards, and inherited batch identity.

Reviews (1): Last reviewed commit: "Document the container parameter extensi..." | Re-trigger Greptile

@binaryfire

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@binaryfire
binaryfire merged commit 56bdabc into 0.4 Sep 2, 2026
38 of 39 checks passed
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