[cdac] Introduce FlushScope and add TargetState scope#128846
[cdac] Introduce FlushScope and add TargetState scope#128846max-charlamb wants to merge 2 commits into
Conversation
Replace `IContract.Flush()` and `Target.Flush()` with a `Flush(FlushScope)` overload, and add a `FlushScope` enum with two values: * `All` -- clear every cache the target holds, including immutable metadata caches (type layouts, contract instances, ECMA metadata, execution-manager ranges). Matches the historical no-argument `Flush` behavior. * `TargetState` -- clear only target-state caches (`Target.ProcessedData`). Contract instances and any metadata caches they own are retained. Each contract is responsible for its own correctness under this scope: contracts that capture a target-memory snapshot at construction must re-read that snapshot in their `Flush(FlushScope.TargetState)` override. This new scope is the foundation for a follow-up cDAC stress-harness change that re-verifies live target state between snapshots without paying the cost of re-walking System.Private.CoreLib ECMA metadata on every iteration. With `Flush(FlushScope.TargetState)`, `ManagedTypeSource_1` retains its CoreLib-only caches across flushes (CoreLib lives in the non-collectible default ALC and its metadata is immutable for the process lifetime). Contract updates in this change: * `ManagedTypeSource_1` -- retains type/typehandle/fielddesc caches across `FlushScope.TargetState`; clears on `FlushScope.All` as before. * `ReJIT_1`, `PlatformMetadata_1` -- lazy-snapshot pattern: the contract no longer captures target state in its constructor, and re-snapshots on the next read after any flush. * `ExecutionManager_1`/`_2`/`ExecutionManagerCore` -- the top `RangeSectionMap` is resolved lazily and re-resolved after any flush so that `FlushScope.TargetState` correctly invalidates execution-range data that may have changed across a runtime resume. All other contracts that hold caches (`EcmaMetadata_1`, `Signature_1`, `RuntimeTypeSystem_1`) currently treat both scopes the same and clear on every call -- they can be tuned in follow-up changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
The Flush() -> Flush(FlushScope) signature change missed EcmaMetadata_1. Because IContract.Flush(FlushScope) has a default no-op body, the build still succeeded but EcmaMetadata's caches silently stopped being flushed via IContract.Flush(scope) -- the old parameterless Flush() became an orphaned method, and IContract's no-op default ran in its place. Match the signature so caches are cleared on flush. Body mirrors the other Flush(FlushScope) impls on this branch (unconditional clear); a follow-up audit will decide whether TargetState should preserve any contract-owned caches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Could we skip flushing the SPC metadata in the legacy cases? |
I think that would be okay, but I'm not confident. I'd like to not do that in this PR. It seems like a very minor perf difference. I'd rather over clear than cause a correctness issue. It was just that this particular case is a very hot path in the stress scenario. |
Personally I'm pretty sure it would be fine as long as we limit ourselves to SPC. I'm fine to do it in a follow up though. |
| { | ||
| foreach (IContract contract in _contracts.Values) | ||
| { | ||
| contract.Flush(); |
There was a problem hiding this comment.
Since no contract needs the scope except ManagedTypeSource, could we just condition the flush of ManagedTypeSource on the scope here, instead of propagating this through every contract?
There was a problem hiding this comment.
We want this at the abstractions level so each contract can decide how they want to 'flush' themselves. If any arbitrary contract registers itself (maybe from outside this repo), ideally it should also be able to decide how to flush.
For that reason, I don't want to special case ManagedTypeSource here.
rcj1
left a comment
There was a problem hiding this comment.
Maybe could be optimized by caching SPC whenever it is not negatively cached, but I'm ok if we leave this as is for now.
Note
This PR description was drafted with the help of GitHub Copilot.
Summary
Replaces
IContract.Flush()andTarget.Flush()with aFlush(FlushScope)overload and introduces aFlushScopeenum:All-- clears every cache the target holds, including immutable metadata caches (type layouts, contract instances, ECMA metadata, execution-manager ranges). Matches the historical no-argumentFlushbehavior.TargetState-- clears only target-state caches (Target.ProcessedData). Contract instances and any metadata caches they own are retained. Each contract is responsible for its own correctness under this scope: contracts that capture a target-memory snapshot at construction must re-read that snapshot in theirFlush(FlushScope.TargetState)override.Motivation
This is the foundation for a follow-up cDAC stress harness change that re-verifies live target state between snapshots (e.g. after every GC allocation under
DOTNET_CdacStress=0x1). Today, every such flush re-walks System.Private.CoreLib's ECMA metadata even though that metadata is immutable for the process lifetime (CoreLib is loaded into the non-collectible default ALC). The newFlushScope.TargetStateletsManagedTypeSource_1retain its CoreLib-only caches across these stress flushes.Measured impact on a
BasicAlloccDAC-stress run (Release cDAC):FlushScope.TargetStatekeeps CoreLib caches (this PR's win)A 2x speedup on the most allocation-heavy stress test before any further optimization.
Contract updates in this PR
ManagedTypeSource_1FlushScope.TargetState; clears onFlushScope.Allas before. Safe because the contract only resolves names in CoreLib.ReJIT_1,PlatformMetadata_1ExecutionManager_1/_2/ExecutionManagerCoreRangeSectionMapis resolved lazily and re-resolved after any flush so thatFlushScope.TargetStatecorrectly invalidates execution-range data that may have changed across a runtime resume.EcmaMetadata_1,Signature_1, andRuntimeTypeSystem_1currently treat both scopes the same and clear on every call -- they can be tuned in follow-up changes if profiling motivates it.Compatibility
All public
Flush()entry points now require aFlushScopeargument. Every caller indotnet/runtimeis updated in this PR:SOSDacImpl.FlushCache,ISOSDacInterface13.LockedFlush,IXCLRDataProcess.Flush-> passFlushScope.All(no behavior change)DacDbiImpl.FlushCache->FlushScope.AllTestPlaceholderTarget,TestTarget) updated to the new signatureThe default no-op override on
IContract.Flush(FlushScope)preserves source compatibility for any contract that does not maintain caches.Validation
cdac.slnxbuilds with 0 warnings, 0 errors.Microsoft.Diagnostics.DataContractReader.Tests: 2417 passed / 0 failed / 16 platform-skipped.Out of scope (planned follow-ups)
cdacstress.{cpp,h}, theDACSTRESSPRIV_REQUEST_FLUSH_TARGET_STATEopcode inIXCLRDataProcess.Request, and the helix pipeline).ContractDescriptorTarget.EcmaMetadata_1._readOnlyMetadataAddresscache.LayoutPairN-source lazy resolution./cc @dotnet/runtime-diagnostics-cdac