feat(cordis): 完成阶段5访问介导与诊断
This commit is contained in:
@@ -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