[cDAC] Add pluggable cache scopes to Target#129604
Draft
max-charlamb wants to merge 1 commit into
Draft
Conversation
Introduces a small abstraction for caching reads from a cDAC Target during focused operations such as a heap walk. - ITargetReadCache (new, in Abstractions): single ReadBuffer(addr, dest, fallback) + Invalidate, IDisposable. Implementations decide whether to satisfy a read from cached state or fall through via the supplied RawReadDelegate. - Target.BeginCacheScope(ITargetReadCache cache) : IDisposable (virtual): opens a scope during which Target reads route through the cache. Default impl on Target keeps the no-cache behavior used by test stubs and still invalidates + disposes the cache on scope end so the contract is consistent. - LinearReadCache (new, public): page-buffered ITargetReadCache. Reads inside a single page are served from a cached buffer; misses, larger-than-page reads, and page-straddling reads fall through. - ContractDescriptorTarget: implements BeginCacheScope; rejects nested scopes; routes TryReadBuffer through the active cache; consolidates typed reads (TryRead<T>, ReadPointer/TryReadPointer, ReadNUInt/ReadNInt) to funnel through TryReadBuffer so they benefit from caching automatically. The bootstrap-time TryReadContractDescriptor helpers continue to read directly. - HeapWalk (Legacy/Dbi): drops its private LinearReadCache, opens a single cache scope spanning the entire walk, and uses Target's typed-read APIs. Tests: LinearReadCacheTests (page hit/miss, cross-page, oversized, invalidate, page-fault fallback, constructor validation); CacheScopeTests (reads inside vs outside scope, typed reads route through cache, nested-scope throws, dispose is idempotent, allows new scope after dispose, null-cache throws). Full cdac unit suite passes: 2595 passed / 0 failed / 16 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a pluggable read-caching mechanism to the cDAC Target abstraction via cache scopes, introduces a reusable page-based cache implementation, migrates the legacy heap-walk code path to use the new scope, and adds unit tests for both the cache and the scoping behavior.
Changes:
- Add
ITargetReadCache+RawReadDelegateabstractions and a newTarget.BeginCacheScope(ITargetReadCache)virtual API. - Implement cache scoping + cache-routed reads in
ContractDescriptorTarget, and add a publicLinearReadCacheimplementation. - Update legacy
HeapWalkto open a single cache scope for the duration of the walk; add unit tests for the cache and scope behaviors.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ITargetReadCache.cs | Adds the cache interface + fallback delegate abstraction. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs | Adds the new BeginCacheScope virtual API and default scope implementation. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/LinearReadCache.cs | Introduces a page-buffered cache implementation. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs | Implements cache scoping and funnels typed reads through cached buffer reads. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/Helpers/HeapWalk.cs | Uses a cache scope for the full heap walk and switches to Target typed read APIs. |
| src/native/managed/cdac/tests/UnitTests/LinearReadCacheTests.cs | Adds unit tests for LinearReadCache behavior. |
| src/native/managed/cdac/tests/UnitTests/ContractDescriptor/CacheScopeTests.cs | Adds unit tests for scope lifecycle, routing, and nesting behavior. |
Comment on lines
+357
to
+361
| public virtual IDisposable BeginCacheScope(ITargetReadCache cache) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(cache); | ||
| return new DefaultCacheScope(cache); | ||
| } |
Comment on lines
+351
to
+355
| /// Only one cache scope may be active per target at a time; opening a second scope while one | ||
| /// is still active throws <see cref="InvalidOperationException"/>. The default implementation | ||
| /// does not route reads through the cache and is suitable for targets (such as test stubs) | ||
| /// that do not need caching; it still invalidates and disposes the cache when the scope ends | ||
| /// so that <see cref="ITargetReadCache"/> implementations see a consistent lifecycle. |
Comment on lines
+28
to
+31
| /// Read <c>destination.Length</c> bytes starting at <paramref name="address"/>. On a cache | ||
| /// miss the implementation must call <paramref name="fallback"/> to fetch the bytes from the | ||
| /// underlying target. Exceptions thrown by <paramref name="fallback"/> must be allowed to | ||
| /// propagate to the caller. |
Comment on lines
+34
to
+39
| public LinearReadCache(uint pageSize) | ||
| { | ||
| ArgumentOutOfRangeException.ThrowIfZero(pageSize); | ||
| _pageSize = pageSize; | ||
| _page = new byte[pageSize]; | ||
| } |
Comment on lines
+62
to
+70
| catch | ||
| { | ||
| // The page is unreadable (e.g. unmapped). Drop any cached state and let the | ||
| // direct read surface the failure. | ||
| _currPageStart = 0; | ||
| _currPageSize = 0; | ||
| fallback(address, destination); | ||
| return; | ||
| } |
Comment on lines
+147
to
+151
| // Cached delegate over ReadBufferRaw so opening a cache scope does not allocate. | ||
| private readonly RawReadDelegate _rawReadBuffer; | ||
| private ITargetReadCache? _activeCache; | ||
|
|
||
| public override IDisposable BeginCacheScope(ITargetReadCache cache) |
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.
Introduces a small abstraction for caching reads from a cDAC
Targetduring focused operations such as a heap walk. Replaces the ad-hocLinearReadCachethat was previously a private helper insideHeapWalk.cs.API
ITargetReadCache(new, inMicrosoft.Diagnostics.DataContractReader.Abstractions):Target.BeginCacheScope(new,virtual):Opens a scope during which Target reads route through the supplied cache. Default implementation is a no-op suitable for test stubs - it still invalidates and disposes the cache on scope end so the lifecycle is consistent.
LinearReadCache(new, public): page-bufferedITargetReadCache. Reads inside a single page are served from a cached buffer; misses, larger-than-page reads, and page-straddling reads fall through. Configurable page size, default0x1000.ContractDescriptorTargetBeginCacheScope. Nested scopes throwInvalidOperationException.TryReadBufferroutes through the active cache when present.TryRead<T>,ReadPointer/TryReadPointer,ReadNUInt/ReadNInt) consolidated to funnel throughTryReadBufferso they benefit from caching automatically.TryReadContractDescriptorhelpers continue to read directly (no target instance yet).Caller migration
HeapWalk(legacy DBI): drops its privateLinearReadCache, opens a single cache scope spanning the entire walk, and usesTarget's typed-read APIs:The cache stays warm across iterations - subsequent iterations typically hit the same page (object header + a trailing component-count field).
Tests
LinearReadCacheTests: page hit, cross-page miss, oversized read, separate page reload,Invalidate, page-fault fallback path, constructor validation.CacheScopeTests: reads inside vs outside scope, typed reads route through cache, nested scope throws,Disposeis idempotent, allows new scope after dispose, null cache throws.Full cdac unit suite:
Passed: 2595, Failed: 0, Skipped: 16.Design notes
See
cdac-cache-scope-proposal.md(session artifact) for the full design discussion. Key choices:InvalidateandDisposeare separate so callers can flush mid-scope (e.g. after a target write) without ending the scope.Target. Keeps caches decoupled from Target; the delegate is cached once perContractDescriptorTargetso opening a scope does not allocate.BeginCacheScopeis virtual, not abstract. Test target stubs (TestPlaceholderTarget,TestTarget) need no changes; concrete reader targets opt into real caching.Note
This PR was authored with assistance from GitHub Copilot.