fix(coroutine): 收口 Timing 测试宿主清理

- 修复 Timing 共享单例仅在当前实例持有引用时才清理,避免测试宿主误伤其他实例
- 新增 TimingTests 的 NonParallelizable 约束,避免静态实例槽位并发污染
- 更新 coroutine optimization 跟踪与 trace,记录 PR #259 review 收口与验证结果
This commit is contained in:
GeWuYou 2026-04-20 10:19:11 +08:00
parent 9576e0f8bd
commit d369118351
5 changed files with 25 additions and 7 deletions

View File

@ -10,6 +10,7 @@ namespace GFramework.Godot.Tests.Coroutine;
/// 验证 <see cref="Timing" /> 在纯托管测试宿主下仍保持与真实 Godot 生命周期一致的阶段语义。 /// 验证 <see cref="Timing" /> 在纯托管测试宿主下仍保持与真实 Godot 生命周期一致的阶段语义。
/// </summary> /// </summary>
[TestFixture] [TestFixture]
[NonParallelizable]
public sealed class TimingTests public sealed class TimingTests
{ {
private Timing _timing = null!; private Timing _timing = null!;

View File

@ -119,6 +119,7 @@ public partial class Timing
/// <summary> /// <summary>
/// 清理测试初始化留下的实例槽位与调度器状态,避免跨测试污染静态单例表。 /// 清理测试初始化留下的实例槽位与调度器状态,避免跨测试污染静态单例表。
/// 仅当当前测试宿主仍持有共享单例引用时才会清理 `_instance`,以免误伤同进程内的其他宿主。
/// </summary> /// </summary>
internal void DisposeForTests() internal void DisposeForTests()
{ {
@ -130,7 +131,7 @@ public partial class Timing
ActiveInstances[_instanceId] = null; ActiveInstances[_instanceId] = null;
} }
CleanupInstanceIfNecessary(); CleanupInstanceIfNecessary(this);
_processScheduler = null; _processScheduler = null;
_processIgnorePauseScheduler = null; _processIgnorePauseScheduler = null;

View File

@ -185,15 +185,19 @@ public partial class Timing : Node
ActiveInstances[_instanceId] = null; ActiveInstances[_instanceId] = null;
} }
CleanupInstanceIfNecessary(); CleanupInstanceIfNecessary(this);
} }
/// <summary> /// <summary>
/// 清理单例引用 /// 仅在当前实例仍持有共享单例引用时清理它,避免多宿主场景误清其他实例
/// </summary> /// </summary>
private static void CleanupInstanceIfNecessary() /// <param name="instance">正在退出生命周期的实例。</param>
private static void CleanupInstanceIfNecessary(Timing instance)
{ {
_instance = null; if (ReferenceEquals(_instance, instance))
{
_instance = null;
}
} }
/// <summary> /// <summary>

View File

@ -12,6 +12,7 @@
- 当前焦点: - 当前焦点:
- 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造 - 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造
- 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`锁定暂停、segment 路由和阶段型等待指令的回归覆盖 - 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`锁定暂停、segment 路由和阶段型等待指令的回归覆盖
- 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行
- 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义或转入文档迁移收口不再回到“Godot 宿主没有自动化回归”的旧状态 - 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义或转入文档迁移收口不再回到“Godot 宿主没有自动化回归”的旧状态
## 当前状态摘要 ## 当前状态摘要
@ -32,6 +33,9 @@
- 暂停时 `Process` / `ProcessIgnorePause` 的差异 - 暂停时 `Process` / `ProcessIgnorePause` 的差异
- `Process` / `PhysicsProcess` / `DeferredProcess` 的推进边界 - `Process` / `PhysicsProcess` / `DeferredProcess` 的推进边界
- `WaitForFixedUpdate``WaitForEndOfFrame` 的阶段型等待语义 - `WaitForFixedUpdate``WaitForEndOfFrame` 的阶段型等待语义
- 针对 PR #259 的最新未解决 review 线程,已补充两项收口:
- `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染
- `Timing` 的测试清理与运行时退树清理现仅在当前实例持有共享 `_instance` 引用时才会清空单例状态
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` 当前通过,合计 `58` 个测试 - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` 当前通过,合计 `58` 个测试
## 当前风险 ## 当前风险
@ -58,9 +62,12 @@
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore`
- 结果:通过 - 结果:通过
- 备注Godot 测试项目共 `58` 个测试全部通过 - 备注Godot 测试项目共 `58` 个测试全部通过
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
- 结果:通过
- 备注:针对 PR #259 review 修复后的 `TimingTests``5` 个测试全部通过
## 下一步 ## 下一步
1. 若继续补验证,优先只做真实场景树相关的节点归属 / 退树 / `queue_free` 回归,不再重新设计 `Timing` 纯托管宿主 1. 若继续补验证,优先只做真实场景树相关的节点归属 / 退树 / `queue_free` 回归,不再重新设计 `Timing` 纯托管宿主
2. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照 2. 当前 PR 合并前可直接回到 GitHub 处理这两条 review 线程的回复与 resolve避免后续重复审阅同一问题
3. 若下一轮涉及更大范围语义调整,再单独开新恢复点,避免把测试、文档和 API 改动重新混成一次任务 3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照

View File

@ -50,6 +50,11 @@
- 完成验证: - 完成验证:
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore` - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore`
- 同日根据 PR #259 的最新 unresolved CodeRabbit review 继续收口:
- 在 `GFramework.Godot.Tests/Coroutine/TimingTests.cs` 为 fixture 添加 `[NonParallelizable]`,避免静态实例槽位在 NUnit 并行执行时互相污染
- 将 `Timing``_instance` 清理改为“仅当当前实例仍持有共享单例引用时才执行”,同时覆盖运行时 `_ExitTree()` 与测试入口 `DisposeForTests()`
- 额外完成验证:
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore`
### 下一步 ### 下一步