mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-08 01:24:31 +08:00
fix(core): 补齐架构销毁后的上下文解绑
- 修复 Architecture 销毁后 GameContext 仍保留活动上下文的问题 - 补充生命周期回归测试并验证失败初始化后的解绑路径 - 收口生成器文档中的多架构表述并更新 ai-plan 追踪
This commit is contained in:
parent
e3fa0db992
commit
ff04a4fbad
@ -6,11 +6,13 @@ using GFramework.Core.Abstractions.Architectures;
|
||||
using GFramework.Core.Abstractions.Enums;
|
||||
using GFramework.Core.Abstractions.Lifecycle;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Core.Abstractions.Rule;
|
||||
using GFramework.Core.Abstractions.Model;
|
||||
using GFramework.Core.Abstractions.Systems;
|
||||
using GFramework.Core.Abstractions.Utility;
|
||||
using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Core.Rule;
|
||||
|
||||
namespace GFramework.Core.Tests.Architectures;
|
||||
|
||||
@ -181,6 +183,60 @@ public class ArchitectureLifecycleBehaviorTests
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证架构销毁后会解除全局 GameContext 绑定。
|
||||
/// 该回归测试用于防止已销毁架构继续充当默认上下文回退入口。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task DestroyAsync_Should_Unbind_Context_From_GameContext()
|
||||
{
|
||||
var architecture = new PhaseTrackingArchitecture();
|
||||
|
||||
await architecture.InitializeAsync();
|
||||
|
||||
Assert.That(GameContext.GetByType(architecture.GetType()), Is.SameAs(architecture.Context));
|
||||
|
||||
await architecture.DestroyAsync();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => GameContext.GetByType(architecture.GetType()));
|
||||
Assert.Throws<InvalidOperationException>(() => GameContext.GetFirstArchitectureContext());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证失败初始化后的销毁同样会解除全局上下文绑定。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task DestroyAsync_After_FailedInitialization_Should_Unbind_Context_From_GameContext()
|
||||
{
|
||||
var destroyOrder = new List<string>();
|
||||
var architecture = new FailingInitializationArchitecture(destroyOrder);
|
||||
|
||||
var exception = Assert.ThrowsAsync<InvalidOperationException>(() => architecture.InitializeAsync());
|
||||
Assert.That(exception, Is.Not.Null);
|
||||
Assert.That(GameContext.GetByType(architecture.GetType()), Is.SameAs(architecture.Context));
|
||||
|
||||
await architecture.DestroyAsync();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => GameContext.GetByType(architecture.GetType()));
|
||||
Assert.Throws<InvalidOperationException>(() => GameContext.GetFirstArchitectureContext());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证销毁后的新 ContextAware 实例不会再通过全局回退命中过期上下文。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task DestroyAsync_Should_Prevent_New_ContextAware_Fallback_From_Using_Destroyed_Context()
|
||||
{
|
||||
var architecture = new PhaseTrackingArchitecture();
|
||||
|
||||
await architecture.InitializeAsync();
|
||||
await architecture.DestroyAsync();
|
||||
|
||||
IContextAware probe = new LifecycleContextAwareProbe();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => probe.GetContext());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证启用 AllowLateRegistration 时,生命周期层会立即初始化后注册的组件,而不是继续沿用初始化期的拒绝策略。
|
||||
/// 由于公共架构 API 在 Ready 之后会先触发容器限制,此回归测试直接覆盖生命周期协作者的对齐逻辑。
|
||||
@ -232,6 +288,13 @@ public class ArchitectureLifecycleBehaviorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅用于验证销毁后全局上下文回退是否仍然泄漏的最小 ContextAware 探针。
|
||||
/// </summary>
|
||||
private sealed class LifecycleContextAwareProbe : ContextAwareBase
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在初始化时注册可销毁组件的测试架构。
|
||||
/// </summary>
|
||||
|
||||
@ -361,7 +361,16 @@ public abstract class Architecture : IArchitecture
|
||||
/// </summary>
|
||||
public virtual async ValueTask DestroyAsync()
|
||||
{
|
||||
await _lifecycle.DestroyAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await _lifecycle.DestroyAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 架构初始化时会把当前实例绑定到 GameContext;销毁后必须解除该全局回退入口,
|
||||
// 避免后续惰性 ContextAware 调用继续命中过期的运行时上下文。
|
||||
GameContext.Unbind(GetType());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
- `GameContext` 已从“字典首个枚举值即默认上下文”收敛为“单活动上下文 + 类型别名兼容查找”;同一全局上下文表不再允许并存两个不同上下文实例
|
||||
- `MicrosoftDiContainer` 的预冻结 `Get<T>()` / `Get(Type)` 已改为复用实例可见性收集逻辑,和 `GetAll*` 的实例暴露规则保持一致
|
||||
- `IIocContainer` XML 文档已明确预冻结查询与 `Contains<T>()` 的契约边界,避免把注册阶段查询误读为完整 DI 激活语义
|
||||
- `Architecture.DestroyAsync()` 现会在生命周期销毁完成后显式解除 `GameContext` 绑定,防止已销毁架构继续充当默认上下文回退入口
|
||||
- 当前分支从 `main` 创建,已完成 `git pull --ff-only origin main`
|
||||
|
||||
## 当前活跃事实
|
||||
@ -33,6 +34,7 @@
|
||||
- `GameContext` 是公开静态入口,任何“允许多个不同上下文并存”的现有测试都需要按单活动上下文语义重写
|
||||
- `Contains<T>()` 在预冻结阶段目前更接近“是否存在注册”,不等同于“是否能立即解析实例”;本轮若不改其行为,需要在文档和测试中明确这一点
|
||||
- `ResolveCqrsRegistrationService()` 仍要求注册阶段对 `ICqrsRegistrationService` 可见的是实例绑定;若后续改成工厂或实现类型注册,需要额外设计注册阶段激活 helper
|
||||
- 现有解绑逻辑通过 `Architecture.GetType()` 移除初始化期绑定;若后续引入更多显式上下文别名,需同步评估是否要在销毁时额外移除这些别名
|
||||
|
||||
## 最近权威验证
|
||||
|
||||
@ -46,8 +48,12 @@
|
||||
- 结果:通过,`92/92` passed
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~SyncArchitectureTests|FullyQualifiedName~AsyncArchitectureTests|FullyQualifiedName~ArchitectureInitializationPipelineTests|FullyQualifiedName~ContextAwareTests"`
|
||||
- 结果:通过,`32/32` passed
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release`
|
||||
- 结果:再次通过,`0 warning / 0 error`
|
||||
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 若后续继续推进,可评估 `Architecture` 销毁阶段是否要显式解绑 `GameContext` 当前活动上下文
|
||||
1. 若后续继续推进,可评估是否要把 `GameContext.ArchitectureReadOnlyDictionary` 标记为兼容层,并收口其公开使用面
|
||||
2. 若 CQRS runtime seam 计划改成工厂式注册,再单独补“注册阶段激活 helper”的设计与测试
|
||||
|
||||
@ -27,6 +27,18 @@
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~GameContextTests|FullyQualifiedName~ContextProviderTests|FullyQualifiedName~ContextAwareTests|FullyQualifiedName~MicrosoftDiContainerTests|FullyQualifiedName~IocContainerLifetimeTests|FullyQualifiedName~ArchitectureInitializationPipelineTests"` 通过,`92/92` passed
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` 通过,`0 warning / 0 error`
|
||||
|
||||
### 阶段:销毁闭环与文档收口完成(SINGLE-CONTEXT-PRIORITY-RP-002)
|
||||
|
||||
- 实现摘要:
|
||||
- `Architecture.DestroyAsync()` 新增 `finally` 解绑,确保销毁完成后自动从 `GameContext` 移除当前架构类型绑定
|
||||
- `ArchitectureLifecycleBehaviorTests` 新增销毁解绑、失败初始化后解绑、以及销毁后新 `ContextAwareBase` 实例不再回退到过期上下文的回归测试
|
||||
- `docs/zh-CN/source-generators/context-get-generator.md` 已把“多架构场景”改写为“自定义上下文来源”,收口对全局多架构并存的暗示
|
||||
- 测试与验证:
|
||||
- `python3 scripts/license-header.py --check` 通过
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~SyncArchitectureTests|FullyQualifiedName~AsyncArchitectureTests|FullyQualifiedName~ArchitectureInitializationPipelineTests|FullyQualifiedName~ContextAwareTests"` 通过,`32/32` passed
|
||||
- 首次并发执行 `dotnet test` 与 `dotnet build` 时出现 `bin/Release` 文件占用导致的 MSBuild copy 冲突;按仓库规则改为单独重跑直接命令后结果通过
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` 单独重跑通过,`0 warning / 0 error`
|
||||
|
||||
### 当前下一步
|
||||
|
||||
1. 若后续要进一步彻底移除全局回退,可单独评估 `Architecture` 销毁解绑与 `GameContext` 公开别名字典的收口策略
|
||||
1. 若后续要进一步彻底移除全局回退,可单独评估 `GameContext` 公开别名字典的收口策略与生成器默认 provider 的进一步简化空间
|
||||
|
||||
@ -651,7 +651,7 @@ public partial class GameController
|
||||
|
||||
- 运行时条件分支控制的注册
|
||||
- 反射、配置驱动或外部程序集动态注册
|
||||
- 无法唯一判定组件归属架构的多架构场景
|
||||
- 需要自定义 provider 或外部切换逻辑才能判定上下文来源的场景
|
||||
|
||||
## 高级场景
|
||||
|
||||
@ -685,9 +685,9 @@ public partial class Controller
|
||||
}
|
||||
```
|
||||
|
||||
### 多架构场景
|
||||
### 自定义上下文来源
|
||||
|
||||
在多架构场景中,可以通过 `SetContextProvider` 切换架构:
|
||||
当默认的当前活动上下文不适用时,可以通过 `SetContextProvider` 显式切换上下文来源:
|
||||
|
||||
```csharp
|
||||
[ContextAware]
|
||||
@ -698,7 +698,7 @@ public partial class GameController
|
||||
|
||||
public static void SetArchitecture(IArchitecture architecture)
|
||||
{
|
||||
// 切换架构提供者
|
||||
// 显式切换当前类型使用的上下文来源
|
||||
SetContextProvider(new CustomContextProvider(architecture));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user