From ff04a4fbad9f44952db996adb44d0c7bcf8f594b Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Thu, 7 May 2026 10:03:16 +0800
Subject: [PATCH] =?UTF-8?q?fix(core):=20=E8=A1=A5=E9=BD=90=E6=9E=B6?=
=?UTF-8?q?=E6=9E=84=E9=94=80=E6=AF=81=E5=90=8E=E7=9A=84=E4=B8=8A=E4=B8=8B?=
=?UTF-8?q?=E6=96=87=E8=A7=A3=E7=BB=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 Architecture 销毁后 GameContext 仍保留活动上下文的问题
- 补充生命周期回归测试并验证失败初始化后的解绑路径
- 收口生成器文档中的多架构表述并更新 ai-plan 追踪
---
.../ArchitectureLifecycleBehaviorTests.cs | 63 +++++++++++++++++++
GFramework.Core/Architectures/Architecture.cs | 11 +++-
.../todos/single-context-priority-tracking.md | 8 ++-
.../traces/single-context-priority-trace.md | 14 ++++-
.../context-get-generator.md | 8 +--
5 files changed, 97 insertions(+), 7 deletions(-)
diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
index cae82837..e5eb0e1f 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
@@ -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
}));
}
+ ///
+ /// 验证架构销毁后会解除全局 GameContext 绑定。
+ /// 该回归测试用于防止已销毁架构继续充当默认上下文回退入口。
+ ///
+ [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(() => GameContext.GetByType(architecture.GetType()));
+ Assert.Throws(() => GameContext.GetFirstArchitectureContext());
+ }
+
+ ///
+ /// 验证失败初始化后的销毁同样会解除全局上下文绑定。
+ ///
+ [Test]
+ public async Task DestroyAsync_After_FailedInitialization_Should_Unbind_Context_From_GameContext()
+ {
+ var destroyOrder = new List();
+ var architecture = new FailingInitializationArchitecture(destroyOrder);
+
+ var exception = Assert.ThrowsAsync(() => architecture.InitializeAsync());
+ Assert.That(exception, Is.Not.Null);
+ Assert.That(GameContext.GetByType(architecture.GetType()), Is.SameAs(architecture.Context));
+
+ await architecture.DestroyAsync();
+
+ Assert.Throws(() => GameContext.GetByType(architecture.GetType()));
+ Assert.Throws(() => GameContext.GetFirstArchitectureContext());
+ }
+
+ ///
+ /// 验证销毁后的新 ContextAware 实例不会再通过全局回退命中过期上下文。
+ ///
+ [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(() => probe.GetContext());
+ }
+
///
/// 验证启用 AllowLateRegistration 时,生命周期层会立即初始化后注册的组件,而不是继续沿用初始化期的拒绝策略。
/// 由于公共架构 API 在 Ready 之后会先触发容器限制,此回归测试直接覆盖生命周期协作者的对齐逻辑。
@@ -232,6 +288,13 @@ public class ArchitectureLifecycleBehaviorTests
}
}
+ ///
+ /// 仅用于验证销毁后全局上下文回退是否仍然泄漏的最小 ContextAware 探针。
+ ///
+ private sealed class LifecycleContextAwareProbe : ContextAwareBase
+ {
+ }
+
///
/// 在初始化时注册可销毁组件的测试架构。
///
diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs
index 8bb71c62..7fa66b1d 100644
--- a/GFramework.Core/Architectures/Architecture.cs
+++ b/GFramework.Core/Architectures/Architecture.cs
@@ -361,7 +361,16 @@ public abstract class Architecture : IArchitecture
///
public virtual async ValueTask DestroyAsync()
{
- await _lifecycle.DestroyAsync().ConfigureAwait(false);
+ try
+ {
+ await _lifecycle.DestroyAsync().ConfigureAwait(false);
+ }
+ finally
+ {
+ // 架构初始化时会把当前实例绑定到 GameContext;销毁后必须解除该全局回退入口,
+ // 避免后续惰性 ContextAware 调用继续命中过期的运行时上下文。
+ GameContext.Unbind(GetType());
+ }
}
///
diff --git a/ai-plan/public/single-context-priority/todos/single-context-priority-tracking.md b/ai-plan/public/single-context-priority/todos/single-context-priority-tracking.md
index 623a6c4e..b25cc74f 100644
--- a/ai-plan/public/single-context-priority/todos/single-context-priority-tracking.md
+++ b/ai-plan/public/single-context-priority/todos/single-context-priority-tracking.md
@@ -16,6 +16,7 @@
- `GameContext` 已从“字典首个枚举值即默认上下文”收敛为“单活动上下文 + 类型别名兼容查找”;同一全局上下文表不再允许并存两个不同上下文实例
- `MicrosoftDiContainer` 的预冻结 `Get()` / `Get(Type)` 已改为复用实例可见性收集逻辑,和 `GetAll*` 的实例暴露规则保持一致
- `IIocContainer` XML 文档已明确预冻结查询与 `Contains()` 的契约边界,避免把注册阶段查询误读为完整 DI 激活语义
+ - `Architecture.DestroyAsync()` 现会在生命周期销毁完成后显式解除 `GameContext` 绑定,防止已销毁架构继续充当默认上下文回退入口
- 当前分支从 `main` 创建,已完成 `git pull --ff-only origin main`
## 当前活跃事实
@@ -33,6 +34,7 @@
- `GameContext` 是公开静态入口,任何“允许多个不同上下文并存”的现有测试都需要按单活动上下文语义重写
- `Contains()` 在预冻结阶段目前更接近“是否存在注册”,不等同于“是否能立即解析实例”;本轮若不改其行为,需要在文档和测试中明确这一点
- `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”的设计与测试
diff --git a/ai-plan/public/single-context-priority/traces/single-context-priority-trace.md b/ai-plan/public/single-context-priority/traces/single-context-priority-trace.md
index 6ae13dcd..44273c0b 100644
--- a/ai-plan/public/single-context-priority/traces/single-context-priority-trace.md
+++ b/ai-plan/public/single-context-priority/traces/single-context-priority-trace.md
@@ -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 的进一步简化空间
diff --git a/docs/zh-CN/source-generators/context-get-generator.md b/docs/zh-CN/source-generators/context-get-generator.md
index b6c62237..1f6d347d 100644
--- a/docs/zh-CN/source-generators/context-get-generator.md
+++ b/docs/zh-CN/source-generators/context-get-generator.md
@@ -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));
}
}