Skip to content

refactor(registry): clear memos on mutation; drop per-resolve version stamp#347

Merged
lesnik512 merged 3 commits into
mainfrom
refactor/registry-invalidate-on-mutation
Jul 18, 2026
Merged

refactor(registry): clear memos on mutation; drop per-resolve version stamp#347
lesnik512 merged 3 commits into
mainfrom
refactor/registry-invalidate-on-mutation

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Follow-up to the dropped C2 memo-swap (#346): instead of adding a bypass layer, remove per-resolve work. Simplification-first — the hot-path perf win is a reported bonus, not gated.

What changes

ProvidersRegistry's resolver/plan memos (_resolvers/_plans) were (version, obj) tuples checked against a monotonic _version counter on every lookup. They become plain dicts cleared on mutation via a _invalidate() helper (register/add_providers/_remove_providers). Validation freshness folds from the counter to a _validated: bool. The internal version property / validated_version / _version are deleted.

The resolve hot path (resolver_for, per resolve and per dependency edge) drops the per-lookup tuple-unpack + int-compare. The invariant collapses to: a memo is valid until the registry changes; changing it clears the memos.

Licensed by the explicit lifecycle contract (from #346): the registry mutates only during single-threaded configure, so clear() never races the concurrent resolve path in a contract-abiding program — and clear()'s invalidation breadth is identical to the old version bump.

Not a behavior change

is_validated() semantics preserved; add_providers→rebuild preserved (the two test_add_providers_rebuilds_stale_wiring_plan_* tests pass unchanged). version is treated as an internal, non-public detail (no consumers in source/tests/integration-kit/user-docs) — removed without a deprecation cycle.

Measured (bonus, no kill-gate)

Guard g1 −4%, g2 (cached singleton) −9%/−12%; comparative median C1 −7.6%, C2 −4.8%, C3 −3.3%; C4 flat (construction-dominated). No regression beyond noise.

Full rationale + free-threaded soundness argument in the change bundle. Architecture docs (resolution/providers/containers/validation/concurrency) promoted in the same PR.

🤖 Generated with Claude Code

lesnik512 and others added 3 commits July 18, 2026 22:04
…n (A2)

Replace ProvidersRegistry's per-lookup version-stamp on the resolver/plan memos
with clear-on-mutation, and fold validation freshness to a _validated bool —
dropping the per-resolve version compare from the hot path. Simplification-first
(no perf kill-gate; perf is a reported bonus), licensed by the explicit
build->resolve->close lifecycle contract. Deletes the internal version/
validated_version machinery (treated as non-public).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… stamp

_plans/_resolvers become plain dicts cleared by register/add_providers/remove
(via _invalidate); the per-lookup version compare leaves the resolve hot path.
Validation freshness folds from the _version/validated_version counter to a
_validated bool. Deletes the internal version property/counter (non-public).
Same invalidation breadth as the old version bump; sound because mutation is a
single-threaded configure-phase operation per the lifecycle contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Updates resolution/providers/containers/validation/concurrency capability docs
off version-stamping onto clear-on-mutation + the _validated flag, records the
sanctioned free-threaded trade in concurrency.md, fixes one stale test comment,
and finalizes the change summary with the measured (bonus) perf delta.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 891231d Previous: c16b80a Ratio
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 435700.78326729283 iter/sec (stddev: 0.00004462224328092711) 433935.3954375655 iter/sec (stddev: 0.00005460575872291772) 1.00
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle 65224.830496207855 iter/sec (stddev: 0.000012609462792397555) 63693.167227274425 iter/sec (stddev: 0.0000131713654431779) 0.98
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 1242427.4849290976 iter/sec (stddev: 3.910800928945715e-7) 1163016.5186574862 iter/sec (stddev: 4.0302918185877904e-7) 0.94
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 2371543.497703308 iter/sec (stddev: 6.491227467209881e-8) 2176586.2148827524 iter/sec (stddev: 7.792856151735141e-8) 0.92
benchmarks/test_guard_resolve.py::test_g3_deep_chain 518832.5108892471 iter/sec (stddev: 5.922256330265779e-7) 502324.995757382 iter/sec (stddev: 5.815581860954582e-7) 0.97
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 316904.1776352059 iter/sec (stddev: 6.097230802332533e-7) 310669.6238494726 iter/sec (stddev: 6.455572929657008e-7) 0.98
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1102952.4181309878 iter/sec (stddev: 3.534874420146444e-7) 1012348.7177749319 iter/sec (stddev: 4.0550288620064454e-7) 0.92

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit afe7406 into main Jul 18, 2026
9 checks passed
@lesnik512
lesnik512 deleted the refactor/registry-invalidate-on-mutation branch July 18, 2026 19:57
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