From 4d9ec37c680293bd352b11f8e2c06667d6f25029 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Mon, 1 Jun 2026 09:52:07 -0400 Subject: [PATCH 1/3] [cdac] Introduce FlushScope and add TargetState scope 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> --- .../ContractRegistry.cs | 7 +++-- .../Contracts/IContract.cs | 13 ++++++-- .../FlushScope.cs | 30 +++++++++++++++++++ .../Target.cs | 6 ++-- .../ExecutionManager/ExecutionManagerCore.cs | 11 ++++--- .../ExecutionManager/ExecutionManager_1.cs | 5 ++-- .../ExecutionManager/ExecutionManager_2.cs | 5 ++-- .../Contracts/ManagedTypeSource_1.cs | 9 +++++- .../Contracts/PlatformMetadata_1.cs | 9 +++--- .../Contracts/ReJIT_1.cs | 9 +++--- .../Contracts/RuntimeTypeSystem_1.cs | 2 +- .../Contracts/Signature/Signature_1.cs | 2 +- .../Dbi/DacDbiImpl.cs | 2 +- .../SOSDacImpl.IXCLRDataProcess.cs | 2 +- .../SOSDacImpl.cs | 2 +- .../CachingContractRegistry.cs | 4 +-- .../ContractDescriptorTarget.cs | 4 +-- .../cdac/tests/DataGenerator/TestTarget.cs | 2 +- .../cdac/tests/TestPlaceholderTarget.cs | 2 +- 19 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index a539964b0fe7d6..3d754b5284ebbb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -166,8 +166,9 @@ public abstract void Register(string version, Func where TContract : IContract; /// - /// Flush all cached data held by contracts in this registry. - /// Called when the target process state may have changed (e.g. on resume). + /// Flush all cached data held by contracts in this registry for the given + /// . Called when the target process state may have changed + /// (e.g. on resume) or as part of a stress-harness re-read of live target state. /// - public abstract void Flush(); + public abstract void Flush(FlushScope scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs index 27f72d3bccddfc..04f96ea3b1b635 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs @@ -10,8 +10,15 @@ public interface IContract static virtual string Name => throw new NotImplementedException(); /// - /// Clear any cached data held by this contract. - /// Called when the target process state may have changed (e.g. on resume). + /// Clear cached data held by this contract for the given . + /// Called when the target process state may have changed (e.g. on resume) or as + /// part of a stress-harness re-read of live target state. + /// + /// Default implementation is a no-op. Contracts that maintain caches or capture + /// target-memory snapshots at construction must override this method and handle + /// each value appropriately. See the remarks on + /// . + /// /// - void Flush() { } + void Flush(FlushScope scope) { } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs new file mode 100644 index 00000000000000..fd0a70926be92d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader; + +/// +/// Scope of a operation. +/// +public enum FlushScope +{ + /// + /// Clear every cache held by the target, including immutable metadata caches + /// (type layouts, contract instances, ECMA metadata, execution-manager ranges). + /// This is the safe default and matches the historical no-argument Flush behavior. + /// + All, + + /// + /// Clear only target-state caches (). Contract + /// instances and any metadata caches they own are retained. + /// + /// Each contract is responsible for its own correctness under this scope: a + /// contract that captures a target-memory snapshot at construction MUST re-read + /// that snapshot in its + /// implementation when called with , otherwise the + /// snapshot becomes stale across the flush. + /// + /// + TargetState, +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 2c4c504d1c14d3..733a43d9e7f2e3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -324,12 +324,12 @@ public readonly record struct FieldInfo public abstract ContractRegistry Contracts { get; } /// - /// Clear all cached data held by this target, including processed data and contract caches. + /// Clear cached data held by this target for the given . /// Called when the target process state may have changed (e.g. on resume). /// - public virtual void Flush() + public virtual void Flush(FlushScope scope) { ProcessedData.Clear(); - Contracts.Flush(); + Contracts.Flush(scope); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index bdc5ed3fb07f87..7c8465b2baf26e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -17,16 +17,19 @@ internal sealed partial class ExecutionManagerCore : IExecutionManager // maps CodeBlockHandle.Address (which is the CodeHeaderAddress) to the CodeBlock private readonly Dictionary _codeInfos = new(); - private readonly Data.RangeSectionMap _topRangeSectionMap; + private readonly TargetPointer _topRangeSectionMapAddress; private readonly ExecutionManagerHelpers.RangeSectionMap _rangeSectionMapLookup; private readonly EEJitManager _eeJitManager; private readonly ReadyToRunJitManager _r2rJitManager; private readonly InterpreterJitManager _interpreterJitManager; - public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionMap) + private Data.RangeSectionMap _topRangeSectionMap + => _target.ProcessedData.GetOrAdd(_topRangeSectionMapAddress); + + public ExecutionManagerCore(Target target, TargetPointer topRangeSectionMapAddress) { _target = target; - _topRangeSectionMap = topRangeSectionMap; + _topRangeSectionMapAddress = topRangeSectionMapAddress; _rangeSectionMapLookup = ExecutionManagerHelpers.RangeSectionMap.Create(_target); INibbleMap nibbleMap = T.Create(_target); _eeJitManager = new EEJitManager(_target, nibbleMap); @@ -34,7 +37,7 @@ public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionM _interpreterJitManager = new InterpreterJitManager(_target, nibbleMap); } - public void Flush() + public void Flush(FlushScope scope) { _codeInfos.Clear(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index e76ff65f697a27..a94a7c0d4b983c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -13,8 +13,7 @@ public sealed class ExecutionManager_1 : IExecutionManager internal ExecutionManager_1(Target target) { TargetPointer addr = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); - Data.RangeSectionMap map = target.ProcessedData.GetOrAdd(addr); - _executionManagerCore = new ExecutionManagerCore(target, map); + _executionManagerCore = new ExecutionManagerCore(target, addr); } public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); @@ -36,5 +35,5 @@ internal ExecutionManager_1(Target target) public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress); public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); - public void Flush() => _executionManagerCore.Flush(); + public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 61513725ef0823..6c447f2e563ab9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -13,8 +13,7 @@ public sealed class ExecutionManager_2 : IExecutionManager internal ExecutionManager_2(Target target) { TargetPointer addr = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); - Data.RangeSectionMap map = target.ProcessedData.GetOrAdd(addr); - _executionManagerCore = new ExecutionManagerCore(target, map); + _executionManagerCore = new ExecutionManagerCore(target, addr); } public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); @@ -36,5 +35,5 @@ internal ExecutionManager_2(Target target) public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress); public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); - public void Flush() => _executionManagerCore.Flush(); + public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs index 74c1282a5e4a5c..c1b57f445b4327 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs @@ -23,8 +23,15 @@ public ManagedTypeSource_1(Target target) _target = target; } - public void Flush() + public void Flush(FlushScope scope) { + // They are safe to retain across FlushScope.TargetState because + // ManagedTypeSource_1 only resolves names in System.Private.CoreLib, which + // is loaded into the non-collectible default AssemblyLoadContext at runtime + // startup and whose ECMA metadata never changes for the process lifetime. + if (scope != FlushScope.All) + return; + _typeInfoCache.Clear(); _typeHandleCache.Clear(); _fieldDescCache.Clear(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs index 5cb85410897c06..4b1d06990fcf8e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs @@ -5,16 +5,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal readonly partial struct PlatformMetadata_1 : IPlatformMetadata +internal readonly struct PlatformMetadata_1 : IPlatformMetadata { internal readonly Target _target; - private readonly Data.PlatformMetadata _cdacMetadata; + private readonly TargetPointer _cdacMetadataAddress; + private Data.PlatformMetadata _cdacMetadata + => _target.ProcessedData.GetOrAdd(_cdacMetadataAddress); public PlatformMetadata_1(Target target) { _target = target; - TargetPointer cdacMetadataAddress = target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); - _cdacMetadata = target.ProcessedData.GetOrAdd(cdacMetadataAddress); + _cdacMetadataAddress = target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); } TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs index 71b29a5eb8c844..e8e9fff8325ef9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs @@ -7,10 +7,12 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal readonly partial struct ReJIT_1 : IReJIT +internal readonly struct ReJIT_1 : IReJIT { internal readonly Target _target; - private readonly Data.ProfControlBlock _profControlBlock; + private readonly TargetPointer _profControlBlockAddress; + private Data.ProfControlBlock _profControlBlock + => _target.ProcessedData.GetOrAdd(_profControlBlockAddress); // see src/coreclr/inc/corprof.idl [Flags] @@ -33,8 +35,7 @@ public enum RejitFlags : uint public ReJIT_1(Target target) { _target = target; - TargetPointer profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); - _profControlBlock = target.ProcessedData.GetOrAdd(profControlBlockAddress); + _profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); } bool IReJIT.IsEnabled() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index fd06a25dd80840..f082df5c2d5802 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -32,7 +32,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Dictionary _methodDescs = new(); private readonly Dictionary _typeHandles = new(); - public void Flush() + public void Flush(FlushScope scope) { _methodTables.Clear(); _methodDescs.Clear(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs index 1c07cf9f8a47f8..ee7cf0485ee0e8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs @@ -25,7 +25,7 @@ internal Signature_1(Target target) _target = target; } - public void Flush() + public void Flush(FlushScope scope) { _thProviders.Clear(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 5c8b54d69861ea..a6c30fe3d9ab5f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -67,7 +67,7 @@ public int CheckDbiVersion(DbiVersion* pVersion) public int FlushCache() { - _target.Flush(); + _target.Flush(FlushScope.All); return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs index 5eddb84087556b..a0840f0362f2ff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -21,7 +21,7 @@ public sealed unsafe partial class SOSDacImpl : IXCLRDataProcess, IXCLRDataProce { int IXCLRDataProcess.Flush() { - _target.Flush(); + _target.Flush(FlushScope.All); // Flush is always propagated — it's cache management, not data retrieval. if (_legacyProcess is not null) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index f0ec3ef67ed4f8..2007bb81728a1e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -6712,7 +6712,7 @@ int ISOSDacInterface13.GetGCFreeRegions(DacComNullableByRef ppEn } int ISOSDacInterface13.LockedFlush() { - _target.Flush(); + _target.Flush(FlushScope.All); // As long as any part of cDAC falls back to the legacy DAC, we need to propagate the Flush call if (_legacyImpl13 is not null) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 8a6f499f13a685..376b680f35966c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -70,11 +70,11 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract return true; } - public override void Flush() + public override void Flush(FlushScope scope) { foreach (IContract contract in _contracts.Values) { - contract.Flush(); + contract.Flush(scope); } } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 6bc5ee12bed173..273ca29a2ae7f5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -143,9 +143,9 @@ private ContractDescriptorTarget(Descriptor mainDescriptor, DataTargetDelegates BuildDescriptors(forceBuild: true); } - public override void Flush() + public override void Flush(FlushScope scope) { - base.Flush(); + base.Flush(scope); BuildDescriptors(); } diff --git a/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs b/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs index e2435cae5f7f30..e1e9a7e8bd6392 100644 --- a/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs +++ b/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs @@ -283,7 +283,7 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract public override void Register(string version, Func creator) => throw new NotImplementedException(); - public override void Flush() { } + public override void Flush(FlushScope scope) { } } // --- Trivial IDataCache for ReadDataField support ---------------- diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index a6bfe639013663..af59d741181944 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -625,7 +625,7 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract return true; } - public override void Flush() { } + public override void Flush(FlushScope scope) { } } } From 3899bf49ad7ca7c13479eb0fcea901bc9222e77c Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:48:36 -0400 Subject: [PATCH 2/3] [cdac] Update EcmaMetadata_1.Flush to FlushScope signature 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> --- .../Contracts/EcmaMetadata_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs index 432005cfaae081..00bcd71d07fe72 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs @@ -16,7 +16,7 @@ internal sealed class EcmaMetadata_1(Target target) : IEcmaMetadata private readonly Dictionary _metadata = []; private readonly Dictionary _readOnlyMetadataAddress = []; - public void Flush() + public void Flush(FlushScope scope) { _metadata.Clear(); _readOnlyMetadataAddress.Clear(); From a312cb13ea0d1516b99f65cebf6d4d4cafd69fa0 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 2 Jun 2026 10:18:36 -0400 Subject: [PATCH 3/3] [cdac] Rename FlushScope.TargetState to ForwardExecution Per review feedback: 'TargetState' was misleading because loaded metadata is also target state. The actual distinction is whether the target has executed forward in time since the last flush (where immutable state can be retained) versus an arbitrary state snapshot (where everything must be re-read). This is the same distinction ICorDebug's CorDebugStateChange enum captures with PROCESS_RUNNING vs FLUSH_ALL. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contracts/IContract.cs | 3 +-- .../FlushScope.cs | 24 +++++++++++++------ .../Contracts/ManagedTypeSource_1.cs | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs index 04f96ea3b1b635..4c94ee13534b3d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs @@ -16,8 +16,7 @@ public interface IContract /// /// Default implementation is a no-op. Contracts that maintain caches or capture /// target-memory snapshots at construction must override this method and handle - /// each value appropriately. See the remarks on - /// . + /// each value appropriately. /// /// void Flush(FlushScope scope) { } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs index fd0a70926be92d..d09c1aa6cbcdc7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs @@ -16,15 +16,25 @@ public enum FlushScope All, /// - /// Clear only target-state caches (). Contract - /// instances and any metadata caches they own are retained. + /// Flush only caches that may have become stale because the target process + /// has executed forward in time since the last flush (e.g. a debugger + /// resume/continue/step). Caches of immutable state that the runtime loads + /// once and never unloads (modules, types, code metadata, ECMA metadata, + /// execution-manager ranges, etc.) may be retained. + /// + /// This corresponds to ICorDebug's CorDebugStateChange.PROCESS_RUNNING: + /// see . + /// Use instead for arbitrary state snapshots where no + /// continuity with the previous target state can be assumed. + /// /// /// Each contract is responsible for its own correctness under this scope: a - /// contract that captures a target-memory snapshot at construction MUST re-read - /// that snapshot in its - /// implementation when called with , otherwise the - /// snapshot becomes stale across the flush. + /// contract that captures a target-memory snapshot at construction MUST + /// re-read that snapshot in its + /// implementation when + /// called with , otherwise the snapshot + /// becomes stale across the flush. /// /// - TargetState, + ForwardExecution, } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs index c1b57f445b4327..db468f425f95b4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs @@ -25,7 +25,7 @@ public ManagedTypeSource_1(Target target) public void Flush(FlushScope scope) { - // They are safe to retain across FlushScope.TargetState because + // They are safe to retain across FlushScope.ForwardExecution because // ManagedTypeSource_1 only resolves names in System.Private.CoreLib, which // is loaded into the non-collectible default AssemblyLoadContext at runtime // startup and whose ECMA metadata never changes for the process lifetime.