feat(cordis): 完成阶段5访问介导与诊断
This commit is contained in:
@@ -1,21 +1,92 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>共享存储中的一条依赖绑定:键解析到 realm 后对应的值与提供者纤程。</summary>
|
||||
public sealed class ShrinkBinding
|
||||
{
|
||||
public ShrinkBinding(object? value, ShrinkFiber? provider, string key)
|
||||
public ShrinkBinding(object? value, ShrinkFiber? provider, string key,
|
||||
IShrinkCoeffectAccessPolicy? accessPolicy = null)
|
||||
{
|
||||
Value = value;
|
||||
Provider = provider;
|
||||
Key = key;
|
||||
AccessPolicy = accessPolicy;
|
||||
}
|
||||
|
||||
public object? Value { get; }
|
||||
public ShrinkFiber? Provider { get; }
|
||||
public string Key { get; }
|
||||
public IShrinkCoeffectAccessPolicy? AccessPolicy { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带命名空间与主版本的类型化余效应键。现有字符串声明可继续使用 <see cref="Id"/>,
|
||||
/// Set/Get 重载则在调用点固定值类型,逐步收口字符串键的碰撞与接口漂移。
|
||||
/// </summary>
|
||||
public sealed class ShrinkKey<T>
|
||||
{
|
||||
public ShrinkKey(string packageName, string name, int majorVersion = 1)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packageName))
|
||||
throw new ArgumentException("Package name must not be empty.", nameof(packageName));
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("Key name must not be empty.", nameof(name));
|
||||
if (majorVersion <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(majorVersion), "Major version must be positive.");
|
||||
|
||||
PackageName = packageName.Trim();
|
||||
Name = name.Trim();
|
||||
MajorVersion = majorVersion;
|
||||
Id = $"{PackageName}/{Name}@v{MajorVersion}";
|
||||
}
|
||||
|
||||
public string PackageName { get; }
|
||||
public string Name { get; }
|
||||
public int MajorVersion { get; }
|
||||
public string Id { get; }
|
||||
public override string ToString() => Id;
|
||||
}
|
||||
|
||||
/// <summary>依赖值被读取时的访问上下文;metadata 来自访问方上下文上的 intercept 合并结果。</summary>
|
||||
public sealed class ShrinkCoeffectAccessContext
|
||||
{
|
||||
internal ShrinkCoeffectAccessContext(string key, Type requestedType, ShrinkFiber? consumer,
|
||||
IReadOnlyDictionary<string, object?> metadata)
|
||||
{
|
||||
Key = key;
|
||||
RequestedType = requestedType;
|
||||
Consumer = consumer;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public Type RequestedType { get; }
|
||||
public ShrinkFiber? Consumer { get; }
|
||||
public IReadOnlyDictionary<string, object?> Metadata { get; }
|
||||
|
||||
public bool TryGetMetadata<T>(string name, out T value)
|
||||
{
|
||||
if (Metadata.TryGetValue(name, out var raw) && raw is T typed)
|
||||
{
|
||||
value = typed;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// provider 侧访问策略。它可以依据 intercept metadata 返回降权包装或拒绝访问,
|
||||
/// 但不会改变依赖满足关系,也不会构成不可信代码沙箱。
|
||||
/// </summary>
|
||||
public interface IShrinkCoeffectAccessPolicy
|
||||
{
|
||||
object? Resolve(ShrinkCoeffectAccessContext context, object? value);
|
||||
}
|
||||
|
||||
/// <summary>访问了纤程链上没有任何一方声明的依赖键(论文算法 6 的 UNDECLARED_ACCESS)。</summary>
|
||||
@@ -42,6 +113,32 @@ namespace ShrinkContext
|
||||
public string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>组件写入了未在 Provide 中声明的键。</summary>
|
||||
public sealed class ShrinkUndeclaredSupplyException : Exception
|
||||
{
|
||||
public ShrinkUndeclaredSupplyException(string key, string fiberName)
|
||||
: base($"Undeclared coeffect supply: '{key}' is not listed in fiber '{fiberName}' Provide keys.")
|
||||
{
|
||||
Key = key;
|
||||
FiberName = fiberName;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public string FiberName { get; }
|
||||
}
|
||||
|
||||
/// <summary>intercept 访问策略拒绝了本次依赖读取。</summary>
|
||||
public sealed class ShrinkCoeffectAccessDeniedException : Exception
|
||||
{
|
||||
public ShrinkCoeffectAccessDeniedException(string key, string message)
|
||||
: base($"Coeffect access denied for '{key}': {message}")
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>两个活跃纤程试图供给同一个 realm(论文定义 45 的供给不相交约束)。</summary>
|
||||
public sealed class ShrinkSupplyConflictException : Exception
|
||||
{
|
||||
|
||||
@@ -15,14 +15,17 @@ namespace ShrinkContext
|
||||
{
|
||||
private readonly List<ShrinkEffectHandle> _effects = new();
|
||||
private readonly Dictionary<string, string>? _isolateOverlay;
|
||||
private Dictionary<string, IReadOnlyDictionary<string, object?>>? _interceptOverlay;
|
||||
|
||||
internal ShrinkCtx(ShrinkContextRuntime runtime, ShrinkCtx? parent, ShrinkFiber? ownerFiber,
|
||||
Dictionary<string, string>? isolateOverlay)
|
||||
Dictionary<string, string>? isolateOverlay,
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? interceptOverlay = null)
|
||||
{
|
||||
Runtime = runtime;
|
||||
Parent = parent;
|
||||
OwnerFiber = ownerFiber;
|
||||
_isolateOverlay = isolateOverlay;
|
||||
ReplaceIntercept(interceptOverlay);
|
||||
}
|
||||
|
||||
public ShrinkContextRuntime Runtime { get; }
|
||||
@@ -106,7 +109,17 @@ namespace ShrinkContext
|
||||
/// 提供依赖(set 是可逆效应):立即安装绑定并通知依赖者;
|
||||
/// 逆操作撤回绑定并再次通知。句柄同时挂在当前上下文上,随上下文卸载自动撤回。
|
||||
/// </summary>
|
||||
public ShrinkEffectHandle Set<T>(string key, T value) => Runtime.SetBinding(this, key, value);
|
||||
public ShrinkEffectHandle Set<T>(string key, T value) => Runtime.SetBinding(this, key, value, null);
|
||||
|
||||
/// <summary>提供带访问策略的依赖;策略只在介导 Get 时运行,原始编排查找不会运行策略。</summary>
|
||||
public ShrinkEffectHandle Set<T>(string key, T value, IShrinkCoeffectAccessPolicy accessPolicy) =>
|
||||
Runtime.SetBinding(this, key, value, accessPolicy ?? throw new ArgumentNullException(nameof(accessPolicy)));
|
||||
|
||||
public ShrinkEffectHandle Set<T>(ShrinkKey<T> key, T value) =>
|
||||
Set((key ?? throw new ArgumentNullException(nameof(key))).Id, value);
|
||||
|
||||
public ShrinkEffectHandle Set<T>(ShrinkKey<T> key, T value, IShrinkCoeffectAccessPolicy accessPolicy) =>
|
||||
Set((key ?? throw new ArgumentNullException(nameof(key))).Id, value, accessPolicy);
|
||||
|
||||
/// <summary>
|
||||
/// 代理介导的依赖访问(论文算法 6):沿纤程链解析已提交视图;
|
||||
@@ -119,7 +132,7 @@ namespace ShrinkContext
|
||||
while (fiber != null)
|
||||
{
|
||||
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
|
||||
return (T)binding.Value!;
|
||||
return Runtime.ResolveAccess<T>(this, key, binding);
|
||||
if (fiber.InjectSet.Contains(key))
|
||||
throw new ShrinkInactiveAccessException(key, fiber.Name);
|
||||
fiber = fiber.Parent;
|
||||
@@ -128,6 +141,9 @@ namespace ShrinkContext
|
||||
throw new ShrinkUndeclaredAccessException(key);
|
||||
}
|
||||
|
||||
public T Get<T>(ShrinkKey<T> key) => Get<T>(
|
||||
(key ?? throw new ArgumentNullException(nameof(key))).Id);
|
||||
|
||||
/// <summary>与 <see cref="Get{T}"/> 相同的介导规则;未激活返回 false 而不抛异常。</summary>
|
||||
public bool TryGet<T>(string key, out T value)
|
||||
{
|
||||
@@ -136,7 +152,7 @@ namespace ShrinkContext
|
||||
{
|
||||
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
|
||||
{
|
||||
value = (T)binding.Value!;
|
||||
value = Runtime.ResolveAccess<T>(this, key, binding);
|
||||
return true;
|
||||
}
|
||||
if (fiber.InjectSet.Contains(key))
|
||||
@@ -148,6 +164,9 @@ namespace ShrinkContext
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGet<T>(ShrinkKey<T> key, out T value) => TryGet(
|
||||
(key ?? throw new ArgumentNullException(nameof(key))).Id, out value);
|
||||
|
||||
/// <summary>
|
||||
/// 派生隔离子上下文(论文定义 28/29 的派生实现):
|
||||
/// 覆盖 key 的 realm 解析;父上下文不受影响;丢弃子上下文即隐式恢复。
|
||||
@@ -162,6 +181,33 @@ namespace ShrinkContext
|
||||
return new ShrinkCtx(Runtime, this, OwnerFiber, new Dictionary<string, string> { [key] = realm });
|
||||
}
|
||||
|
||||
public ShrinkCtx Isolate<T>(ShrinkKey<T> key, string realm) => Isolate(
|
||||
(key ?? throw new ArgumentNullException(nameof(key))).Id, realm);
|
||||
|
||||
/// <summary>
|
||||
/// 派生带拦截元数据的子上下文。子级同名 metadata 覆盖父级;丢弃子上下文即恢复。
|
||||
/// 该操作不改变 realm、provider 或 target,因此不会触发 fiber 重载。
|
||||
/// </summary>
|
||||
public ShrinkCtx Intercept(string key, IReadOnlyDictionary<string, object?> metadata)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentException("Key must not be null or empty.", nameof(key));
|
||||
if (metadata == null)
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
|
||||
var metadataCopy = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
foreach (var pair in metadata)
|
||||
metadataCopy[pair.Key] = pair.Value;
|
||||
var overlay = new Dictionary<string, IReadOnlyDictionary<string, object?>>(StringComparer.Ordinal)
|
||||
{
|
||||
[key] = metadataCopy
|
||||
};
|
||||
return new ShrinkCtx(Runtime, this, OwnerFiber, null, overlay);
|
||||
}
|
||||
|
||||
public ShrinkCtx Intercept<T>(ShrinkKey<T> key, IReadOnlyDictionary<string, object?> metadata) =>
|
||||
Intercept((key ?? throw new ArgumentNullException(nameof(key))).Id, metadata);
|
||||
|
||||
internal ShrinkCtx CreateChildForFiber(ShrinkFiber fiber) => new(Runtime, this, fiber, null);
|
||||
|
||||
/// <summary>在派生子上下文中叠加隔离覆盖(同键后者优先),供加载器为条目配置隔离域。</summary>
|
||||
@@ -180,6 +226,38 @@ namespace ShrinkContext
|
||||
return new ShrinkCtx(Runtime, this, OwnerFiber, overlay);
|
||||
}
|
||||
|
||||
/// <summary>在当前纤程上下文上叠加 loader 条目配置的 intercept。</summary>
|
||||
internal ShrinkCtx WithIntercept(
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>> intercept) =>
|
||||
new(Runtime, this, OwnerFiber, null, interceptOverlay: intercept);
|
||||
|
||||
/// <summary>原地替换本层 intercept,供 loader 在不重载 fiber 的情况下更新访问策略。</summary>
|
||||
internal void ReplaceIntercept(
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? intercept)
|
||||
{
|
||||
if (intercept == null || intercept.Count == 0)
|
||||
{
|
||||
_interceptOverlay = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var copy = new Dictionary<string, IReadOnlyDictionary<string, object?>>(StringComparer.Ordinal);
|
||||
foreach (var pair in intercept)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pair.Key))
|
||||
throw new ArgumentException("Intercept key must not be empty.", nameof(intercept));
|
||||
if (pair.Value == null)
|
||||
throw new ArgumentException($"Intercept metadata for '{pair.Key}' must not be null.",
|
||||
nameof(intercept));
|
||||
var metadataCopy = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
foreach (var metadataPair in pair.Value)
|
||||
metadataCopy[metadataPair.Key] = metadataPair.Value;
|
||||
copy[pair.Key] = metadataCopy;
|
||||
}
|
||||
|
||||
_interceptOverlay = copy;
|
||||
}
|
||||
|
||||
/// <summary>解析 key 在本上下文中归属的 realm:沿父链找最近的隔离覆盖,默认 realm 为 key 自身。</summary>
|
||||
internal string ResolveRealm(string key)
|
||||
{
|
||||
@@ -193,5 +271,30 @@ namespace ShrinkContext
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>按根到叶顺序合并拦截元数据;越接近访问方的上下文优先。</summary>
|
||||
internal IReadOnlyDictionary<string, object?> ResolveInterceptMetadata(string key)
|
||||
{
|
||||
var chain = new Stack<ShrinkCtx>();
|
||||
for (var current = this; current != null; current = current.Parent)
|
||||
chain.Push(current);
|
||||
|
||||
var merged = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
while (chain.Count > 0)
|
||||
{
|
||||
var current = chain.Pop();
|
||||
if (current._interceptOverlay == null ||
|
||||
!current._interceptOverlay.TryGetValue(key, out var metadata))
|
||||
continue;
|
||||
|
||||
foreach (var pair in metadata)
|
||||
merged[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
internal IReadOnlyCollection<string> GetLocalInterceptKeys() =>
|
||||
_interceptOverlay?.Keys ?? (IReadOnlyCollection<string>)Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace ShrinkContext
|
||||
Parent = parent;
|
||||
Config = config;
|
||||
InjectSet = new HashSet<string>(component.Inject ?? Array.Empty<string>(), StringComparer.Ordinal);
|
||||
ProvideSet = new HashSet<string>(component.Provide ?? Array.Empty<string>(), StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public long Uid { get; }
|
||||
@@ -62,5 +63,6 @@ namespace ShrinkContext
|
||||
internal bool InTransition;
|
||||
internal bool Retired;
|
||||
internal HashSet<string> InjectSet { get; }
|
||||
internal HashSet<string> ProvideSet { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
@@ -9,7 +10,8 @@ namespace ShrinkContext
|
||||
public sealed class ShrinkLoaderEntry
|
||||
{
|
||||
public ShrinkLoaderEntry(string id, string component, object? config = null, bool disabled = false,
|
||||
IReadOnlyDictionary<string, string>? isolate = null)
|
||||
IReadOnlyDictionary<string, string>? isolate = null,
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? intercept = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
throw new ArgumentException("Entry id must not be null or empty.", nameof(id));
|
||||
@@ -21,6 +23,7 @@ namespace ShrinkContext
|
||||
Config = config;
|
||||
Disabled = disabled;
|
||||
Isolate = isolate;
|
||||
Intercept = intercept;
|
||||
}
|
||||
|
||||
/// <summary>稳定协调键:条目增删与更新的 diff 依据。</summary>
|
||||
@@ -37,6 +40,9 @@ namespace ShrinkContext
|
||||
|
||||
/// <summary>条目级隔离域:键 → realm,叠加到该纤程自身的解析。</summary>
|
||||
public IReadOnlyDictionary<string, string>? Isolate { get; }
|
||||
|
||||
/// <summary>条目级访问元数据:依赖键 → metadata。更新时不改变 target,也不重载 fiber。</summary>
|
||||
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? Intercept { get; }
|
||||
}
|
||||
|
||||
/// <summary>组件目录:条目的组件名 → 组件工厂。装配期由宿主/CodeGen 注册,加载器不做全域反射扫描。</summary>
|
||||
@@ -69,23 +75,68 @@ namespace ShrinkContext
|
||||
component = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Contains(string name) => _factories.ContainsKey(name);
|
||||
}
|
||||
|
||||
/// <summary>加载器配置错误(重复条目 id、未知组件名等)。</summary>
|
||||
public sealed class ShrinkLoaderException : Exception
|
||||
public class ShrinkLoaderException : Exception
|
||||
{
|
||||
public ShrinkLoaderException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShrinkLoaderTransactionPhase
|
||||
{
|
||||
Idle = 0,
|
||||
Validating = 1,
|
||||
Removing = 2,
|
||||
Applying = 3,
|
||||
Restoring = 4,
|
||||
Completed = 5,
|
||||
Failed = 6,
|
||||
}
|
||||
|
||||
/// <summary>最近一次声明式协调的稳定诊断结果。</summary>
|
||||
public sealed class ShrinkLoaderTransactionDiagnostic
|
||||
{
|
||||
internal ShrinkLoaderTransactionDiagnostic(long generation)
|
||||
{
|
||||
Generation = generation;
|
||||
}
|
||||
|
||||
public long Generation { get; }
|
||||
public ShrinkLoaderTransactionPhase Phase { get; internal set; }
|
||||
public string? CurrentEntryId { get; internal set; }
|
||||
public string? ErrorType { get; internal set; }
|
||||
public string? ErrorMessage { get; internal set; }
|
||||
public bool RestoreAttempted { get; internal set; }
|
||||
public bool PreviousCompositionRestored { get; internal set; }
|
||||
public string? RestoreErrorType { get; internal set; }
|
||||
public string? RestoreErrorMessage { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class ShrinkLoaderRestoreException : ShrinkLoaderException
|
||||
{
|
||||
public ShrinkLoaderRestoreException(Exception applyError, Exception restoreError)
|
||||
: base("Loader apply failed and restoring the previous composition also failed.")
|
||||
{
|
||||
ApplyError = applyError;
|
||||
RestoreError = restoreError;
|
||||
}
|
||||
|
||||
public Exception ApplyError { get; }
|
||||
public Exception RestoreError { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 声明式组件加载器(论文 5.2 的原型子集):
|
||||
/// 编排者把期望组合表达为条目列表,加载器将其增量协调为纤程操作——
|
||||
/// 条目消失 → 退役;disabled → 卸载;组件/配置变化 → 重建;其余保持不动(幂等)。
|
||||
///
|
||||
/// 与完整实现的差异(后续阶段补齐):config 变化当前统一走重建而非组件自决 diff;
|
||||
/// isolate 变化走重建而非领域原地重分配;intercept 尚未支持。
|
||||
/// 与完整实现的差异:config 变化当前统一走重建而非组件自决 diff;
|
||||
/// isolate 变化仍走重建而非领域原地重分配。intercept metadata 可原地更新,不触发重载。
|
||||
/// </summary>
|
||||
public sealed class ShrinkContextLoader
|
||||
{
|
||||
@@ -95,12 +146,15 @@ namespace ShrinkContext
|
||||
public object? Config;
|
||||
public bool Disabled;
|
||||
public IReadOnlyDictionary<string, string>? Isolate;
|
||||
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? Intercept;
|
||||
public ShrinkFiber? Fiber;
|
||||
}
|
||||
|
||||
private readonly ShrinkContextRuntime _runtime;
|
||||
private readonly ShrinkComponentCatalog _catalog;
|
||||
private readonly Dictionary<string, ManagedEntry> _entries = new(StringComparer.Ordinal);
|
||||
private long _transactionGeneration;
|
||||
private bool _applying;
|
||||
|
||||
public ShrinkContextLoader(ShrinkContextRuntime runtime, ShrinkComponentCatalog catalog)
|
||||
{
|
||||
@@ -111,6 +165,8 @@ namespace ShrinkContext
|
||||
/// <summary>当前托管条目 id(含 disabled 但仍被管理的条目)。</summary>
|
||||
public IEnumerable<string> ManagedEntryIds => _entries.Keys;
|
||||
|
||||
public ShrinkLoaderTransactionDiagnostic? LastTransaction { get; private set; }
|
||||
|
||||
/// <summary>查询条目当前纤程(可能处于 Inactive——依赖缺失时被响应式停用但仍被管理)。</summary>
|
||||
public bool TryGetFiber(string entryId, out ShrinkFiber fiber)
|
||||
{
|
||||
@@ -132,15 +188,71 @@ namespace ShrinkContext
|
||||
{
|
||||
if (desired == null)
|
||||
throw new ArgumentNullException(nameof(desired));
|
||||
if (_applying)
|
||||
throw new InvalidOperationException("A loader composition transaction is already running.");
|
||||
|
||||
var previous = CaptureEntries();
|
||||
var transaction = new ShrinkLoaderTransactionDiagnostic(++_transactionGeneration);
|
||||
LastTransaction = transaction;
|
||||
_applying = true;
|
||||
try
|
||||
{
|
||||
await ApplyCoreAsync(desired, transaction, restoring: false);
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Completed;
|
||||
transaction.CurrentEntryId = null;
|
||||
}
|
||||
catch (Exception applyError)
|
||||
{
|
||||
transaction.ErrorType = applyError.GetType().FullName;
|
||||
transaction.ErrorMessage = applyError.Message;
|
||||
transaction.RestoreAttempted = true;
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Restoring;
|
||||
transaction.CurrentEntryId = null;
|
||||
|
||||
try
|
||||
{
|
||||
await ApplyCoreAsync(previous, transaction, restoring: true);
|
||||
transaction.PreviousCompositionRestored = true;
|
||||
}
|
||||
catch (Exception restoreError)
|
||||
{
|
||||
transaction.RestoreErrorType = restoreError.GetType().FullName;
|
||||
transaction.RestoreErrorMessage = restoreError.Message;
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Failed;
|
||||
transaction.CurrentEntryId = null;
|
||||
throw new ShrinkLoaderRestoreException(applyError, restoreError);
|
||||
}
|
||||
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Failed;
|
||||
transaction.CurrentEntryId = null;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_applying = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask ApplyCoreAsync(IReadOnlyList<ShrinkLoaderEntry> desired,
|
||||
ShrinkLoaderTransactionDiagnostic transaction, bool restoring)
|
||||
{
|
||||
transaction.Phase = restoring
|
||||
? ShrinkLoaderTransactionPhase.Restoring
|
||||
: ShrinkLoaderTransactionPhase.Validating;
|
||||
|
||||
var desiredIds = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var entry in desired)
|
||||
{
|
||||
if (!desiredIds.Add(entry.Id))
|
||||
throw new ShrinkLoaderException($"Duplicate loader entry id: '{entry.Id}'.");
|
||||
if (!entry.Disabled && !_catalog.Contains(entry.Component))
|
||||
throw new ShrinkLoaderException(
|
||||
$"Unknown component '{entry.Component}' requested by entry '{entry.Id}'.");
|
||||
}
|
||||
|
||||
// 1) 消失的条目退役(其依赖者由响应式通知自动停用,条目本身仍被管理)
|
||||
if (!restoring)
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Removing;
|
||||
var removedIds = new List<string>();
|
||||
foreach (var id in _entries.Keys)
|
||||
{
|
||||
@@ -150,6 +262,7 @@ namespace ShrinkContext
|
||||
|
||||
foreach (var id in removedIds)
|
||||
{
|
||||
transaction.CurrentEntryId = id;
|
||||
var managed = _entries[id];
|
||||
if (managed.Fiber != null)
|
||||
await _runtime.RetireAsync(managed.Fiber);
|
||||
@@ -157,8 +270,11 @@ namespace ShrinkContext
|
||||
}
|
||||
|
||||
// 2) 按声明顺序逐条分派
|
||||
if (!restoring)
|
||||
transaction.Phase = ShrinkLoaderTransactionPhase.Applying;
|
||||
foreach (var entry in desired)
|
||||
{
|
||||
transaction.CurrentEntryId = entry.Id;
|
||||
_entries.TryGetValue(entry.Id, out var managed);
|
||||
|
||||
if (entry.Disabled)
|
||||
@@ -170,7 +286,8 @@ namespace ShrinkContext
|
||||
Component = entry.Component,
|
||||
Config = entry.Config,
|
||||
Disabled = true,
|
||||
Isolate = entry.Isolate
|
||||
Isolate = entry.Isolate,
|
||||
Intercept = entry.Intercept
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -179,6 +296,10 @@ namespace ShrinkContext
|
||||
await _runtime.RetireAsync(managed.Fiber);
|
||||
managed.Fiber = null;
|
||||
managed.Disabled = true;
|
||||
managed.Component = entry.Component;
|
||||
managed.Config = entry.Config;
|
||||
managed.Isolate = entry.Isolate;
|
||||
managed.Intercept = entry.Intercept;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -189,28 +310,61 @@ namespace ShrinkContext
|
||||
Equals(managed.Config, entry.Config) &&
|
||||
IsolateEquals(managed.Isolate, entry.Isolate))
|
||||
{
|
||||
if (!InterceptEquals(managed.Intercept, entry.Intercept))
|
||||
{
|
||||
managed.Fiber.Ctx.ReplaceIntercept(entry.Intercept);
|
||||
managed.Intercept = entry.Intercept;
|
||||
}
|
||||
continue; // 幂等:无变化不打扰(被响应式停用的纤程保持管理,等待依赖回归)
|
||||
}
|
||||
|
||||
if (managed?.Fiber != null)
|
||||
{
|
||||
await _runtime.RetireAsync(managed.Fiber);
|
||||
managed.Fiber = null;
|
||||
}
|
||||
|
||||
if (!_catalog.TryCreate(entry.Component, out var component))
|
||||
throw new ShrinkLoaderException(
|
||||
$"Unknown component '{entry.Component}' requested by entry '{entry.Id}'.");
|
||||
throw new ShrinkLoaderException($"Component factory '{entry.Component}' returned no component.");
|
||||
|
||||
var fiber = _runtime.Use(component, entry.Config, _runtime.RootContext, entry.Isolate);
|
||||
var fiber = _runtime.Use(component, entry.Config, _runtime.RootContext, entry.Isolate, entry.Intercept);
|
||||
if (fiber.LastError != null)
|
||||
{
|
||||
var failure = fiber.LastError;
|
||||
await _runtime.RetireAsync(fiber);
|
||||
throw new ShrinkLoaderException(
|
||||
$"Component '{entry.Component}' for entry '{entry.Id}' failed during apply: {failure.Message}");
|
||||
}
|
||||
_entries[entry.Id] = new ManagedEntry
|
||||
{
|
||||
Component = entry.Component,
|
||||
Config = entry.Config,
|
||||
Disabled = false,
|
||||
Isolate = entry.Isolate,
|
||||
Intercept = entry.Intercept,
|
||||
Fiber = fiber
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<ShrinkLoaderEntry> CaptureEntries()
|
||||
{
|
||||
var entries = new List<ShrinkLoaderEntry>(_entries.Count);
|
||||
foreach (var pair in _entries.OrderBy(item => item.Key, StringComparer.Ordinal))
|
||||
{
|
||||
var managed = pair.Value;
|
||||
entries.Add(new ShrinkLoaderEntry(
|
||||
pair.Key,
|
||||
managed.Component,
|
||||
managed.Config,
|
||||
managed.Disabled,
|
||||
managed.Isolate,
|
||||
managed.Intercept));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static bool IsolateEquals(IReadOnlyDictionary<string, string>? a, IReadOnlyDictionary<string, string>? b)
|
||||
{
|
||||
if (a == null && b == null)
|
||||
@@ -228,5 +382,31 @@ namespace ShrinkContext
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool InterceptEquals(
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? a,
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? b)
|
||||
{
|
||||
if (a == null && b == null)
|
||||
return true;
|
||||
if (a == null || b == null || a.Count != b.Count)
|
||||
return false;
|
||||
|
||||
foreach (var keyPair in a)
|
||||
{
|
||||
if (!b.TryGetValue(keyPair.Key, out var otherMetadata) ||
|
||||
keyPair.Value.Count != otherMetadata.Count)
|
||||
return false;
|
||||
|
||||
foreach (var metadataPair in keyPair.Value)
|
||||
{
|
||||
if (!otherMetadata.TryGetValue(metadataPair.Key, out var otherValue) ||
|
||||
!Equals(metadataPair.Value, otherValue))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认运行时入口 + Domain Reload 安全重置。
|
||||
/// 原型阶段不自动启动任何组件;编排方式(配置树加载器)属于后续阶段。
|
||||
/// 不自动启动任何组件;上层 composition root 通过 ShrinkContextLoader 提交期望组合。
|
||||
/// </summary>
|
||||
public static class ShrinkContextDefaults
|
||||
{
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
public enum ShrinkFiberDiagnosticStatus
|
||||
{
|
||||
Inactive = 0,
|
||||
Waiting = 1,
|
||||
Loading = 2,
|
||||
Active = 3,
|
||||
Unloading = 4,
|
||||
Failed = 5,
|
||||
Retired = 6,
|
||||
}
|
||||
|
||||
/// <summary>单个依赖键在快照时刻的解析、提交与潜在提供者。</summary>
|
||||
public sealed class ShrinkDependencyDiagnostic
|
||||
{
|
||||
public ShrinkDependencyDiagnostic(string key, string realm, long? currentProviderUid,
|
||||
long? committedProviderUid, IReadOnlyList<long> potentialProviderUids)
|
||||
{
|
||||
Key = key;
|
||||
Realm = realm;
|
||||
CurrentProviderUid = currentProviderUid;
|
||||
CommittedProviderUid = committedProviderUid;
|
||||
PotentialProviderUids = potentialProviderUids;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
public string Realm { get; }
|
||||
public long? CurrentProviderUid { get; }
|
||||
public long? CommittedProviderUid { get; }
|
||||
public IReadOnlyList<long> PotentialProviderUids { get; }
|
||||
public bool IsSatisfied => CurrentProviderUid.HasValue;
|
||||
}
|
||||
|
||||
/// <summary>一个 fiber 的稳定只读诊断投影,不暴露可变运行时对象。</summary>
|
||||
public sealed class ShrinkFiberDiagnostic
|
||||
{
|
||||
public ShrinkFiberDiagnostic(long uid, string name, ShrinkFiberDiagnosticStatus status,
|
||||
ShrinkFiberState lifecycleState, bool retired, bool inTransition,
|
||||
IReadOnlyList<string> inject, IReadOnlyList<string> provide,
|
||||
IReadOnlyList<long> targetProviderUids,
|
||||
IReadOnlyList<ShrinkDependencyDiagnostic> dependencies,
|
||||
IReadOnlyList<string> interceptKeys,
|
||||
string? errorType, string? errorMessage)
|
||||
{
|
||||
Uid = uid;
|
||||
Name = name;
|
||||
Status = status;
|
||||
LifecycleState = lifecycleState;
|
||||
Retired = retired;
|
||||
InTransition = inTransition;
|
||||
Inject = inject;
|
||||
Provide = provide;
|
||||
TargetProviderUids = targetProviderUids;
|
||||
Dependencies = dependencies;
|
||||
InterceptKeys = interceptKeys;
|
||||
ErrorType = errorType;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public long Uid { get; }
|
||||
public string Name { get; }
|
||||
public ShrinkFiberDiagnosticStatus Status { get; }
|
||||
public ShrinkFiberState LifecycleState { get; }
|
||||
public bool Retired { get; }
|
||||
public bool InTransition { get; }
|
||||
public IReadOnlyList<string> Inject { get; }
|
||||
public IReadOnlyList<string> Provide { get; }
|
||||
public IReadOnlyList<long> TargetProviderUids { get; }
|
||||
public IReadOnlyList<ShrinkDependencyDiagnostic> Dependencies { get; }
|
||||
public IReadOnlyList<string> InterceptKeys { get; }
|
||||
public string? ErrorType { get; }
|
||||
public string? ErrorMessage { get; }
|
||||
}
|
||||
|
||||
public sealed class ShrinkNotificationDiagnostic
|
||||
{
|
||||
public ShrinkNotificationDiagnostic(long dispatchCount, long candidateVisitCount,
|
||||
int lastCandidateCount, int indexedKeyCount)
|
||||
{
|
||||
DispatchCount = dispatchCount;
|
||||
CandidateVisitCount = candidateVisitCount;
|
||||
LastCandidateCount = lastCandidateCount;
|
||||
IndexedKeyCount = indexedKeyCount;
|
||||
}
|
||||
|
||||
public long DispatchCount { get; }
|
||||
public long CandidateVisitCount { get; }
|
||||
public int LastCandidateCount { get; }
|
||||
public int IndexedKeyCount { get; }
|
||||
}
|
||||
|
||||
/// <summary>运行时整体快照:fiber、绑定与 notify 索引状态。</summary>
|
||||
public sealed class ShrinkContextRuntimeDiagnostic
|
||||
{
|
||||
public ShrinkContextRuntimeDiagnostic(IReadOnlyList<ShrinkFiberDiagnostic> fibers,
|
||||
int bindingCount, ShrinkNotificationDiagnostic notifications)
|
||||
{
|
||||
Fibers = fibers;
|
||||
BindingCount = bindingCount;
|
||||
Notifications = notifications;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ShrinkFiberDiagnostic> Fibers { get; }
|
||||
public int BindingCount { get; }
|
||||
public ShrinkNotificationDiagnostic Notifications { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ee8044af3885c84a9b86b101af8ae28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -13,14 +13,20 @@ namespace ShrinkContext
|
||||
/// 约定:
|
||||
/// - 仅在主线程使用;
|
||||
/// - 异步转换在同步可完成路径上内联跑完(无需 PlayerLoop 泵即可确定性测试);
|
||||
/// - 目标(Target)以提供者 uid 集合表示,uid 永不复用,因此“同值不同提供者”的替换也会触发重载。
|
||||
/// - 目标(Target)按 inject 声明顺序记录 provider uid 视图,uid 永不复用,
|
||||
/// 因此“同值不同提供者”的替换及多键 provider 映射变化都能触发重载。
|
||||
/// </summary>
|
||||
public sealed class ShrinkContextRuntime
|
||||
{
|
||||
private readonly ShrinkCtx _rootCtx;
|
||||
private readonly List<ShrinkFiber> _fibers = new();
|
||||
private readonly Dictionary<string, ShrinkBinding> _store = new();
|
||||
private readonly Dictionary<string, HashSet<ShrinkFiber>> _injectIndex =
|
||||
new(StringComparer.Ordinal);
|
||||
private long _uidCounter;
|
||||
private long _notificationDispatchCount;
|
||||
private long _notificationCandidateVisitCount;
|
||||
private int _lastNotificationCandidateCount;
|
||||
|
||||
public ShrinkContextRuntime()
|
||||
{
|
||||
@@ -39,7 +45,8 @@ namespace ShrinkContext
|
||||
/// isolate 为该纤程自身的键解析叠加隔离域(不影响父上下文)。
|
||||
/// </summary>
|
||||
public ShrinkFiber Use(IShrinkComponent component, object? config = null, ShrinkCtx? parentCtx = null,
|
||||
IReadOnlyDictionary<string, string>? isolate = null)
|
||||
IReadOnlyDictionary<string, string>? isolate = null,
|
||||
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? intercept = null)
|
||||
{
|
||||
if (component == null)
|
||||
throw new ArgumentNullException(nameof(component));
|
||||
@@ -49,8 +56,11 @@ namespace ShrinkContext
|
||||
var fiberCtx = parent.CreateChildForFiber(fiber);
|
||||
if (isolate != null && isolate.Count > 0)
|
||||
fiberCtx = fiberCtx.WithIsolate(isolate);
|
||||
if (intercept != null && intercept.Count > 0)
|
||||
fiberCtx = fiberCtx.WithIntercept(intercept);
|
||||
fiber.Ctx = fiberCtx;
|
||||
_fibers.Add(fiber);
|
||||
IndexFiber(fiber);
|
||||
|
||||
var instantiationHandle = new ShrinkEffectHandle();
|
||||
instantiationHandle.AttachInverse(() => RetireCoreAsync(fiber));
|
||||
@@ -68,6 +78,7 @@ namespace ShrinkContext
|
||||
{
|
||||
if (fiber.Retired)
|
||||
return;
|
||||
UnindexFiber(fiber);
|
||||
if (fiber.State == ShrinkFiberState.Inactive && !fiber.InTransition)
|
||||
{
|
||||
fiber.Retired = true;
|
||||
@@ -97,7 +108,7 @@ namespace ShrinkContext
|
||||
// ---------------- 生命周期(论文算法 5) ----------------
|
||||
|
||||
/// <summary>
|
||||
/// 重算目标:任一依赖键不可解析 → ⊥;否则为全部提供者 uid 的有序集合。
|
||||
/// 重算目标:任一依赖键不可解析 → ⊥;否则为按 inject 键顺序记录的 provider uid 视图。
|
||||
/// 目标变化即启动(或在惯性边界链式触发)reload / unload 转换。
|
||||
/// </summary>
|
||||
internal bool Refresh(ShrinkFiber fiber)
|
||||
@@ -222,19 +233,38 @@ namespace ShrinkContext
|
||||
internal IReadOnlyList<ShrinkFiber> Notify(ShrinkCtx sourceCtx, IReadOnlyCollection<string> keys)
|
||||
{
|
||||
var affected = new List<ShrinkFiber>();
|
||||
foreach (var fiber in _fibers.ToArray())
|
||||
if (keys.Count == 0)
|
||||
return affected;
|
||||
|
||||
var candidates = new HashSet<ShrinkFiber>();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (_injectIndex.TryGetValue(key, out var indexed))
|
||||
candidates.UnionWith(indexed);
|
||||
}
|
||||
|
||||
_notificationDispatchCount++;
|
||||
_lastNotificationCandidateCount = candidates.Count;
|
||||
_notificationCandidateVisitCount += candidates.Count;
|
||||
|
||||
foreach (var fiber in candidates.OrderBy(candidate => candidate.Uid))
|
||||
{
|
||||
if (fiber.Retired)
|
||||
continue;
|
||||
|
||||
var realmMatches = false;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!fiber.InjectSet.Contains(key))
|
||||
continue;
|
||||
if (fiber.Ctx.ResolveRealm(key) != sourceCtx.ResolveRealm(key))
|
||||
continue;
|
||||
|
||||
if (Refresh(fiber))
|
||||
affected.Add(fiber);
|
||||
realmMatches = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (realmMatches && Refresh(fiber))
|
||||
affected.Add(fiber);
|
||||
}
|
||||
|
||||
return affected;
|
||||
@@ -242,12 +272,18 @@ namespace ShrinkContext
|
||||
|
||||
// ---------------- 余效应存储(论文算法 2) ----------------
|
||||
|
||||
internal ShrinkEffectHandle SetBinding(ShrinkCtx ctx, string key, object? value)
|
||||
internal ShrinkEffectHandle SetBinding(ShrinkCtx ctx, string key, object? value,
|
||||
IShrinkCoeffectAccessPolicy? accessPolicy)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentException("Key must not be null or empty.", nameof(key));
|
||||
|
||||
var realm = ctx.ResolveRealm(key);
|
||||
var setter = ctx.OwnerFiber;
|
||||
if (setter != null && !setter.ProvideSet.Contains(key))
|
||||
throw new ShrinkUndeclaredSupplyException(key, setter.Name);
|
||||
|
||||
InstallBinding(realm, key, value, setter, ctx);
|
||||
InstallBinding(realm, key, value, setter, ctx, accessPolicy);
|
||||
|
||||
var handle = new ShrinkEffectHandle();
|
||||
handle.AttachInverse(() =>
|
||||
@@ -259,12 +295,13 @@ namespace ShrinkContext
|
||||
return handle;
|
||||
}
|
||||
|
||||
private void InstallBinding(string realm, string key, object? value, ShrinkFiber? setter, ShrinkCtx ctx)
|
||||
private void InstallBinding(string realm, string key, object? value, ShrinkFiber? setter, ShrinkCtx ctx,
|
||||
IShrinkCoeffectAccessPolicy? accessPolicy)
|
||||
{
|
||||
if (_store.TryGetValue(realm, out var existing) && !IsTakeoverAllowed(existing, setter))
|
||||
throw new ShrinkSupplyConflictException(key, existing.Provider, setter);
|
||||
|
||||
_store[realm] = new ShrinkBinding(value, setter, key);
|
||||
_store[realm] = new ShrinkBinding(value, setter, key, accessPolicy);
|
||||
Notify(ctx, new[] { key });
|
||||
}
|
||||
|
||||
@@ -310,6 +347,33 @@ namespace ShrinkContext
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetRaw<T>(ShrinkCtx ctx, ShrinkKey<T> key, out T value) => TryGetRaw(
|
||||
ctx, (key ?? throw new ArgumentNullException(nameof(key))).Id, out value);
|
||||
|
||||
/// <summary>执行 provider 绑定的访问策略;无策略时直接返回原始值。</summary>
|
||||
internal T ResolveAccess<T>(ShrinkCtx ctx, string key, ShrinkBinding binding)
|
||||
{
|
||||
object? value = binding.Value;
|
||||
if (binding.AccessPolicy != null)
|
||||
{
|
||||
var access = new ShrinkCoeffectAccessContext(
|
||||
key,
|
||||
typeof(T),
|
||||
ctx.OwnerFiber,
|
||||
ctx.ResolveInterceptMetadata(key));
|
||||
value = binding.AccessPolicy.Resolve(access, value);
|
||||
}
|
||||
|
||||
if (value is T typed)
|
||||
return typed;
|
||||
if (value == null && (!typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) != null))
|
||||
return default!;
|
||||
|
||||
throw new InvalidCastException(
|
||||
$"Coeffect '{key}' resolved value of type '{value?.GetType().FullName ?? "<null>"}' " +
|
||||
$"but consumer requested '{typeof(T).FullName}'.");
|
||||
}
|
||||
|
||||
// ---------------- 目标计算 ----------------
|
||||
|
||||
private long[]? ComputeTarget(ShrinkFiber fiber)
|
||||
@@ -323,7 +387,9 @@ namespace ShrinkContext
|
||||
uids.Add(binding.Provider?.Uid ?? 0);
|
||||
}
|
||||
|
||||
return uids.Distinct().OrderBy(uid => uid).ToArray();
|
||||
// target 是 key -> provider uid 的视图,不是 uid 集合。保留 inject 声明顺序与重复 uid,
|
||||
// 才能观察两个键在相同提供者集合之间互换的变化。
|
||||
return uids.ToArray();
|
||||
}
|
||||
|
||||
private Dictionary<string, ShrinkBinding> ResolveView(ShrinkFiber fiber)
|
||||
@@ -361,5 +427,102 @@ namespace ShrinkContext
|
||||
return;
|
||||
await RetireAsync(fiber);
|
||||
}
|
||||
|
||||
private void IndexFiber(ShrinkFiber fiber)
|
||||
{
|
||||
foreach (var key in fiber.InjectSet)
|
||||
{
|
||||
if (!_injectIndex.TryGetValue(key, out var fibers))
|
||||
{
|
||||
fibers = new HashSet<ShrinkFiber>();
|
||||
_injectIndex.Add(key, fibers);
|
||||
}
|
||||
|
||||
fibers.Add(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnindexFiber(ShrinkFiber fiber)
|
||||
{
|
||||
foreach (var key in fiber.InjectSet)
|
||||
{
|
||||
if (!_injectIndex.TryGetValue(key, out var fibers))
|
||||
continue;
|
||||
fibers.Remove(fiber);
|
||||
if (fibers.Count == 0)
|
||||
_injectIndex.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>捕获不含可变运行时引用的诊断快照。</summary>
|
||||
public ShrinkContextRuntimeDiagnostic CaptureDiagnostic()
|
||||
{
|
||||
var diagnostics = new List<ShrinkFiberDiagnostic>(_fibers.Count);
|
||||
foreach (var fiber in _fibers.OrderBy(item => item.Uid))
|
||||
{
|
||||
var dependencies = new List<ShrinkDependencyDiagnostic>();
|
||||
foreach (var key in fiber.Component.Inject ?? Array.Empty<string>())
|
||||
{
|
||||
var realm = fiber.Ctx.ResolveRealm(key);
|
||||
var current = ResolveBinding(fiber.Ctx, key);
|
||||
ShrinkBinding? committed = null;
|
||||
fiber.Committed?.TryGetValue(key, out committed);
|
||||
var potential = _fibers
|
||||
.Where(candidate => !candidate.Retired && candidate.ProvideSet.Contains(key) &&
|
||||
candidate.Ctx.ResolveRealm(key) == realm)
|
||||
.Select(candidate => candidate.Uid)
|
||||
.OrderBy(uid => uid)
|
||||
.ToArray();
|
||||
|
||||
dependencies.Add(new ShrinkDependencyDiagnostic(
|
||||
key,
|
||||
realm,
|
||||
current?.Provider?.Uid,
|
||||
committed?.Provider?.Uid,
|
||||
potential));
|
||||
}
|
||||
|
||||
diagnostics.Add(new ShrinkFiberDiagnostic(
|
||||
fiber.Uid,
|
||||
fiber.Name,
|
||||
GetDiagnosticStatus(fiber),
|
||||
fiber.State,
|
||||
fiber.Retired,
|
||||
fiber.InTransition,
|
||||
(fiber.Component.Inject ?? Array.Empty<string>()).ToArray(),
|
||||
(fiber.Component.Provide ?? Array.Empty<string>()).ToArray(),
|
||||
fiber.Target?.ToArray() ?? Array.Empty<long>(),
|
||||
dependencies,
|
||||
fiber.Ctx.GetLocalInterceptKeys().OrderBy(key => key, StringComparer.Ordinal).ToArray(),
|
||||
fiber.LastError?.GetType().FullName,
|
||||
fiber.LastError?.Message));
|
||||
}
|
||||
|
||||
return new ShrinkContextRuntimeDiagnostic(
|
||||
diagnostics,
|
||||
_store.Count,
|
||||
new ShrinkNotificationDiagnostic(
|
||||
_notificationDispatchCount,
|
||||
_notificationCandidateVisitCount,
|
||||
_lastNotificationCandidateCount,
|
||||
_injectIndex.Count));
|
||||
}
|
||||
|
||||
private static ShrinkFiberDiagnosticStatus GetDiagnosticStatus(ShrinkFiber fiber)
|
||||
{
|
||||
if (fiber.Retired)
|
||||
return ShrinkFiberDiagnosticStatus.Retired;
|
||||
if (fiber.LastError != null)
|
||||
return ShrinkFiberDiagnosticStatus.Failed;
|
||||
|
||||
return fiber.State switch
|
||||
{
|
||||
ShrinkFiberState.Loading => ShrinkFiberDiagnosticStatus.Loading,
|
||||
ShrinkFiberState.Active => ShrinkFiberDiagnosticStatus.Active,
|
||||
ShrinkFiberState.Unloading => ShrinkFiberDiagnosticStatus.Unloading,
|
||||
ShrinkFiberState.Inactive when fiber.InjectSet.Count > 0 => ShrinkFiberDiagnosticStatus.Waiting,
|
||||
_ => ShrinkFiberDiagnosticStatus.Inactive,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user