Thanks to visit codestin.com
Credit goes to github.com

Skip to content

perf(dotnet/policy): cache YamlDotNet IDeserializer in Policy.FromYaml#2155

Merged
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/dotnet-policy-cache-yaml-deserializer
May 12, 2026
Merged

perf(dotnet/policy): cache YamlDotNet IDeserializer in Policy.FromYaml#2155
imran-siddique merged 1 commit into
microsoft:mainfrom
aegis-initiative:fix/dotnet-policy-cache-yaml-deserializer

Conversation

@finnoybu

Copy link
Copy Markdown
Contributor

Summary

Policy.FromYaml rebuilds a fresh IDeserializer on every call:

var deserializer = new DeserializerBuilder()
    .WithNamingConvention(UnderscoredNamingConvention.Instance)
    .IgnoreUnmatchedProperties()
    .Build();

DeserializerBuilder.Build() walks every public property on the target type (Policy.PolicyDocument and Policy.RuleDocument), resolves the naming convention, and wires up type inspectors and converters. That cost is identical on every call, and YamlDotNet documents the resulting IDeserializer as thread-safe once built -- so building it per call is pure waste. Loading a handful of policies at startup, or hot-reloading them from disk, pays the construction cost N times for no benefit.

Change

Promote the builder result to a static readonly IDeserializer field so every FromYaml / FromYamlFile call reuses the same configured deserializer. This mirrors the existing JsonSerializerOptions caching right above it -- the JSON path already does the equivalent.

Tests

Added FromYaml_CachedDeserializer_ProducesStableResultsAcrossCalls: parses three different policy documents sequentially and in parallel across multiple iterations, and asserts that field-for-field results stay stable across calls. Guards against future refactors that would accidentally introduce per-call state on the shared instance.

Test plan

  • dotnet test -- 658 / 658 passing (previous baseline 657 + 1 new)
  • Existing FromYaml_* tests unchanged

Surfaced during independent audit conducted by @finnoybu (Ken Tannenbaum, AEGIS Initiative); [LOW, .NET].

DeserializerBuilder.Build() walks every public property on the target
type (Policy.PolicyDocument and Policy.RuleDocument), resolves naming
conventions, and wires up type inspectors and converters. That cost is
identical on every call and the resulting IDeserializer is documented as
thread-safe once built, so building one per call is pure waste --
loading a few policies at startup, or hot-reloading them from disk,
pays the construction cost N times for no benefit.

Promote the builder result to a static-readonly field so every
FromYaml / FromYamlFile call reuses the same configured deserializer.
This mirrors the existing JsonSerializerOptions caching right above
it (PolicyDocument's JSON path already does the equivalent).

The new regression test parses three different policy documents
sequentially and in parallel across multiple iterations, pinning that
the cached deserializer's results stay stable across calls -- guarding
against future refactors that would (accidentally) introduce per-call
state on the shared instance.
@github-actions github-actions Bot added the tests label May 12, 2026
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `Policy.cs`

Policy.cs

  • test_name -- Validate that FromYaml correctly handles invalid YAML input.
  • test_name -- Ensure FromYaml throws an exception for empty YAML strings.
  • test_name -- Test FromYaml with YAML that has unexpected fields to confirm it ignores them properly.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — View details

TL;DR: 0 blockers, 0 warnings. No issues found. Clean change.

# Sev Issue Where

Action items: None.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

No security issues found.

@github-actions github-actions Bot added the size/M Medium PR (< 200 lines) label May 12, 2026
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

API Compatibility

Severity Change Impact
Potential The FromYaml method now uses a static cached instance of IDeserializer instead of creating a new instance on each call. This change may affect any existing code that relies on the state of the deserializer being fresh for each call, potentially leading to unexpected behavior if the deserializer's state is altered in future implementations.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

Docs Sync

  • FromYaml in Policy.cs -- missing docstring
  • README.md -- section on YAML policy loading needs update
  • CHANGELOG.md -- missing entry for behavioral change regarding caching of IDeserializer in FromYaml

@github-actions

Copy link
Copy Markdown

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential NONE
Overall MEDIUM

Automated check by AGT Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label May 12, 2026
@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ✅ Passed No issues found
🛡️ Security Scan ✅ Passed No issues found
🔄 Breaking Changes ✅ Completed Analysis complete
📝 Docs Sync ✅ Completed Analysis complete
🧪 Test Coverage ✅ Completed Analysis complete

Verdict: ✅ Ready for human review

@imran-siddique imran-siddique merged commit 2e69fac into microsoft:main May 12, 2026
13 of 14 checks passed
MohammadHaroonAbuomar pushed a commit to MohammadHaroonAbuomar/agt-acs that referenced this pull request Jun 1, 2026
microsoft#2155)

DeserializerBuilder.Build() walks every public property on the target
type (Policy.PolicyDocument and Policy.RuleDocument), resolves naming
conventions, and wires up type inspectors and converters. That cost is
identical on every call and the resulting IDeserializer is documented as
thread-safe once built, so building one per call is pure waste --
loading a few policies at startup, or hot-reloading them from disk,
pays the construction cost N times for no benefit.

Promote the builder result to a static-readonly field so every
FromYaml / FromYamlFile call reuses the same configured deserializer.
This mirrors the existing JsonSerializerOptions caching right above
it (PolicyDocument's JSON path already does the equivalent).

The new regression test parses three different policy documents
sequentially and in parallel across multiple iterations, pinning that
the cached deserializer's results stay stable across calls -- guarding
against future refactors that would (accidentally) introduce per-call
state on the shared instance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk size/M Medium PR (< 200 lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants