Resolve the config section once per type (P5) - #26
Merged
Conversation
ConfigurationBinder called _configuration.GetSection(...) on every property, though the section is constant per settings type. Cache the resolved IConfigurationSection per section name in a ConcurrentDictionary, using a zero-capture GetOrAdd (static factory + factoryArgument) so no per-call delegate is allocated. Reload-safe: GetSection returns a live view over the configuration root, so the cached section re-reads providers on each access. Drop the dead ?. (GetSection never returns null) and strip a stray BOM. Chosen over threading the section through the ISectionBinder contract: that would be a layering violation (Core must not reference Microsoft.Extensions.Configuration) and the optimization is single-implementer. Plan reviewed by the architect/perf/security agents; code reviewed via /code-review. Proof (new gated ConfigBinderBenchmark): BindNoRoot 80->40 B (-50%), BindWithRoot 144->56 B (-61%). Adds 3 tests (multi-property with/without RootSection, plus a cached-section-reflects-later-change live-view test). Also wires P4's ConvertArrayBenchmark into the CI filter (it was never gated) and adds Microsoft.Extensions.Configuration to the benchmark project. Refreshes SESSION-HANDOFF.md + FIX-PLAN.md and logs a pre-existing secret-leak finding surfaced by the security review (S1: Resources.cs interpolates the raw bound value into the SettingsPropertyValueException message). Suite: 71 tests per TFM.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P5 — resolve the config section once per type
ConfigurationBinder.BindPropertySettingscalled_configuration.GetSection(...)on every property, though the section is constant per settings type. This caches the resolvedIConfigurationSectionper section name and reuses it.The change
ConfigurationBindergains aprivate readonly ConcurrentDictionary<string, IConfigurationSection>;BindPropertySettingsresolves via a zero-captureGetOrAdd(context.Section, static (name, self) => self.ResolveSection(name), this)— a capturing lambda would allocate a fresh 64 B delegate per call (measured).GetSectionreturns a live view over the configuration root, so the cached section re-reads the providers on each access (locked by a test).?.(GetSectionis contractually non-null); stripped a stray UTF-8 BOM.Why an internal cache, not a contract change
The plan was reviewed by architect / perf / security specialists up front. Threading a resolved section through the public
ISectionBinder/BindingContextcontract would be a layering violation — Core must not referenceMicrosoft.Extensions.Configuration— and the optimization is single-implementer (env / command-line / in-memory binders are flat(section, key)lookups). So the cache lives on the binder.Proof
New gated
ConfigBinderBenchmark(ShortRun, net10):BindNoRootBindWithRootMatches the perf review's predicted −40 B / −88 B deltas. The residual is the irreducible
"Section:Key"path string (provider dictionaries are string-keyed; values can't be cached since config is reload-live).Tests
+3 in
ConfigurationBinderCacheTests.cs: multi-property (with + withoutRootSection) proving every property resolves from the cached section, andBind_CachedSection_ReflectsLaterConfigChangeproving the cache stays a live view. Suite: 71 per TFM.Also in this PR
ConvertArrayBenchmarkinto the CI filter — it existed but was never actually gated.Microsoft.Extensions.Configurationto the benchmark project.Resources.cs:34-36interpolates the raw bound value intoSettingsPropertyValueException, which can leak secrets into logs — a separate follow-up, not addressed here.Review
Plan reviewed by architect / perf / security; code reviewed via
/code-review(clean).Like other new benchmarks,
ConfigBinderBenchmarkhas nogh-pagesbaseline until the first master run, so this PR isn't gated on it; before/after captured above.