mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(core): 修复 PR review 回归问题
- 修复 CoroutineScheduler 在零初始容量下的扩容边界并补充回归测试 - 修复 Store dispatch 快照阶段的异常回滚逻辑并补充异常安全测试 - 更新 analyzer-warning-reduction 的 tracking 与 trace 以记录 RP-010 验证结果
This commit is contained in:
parent
c61ee140a1
commit
aa78dfbf51
@ -345,6 +345,20 @@ public class CoroutineSchedulerTests
|
||||
Assert.That(_scheduler.ActiveCoroutineCount, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证调度器在零初始容量下会在首次启动协程时自动扩容,而不是写入越界。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Run_Should_Grow_From_Zero_Initial_Capacity()
|
||||
{
|
||||
var scheduler = new CoroutineScheduler(new TestTimeSource(), initialCapacity: 0);
|
||||
|
||||
var handle = scheduler.Run(CreateYieldingCoroutine(new WaitOneFrame()));
|
||||
|
||||
Assert.That(handle.IsValid, Is.True);
|
||||
Assert.That(scheduler.ActiveCoroutineCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证协程调度器应该使用提供的时间源
|
||||
/// </summary>
|
||||
@ -563,4 +577,4 @@ public class TestTimeSource : ITimeSource
|
||||
DeltaTime = 0.1;
|
||||
CurrentTime += DeltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GFramework.Core.Tests.StateManagement;
|
||||
|
||||
/// <summary>
|
||||
@ -384,6 +387,32 @@ public class StoreTests
|
||||
Assert.That(logs, Is.EqualTo(new[] { "dynamic:before", "dynamic:after" }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试当 dispatch 作用域在快照阶段抛出异常时,Store 不会残留“正在分发”标记而锁死后续调用。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws()
|
||||
{
|
||||
var store = new Store<CounterState>(
|
||||
new CounterState(0, "Player"),
|
||||
actionMatchingMode: StoreActionMatchingMode.IncludeAssignableTypes);
|
||||
|
||||
store.RegisterReducer<IncrementAction>((state, action) => state with { Count = state.Count + action.Amount });
|
||||
|
||||
var reducers = GetReducersDictionary(store);
|
||||
var throwingActionType = new ThrowingAssignableType(typeof(IncrementAction), "simulated reducer snapshot failure");
|
||||
reducers.Add(throwingActionType, CreateEmptyReducerRegistrationList(reducers));
|
||||
|
||||
Assert.That(
|
||||
() => store.Dispatch(new IncrementAction(1)),
|
||||
Throws.InvalidOperationException.With.Message.EqualTo("simulated reducer snapshot failure"));
|
||||
|
||||
reducers.Remove(throwingActionType);
|
||||
|
||||
Assert.That(() => store.Dispatch(new IncrementAction(1)), Throws.Nothing);
|
||||
Assert.That(store.State.Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试未命中的 action 仍会记录诊断信息,但不会改变状态。
|
||||
/// </summary>
|
||||
@ -681,6 +710,32 @@ public class StoreTests
|
||||
return store;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 Store 内部 reducer 字典,以便在异常安全回归测试中注入受控的异常源。
|
||||
/// </summary>
|
||||
/// <param name="store">要读取的 Store 实例。</param>
|
||||
/// <returns>Store 当前持有的 reducer 字典引用。</returns>
|
||||
private static IDictionary GetReducersDictionary(Store<CounterState> store)
|
||||
{
|
||||
var reducersField = typeof(Store<CounterState>).GetField("_reducers", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("Unable to locate Store reducer dictionary field.");
|
||||
|
||||
return (IDictionary)(reducersField.GetValue(store)
|
||||
?? throw new InvalidOperationException("Store reducer dictionary should not be null."));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建与 Store 私有 reducer 注册列表兼容的空列表实例。
|
||||
/// </summary>
|
||||
/// <param name="reducers">现有 reducer 字典,用于推断私有列表元素类型。</param>
|
||||
/// <returns>可写入私有 reducer 字典的空列表。</returns>
|
||||
private static object CreateEmptyReducerRegistrationList(IDictionary reducers)
|
||||
{
|
||||
var valueType = reducers.GetType().GenericTypeArguments[1];
|
||||
return Activator.CreateInstance(valueType)
|
||||
?? throw new InvalidOperationException("Unable to create an empty reducer registration list.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于测试的计数器状态。
|
||||
/// 使用 record 保持逻辑不可变语义,便于 Store 基于状态快照进行比较和断言。
|
||||
@ -876,4 +931,20 @@ public class StoreTests
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于在回归测试中稳定模拟 <see cref="Type.IsAssignableFrom(Type)"/> 失败的代理类型。
|
||||
/// </summary>
|
||||
private sealed class ThrowingAssignableType(Type delegatingType, string message) : TypeDelegator(delegatingType)
|
||||
{
|
||||
/// <summary>
|
||||
/// 在 Store 创建 reducer 快照时抛出受控异常,验证 dispatch 作用域能够正确回滚。
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">待比较的 action 运行时类型。</param>
|
||||
/// <returns>此实现不会正常返回。</returns>
|
||||
public override bool IsAssignableFrom(Type? typeInfo)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ namespace GFramework.Core.Coroutine;
|
||||
/// </remarks>
|
||||
/// <param name="timeSource">缩放时间源,提供调度器默认推进所使用的时间数据。</param>
|
||||
/// <param name="instanceId">协程实例编号,用于生成带宿主前缀的句柄。</param>
|
||||
/// <param name="initialCapacity">调度器初始槽位容量。</param>
|
||||
/// <param name="initialCapacity">调度器初始槽位容量;允许为 0,此时首次启动协程会按需自动扩容。</param>
|
||||
/// <param name="enableStatistics">是否启用协程统计功能。</param>
|
||||
/// <param name="realtimeTimeSource">
|
||||
/// 非缩放时间源。
|
||||
@ -1036,7 +1036,9 @@ public sealed class CoroutineScheduler(
|
||||
/// </summary>
|
||||
private void Expand()
|
||||
{
|
||||
Array.Resize(ref _slots, _slots.Length * 2);
|
||||
// 允许构造器以 0 容量启动,用于极简场景或测试;首次分配时至少扩到 1,避免后续写槽位越界。
|
||||
var expandedLength = Math.Max(1, _slots.Length * 2);
|
||||
Array.Resize(ref _slots, expandedLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -828,11 +828,20 @@ public sealed class Store<TState> : IStore<TState>, IStoreDiagnostics<TState>
|
||||
EnsureNotDispatching();
|
||||
_isDispatching = true;
|
||||
|
||||
var context = new StoreDispatchContext<TState>(action!, _state);
|
||||
stateComparerSnapshot = _stateComparer;
|
||||
middlewaresSnapshot = CreateMiddlewareSnapshotCore();
|
||||
reducersSnapshot = CreateReducerSnapshotCore(context.ActionType);
|
||||
return context;
|
||||
try
|
||||
{
|
||||
var context = new StoreDispatchContext<TState>(action!, _state);
|
||||
stateComparerSnapshot = _stateComparer;
|
||||
middlewaresSnapshot = CreateMiddlewareSnapshotCore();
|
||||
reducersSnapshot = CreateReducerSnapshotCore(context.ActionType);
|
||||
return context;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 进入 dispatch 标记早于快照构建;如果这里抛异常,必须同步回滚标记,避免后续调用被永久判定为嵌套分发。
|
||||
_isDispatching = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-009`
|
||||
- 当前阶段:`Phase 9`
|
||||
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-010`
|
||||
- 当前阶段:`Phase 10`
|
||||
- 当前焦点:
|
||||
- 当前 `MA0048` 已在 `GFramework.Core` 的 `net8.0` warnings-only 基线中清零;下一轮切到 `MA0046`
|
||||
- 当前 PR #265 的 follow-up 已收口;下一轮恢复到 `MA0046` 主批次
|
||||
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
|
||||
- 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015` 与 `MA0077`
|
||||
只是当前最明显的低数量示例,不构成限定
|
||||
@ -21,6 +21,7 @@
|
||||
|
||||
- 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理
|
||||
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
|
||||
- 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚
|
||||
- 当前 `PauseStackManager`、`Store`、`CoroutineScheduler` 与 `GFramework.Core` 的 `MA0048`
|
||||
文件/类型命名冲突已从 active 入口移除;主题内剩余 warning 主要集中在 `MA0046` delegate 形状、
|
||||
`MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项
|
||||
@ -41,6 +42,9 @@
|
||||
不同模型的 subagent 并行处理
|
||||
- `RP-009` 在不改公共 API 的前提下,将同名泛型家族收拢到与类型名一致的单文件中,清空当前 `GFramework.Core`
|
||||
`net8.0` 基线中的 `MA0048`,并通过定向 build/test 验证 `Command`、`Query`、`Event` 路径未回归
|
||||
- `RP-010` 使用 `gframework-pr-review` 复核当前分支 PR #265 后,修复了仍在本地成立的两个 follow-up 风险:
|
||||
`CoroutineScheduler` 的 `initialCapacity: 0` 扩容越界,以及 `Store` 在 dispatch 快照阶段抛异常时可能残留
|
||||
`_isDispatching = true` 的锁死问题
|
||||
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
|
||||
|
||||
## 当前风险
|
||||
@ -94,6 +98,11 @@
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0048`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CommandExecutorTests|FullyQualifiedName~AbstractAsyncCommandTests|FullyQualifiedName~QueryExecutorTests|FullyQualifiedName~AbstractAsyncQueryTests|FullyQualifiedName~EventTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`83 Passed`,`0 Failed`
|
||||
- `RP-010` 的定向验证结果:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`;新增修复未引入新的 `GFramework.Core` `net8.0` 构建错误
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CoroutineSchedulerTests.Run_Should_Grow_From_Zero_Initial_Capacity|FullyQualifiedName~StoreTests.Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
|
||||
|
||||
## 下一步
|
||||
|
||||
@ -1,5 +1,31 @@
|
||||
# Analyzer Warning Reduction 追踪
|
||||
|
||||
## 2026-04-21 — RP-010
|
||||
|
||||
### 阶段:PR #265 follow-up 收口(RP-010)
|
||||
|
||||
- 使用 `gframework-pr-review` 抓取当前分支 PR #265 的 latest head review threads、CodeRabbit review body、MegaLinter 摘要与 CTRF
|
||||
测试结果;确认最新 unresolved thread 只剩 `CoroutineScheduler` 零容量扩容边界
|
||||
- 本地复核后确认两处仍成立的风险:
|
||||
- `CoroutineScheduler.Expand()` 在 `_slots.Length == 0` 时会把容量从 `0` 扩到 `0`,首次 `Run` 写槽位会越界
|
||||
- `Store.EnterDispatchScope()` 在 `_isDispatching = true` 之后、快照构建完成之前若抛异常,会留下永久的嵌套分发误判
|
||||
- 实施最小修复:
|
||||
- 将 `Expand()` 调整为 `Math.Max(1, _slots.Length * 2)`,保持已有倍增策略,只补上零容量边界
|
||||
- 为 `EnterDispatchScope()` 增加快照阶段的异常回滚,确保 `_isDispatching` 与实际 dispatch 生命周期保持一致
|
||||
- 新增回归测试覆盖零容量启动路径,以及 dispatch 快照阶段抛错后的可恢复性
|
||||
- 当前 PR 信号复核结论:
|
||||
- CTRF:最新评论显示 `2135 passed / 0 failed`
|
||||
- MegaLinter:唯一告警仍是 CI 中 `dotnet-format` restore 失败,未发现新的本地代码格式问题
|
||||
- 旧 review body 中提到的 `Store` 异常安全问题虽未表现为最新 open thread,但在本地代码中仍可成立,因此一并收口
|
||||
- 定向验证命令:
|
||||
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`15 Warning(s)`,`0 Error(s)`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CoroutineSchedulerTests.Run_Should_Grow_From_Zero_Initial_Capacity|FullyQualifiedName~StoreTests.Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws" -m:1 -p:RestoreFallbackFolders="" -nologo`
|
||||
- 结果:`2 Passed`,`0 Failed`
|
||||
- 下一步建议:
|
||||
- 若继续本主题,恢复到 `MA0046` 主批次,不再停留在当前 PR follow-up
|
||||
- 若 PR review 还出现新线程,继续遵守“只修复当前本地仍成立的问题”的策略
|
||||
|
||||
## 2026-04-21 — RP-009
|
||||
|
||||
### 阶段:`MA0048` 批次收口(RP-009)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user