From d369118351d00bd937fa0c5e05f090bfac3c18c8 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:19:11 +0800
Subject: [PATCH] =?UTF-8?q?fix(coroutine):=20=E6=94=B6=E5=8F=A3=20Timing?=
=?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E5=AE=BF=E4=B8=BB=E6=B8=85=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 Timing 共享单例仅在当前实例持有引用时才清理,避免测试宿主误伤其他实例
- 新增 TimingTests 的 NonParallelizable 约束,避免静态实例槽位并发污染
- 更新 coroutine optimization 跟踪与 trace,记录 PR #259 review 收口与验证结果
---
GFramework.Godot.Tests/Coroutine/TimingTests.cs | 1 +
GFramework.Godot/Coroutine/Timing.Testing.cs | 3 ++-
GFramework.Godot/Coroutine/Timing.cs | 12 ++++++++----
.../todos/coroutine-optimization-tracking.md | 11 +++++++++--
.../traces/coroutine-optimization-trace.md | 5 +++++
5 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/GFramework.Godot.Tests/Coroutine/TimingTests.cs b/GFramework.Godot.Tests/Coroutine/TimingTests.cs
index bbfc7561..5db137ca 100644
--- a/GFramework.Godot.Tests/Coroutine/TimingTests.cs
+++ b/GFramework.Godot.Tests/Coroutine/TimingTests.cs
@@ -10,6 +10,7 @@ namespace GFramework.Godot.Tests.Coroutine;
/// 验证 在纯托管测试宿主下仍保持与真实 Godot 生命周期一致的阶段语义。
///
[TestFixture]
+[NonParallelizable]
public sealed class TimingTests
{
private Timing _timing = null!;
diff --git a/GFramework.Godot/Coroutine/Timing.Testing.cs b/GFramework.Godot/Coroutine/Timing.Testing.cs
index 3aea3a1c..ea9a24ba 100644
--- a/GFramework.Godot/Coroutine/Timing.Testing.cs
+++ b/GFramework.Godot/Coroutine/Timing.Testing.cs
@@ -119,6 +119,7 @@ public partial class Timing
///
/// 清理测试初始化留下的实例槽位与调度器状态,避免跨测试污染静态单例表。
+ /// 仅当当前测试宿主仍持有共享单例引用时才会清理 `_instance`,以免误伤同进程内的其他宿主。
///
internal void DisposeForTests()
{
@@ -130,7 +131,7 @@ public partial class Timing
ActiveInstances[_instanceId] = null;
}
- CleanupInstanceIfNecessary();
+ CleanupInstanceIfNecessary(this);
_processScheduler = null;
_processIgnorePauseScheduler = null;
diff --git a/GFramework.Godot/Coroutine/Timing.cs b/GFramework.Godot/Coroutine/Timing.cs
index d4d0bf51..a2094ffc 100644
--- a/GFramework.Godot/Coroutine/Timing.cs
+++ b/GFramework.Godot/Coroutine/Timing.cs
@@ -185,15 +185,19 @@ public partial class Timing : Node
ActiveInstances[_instanceId] = null;
}
- CleanupInstanceIfNecessary();
+ CleanupInstanceIfNecessary(this);
}
///
- /// 清理单例引用。
+ /// 仅在当前实例仍持有共享单例引用时清理它,避免多宿主场景误清其他实例。
///
- private static void CleanupInstanceIfNecessary()
+ /// 正在退出生命周期的实例。
+ private static void CleanupInstanceIfNecessary(Timing instance)
{
- _instance = null;
+ if (ReferenceEquals(_instance, instance))
+ {
+ _instance = null;
+ }
}
///
diff --git a/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md b/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md
index 80a91b68..8a16f54b 100644
--- a/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md
+++ b/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md
@@ -12,6 +12,7 @@
- 当前焦点:
- 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造
- 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`,锁定暂停、segment 路由和阶段型等待指令的回归覆盖
+ - 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行
- 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义,或转入文档迁移收口,不再回到“Godot 宿主没有自动化回归”的旧状态
## 当前状态摘要
@@ -32,6 +33,9 @@
- 暂停时 `Process` / `ProcessIgnorePause` 的差异
- `Process` / `PhysicsProcess` / `DeferredProcess` 的推进边界
- `WaitForFixedUpdate` 与 `WaitForEndOfFrame` 的阶段型等待语义
+- 针对 PR #259 的最新未解决 review 线程,已补充两项收口:
+ - `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染
+ - `Timing` 的测试清理与运行时退树清理现仅在当前实例持有共享 `_instance` 引用时才会清空单例状态
- `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`
- 结果:通过
- 备注: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` 纯托管宿主
-2. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照
-3. 若下一轮涉及更大范围语义调整,再单独开新恢复点,避免把测试、文档和 API 改动重新混成一次任务
+2. 当前 PR 合并前可直接回到 GitHub 处理这两条 review 线程的回复与 resolve,避免后续重复审阅同一问题
+3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照
diff --git a/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md b/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md
index 4b1f4f93..97d5cc22 100644
--- a/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md
+++ b/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md
@@ -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 --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`
### 下一步