mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
fix(game): 修复同步加载阶段的取消透传
- 修复 YAML 同步反序列化与构表阶段的取消处理,避免已取消会话被包装为配置加载失败 - 补充私有同步路径的回归测试,覆盖反序列化与构表阶段的 OperationCanceledException 透传语义
This commit is contained in:
parent
953a03b937
commit
1753778cae
@ -4,6 +4,7 @@ using System.Threading;
|
||||
using GFramework.Core.Abstractions.Events;
|
||||
using GFramework.Game.Abstractions.Config;
|
||||
using GFramework.Game.Config;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace GFramework.Game.Tests.Config;
|
||||
|
||||
@ -2828,6 +2829,88 @@ public class YamlConfigLoaderTests
|
||||
Throws.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证同步反序列化阶段遇到已取消 token 时会直接透传 <see cref="OperationCanceledException" />,
|
||||
/// 避免把停止加载误报为 YAML 解析失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DeserializeValue_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
|
||||
{
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", static config => config.Id);
|
||||
var registration = GetSingleYamlTableRegistration(loader);
|
||||
var deserializeValueMethod = registration.GetType()
|
||||
.GetMethod("DeserializeValue", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(deserializeValueMethod, Is.Not.Null);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
var deserializer = new DeserializerBuilder().Build();
|
||||
var exception = Assert.Throws<TargetInvocationException>(() =>
|
||||
deserializeValueMethod!.Invoke(
|
||||
registration,
|
||||
new object?[]
|
||||
{
|
||||
deserializer,
|
||||
Path.Combine(_rootPath, "monster"),
|
||||
Path.Combine(_rootPath, "monster", "slime.yaml"),
|
||||
null,
|
||||
"""
|
||||
id: 1
|
||||
name: Slime
|
||||
hp: 10
|
||||
""",
|
||||
cancellationTokenSource.Token
|
||||
}));
|
||||
|
||||
// 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
|
||||
Assert.That(exception!.InnerException, Is.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证构建最终配置表阶段遇到已取消 token 时会继续透传 <see cref="OperationCanceledException" />,
|
||||
/// 避免热重载把提交前取消记录成构表失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void BuildLoadResult_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
|
||||
{
|
||||
var loader = new YamlConfigLoader(_rootPath)
|
||||
.RegisterTable<int, MonsterConfigStub>("monster", "monster", static config => config.Id);
|
||||
var registration = GetSingleYamlTableRegistration(loader);
|
||||
var buildLoadResultMethod = registration.GetType()
|
||||
.GetMethod("BuildLoadResult", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(buildLoadResultMethod, Is.Not.Null);
|
||||
|
||||
using var cancellationTokenSource = new CancellationTokenSource();
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
var exception = Assert.Throws<TargetInvocationException>(() =>
|
||||
buildLoadResultMethod!.Invoke(
|
||||
registration,
|
||||
new object?[]
|
||||
{
|
||||
Path.Combine(_rootPath, "monster"),
|
||||
null,
|
||||
new List<MonsterConfigStub>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Slime",
|
||||
Hp = 10
|
||||
}
|
||||
},
|
||||
new List<YamlConfigReferenceUsage>(),
|
||||
cancellationTokenSource.Token
|
||||
}));
|
||||
|
||||
// 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
|
||||
Assert.That(exception!.InnerException, Is.InstanceOf<OperationCanceledException>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证依赖关系仅来自 <c>contains</c> 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。
|
||||
/// </summary>
|
||||
|
||||
@ -484,7 +484,7 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
referenceUsages,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return BuildLoadResult(directoryPath, schema, values, referenceUsages);
|
||||
return BuildLoadResult(directoryPath, schema, values, referenceUsages, cancellationToken);
|
||||
}
|
||||
|
||||
private string GetValidatedDirectoryPath(string rootPath)
|
||||
@ -526,7 +526,7 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var yaml = await ReadYamlAsync(directoryPath, file, schema, cancellationToken).ConfigureAwait(false);
|
||||
CollectReferenceUsages(referenceUsages, schema, file, yaml);
|
||||
values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml));
|
||||
values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml, cancellationToken));
|
||||
}
|
||||
|
||||
return values;
|
||||
@ -592,10 +592,12 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
string directoryPath,
|
||||
string file,
|
||||
YamlConfigSchema? schema,
|
||||
string yaml)
|
||||
string yaml,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var value = deserializer.Deserialize<TValue>(yaml);
|
||||
if (value != null)
|
||||
{
|
||||
@ -604,6 +606,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
|
||||
throw new InvalidOperationException("YAML content was deserialized to null.");
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 同步反序列化阶段也要透传会话级取消,避免把停止加载误报为 YAML 解析失败。
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
@ -622,10 +629,12 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
string directoryPath,
|
||||
YamlConfigSchema? schema,
|
||||
List<TValue> values,
|
||||
List<YamlConfigReferenceUsage> referenceUsages)
|
||||
List<YamlConfigReferenceUsage> referenceUsages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var table = new InMemoryConfigTable<TKey, TValue>(values, _keySelector, _comparer);
|
||||
return new YamlTableLoadResult(
|
||||
Name,
|
||||
@ -633,6 +642,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
||||
schema?.ReferencedTableNames ?? Array.Empty<string>(),
|
||||
referenceUsages);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// 构建最终配置表时继续保留原始取消语义,避免热重载把提交前取消记录成构表失败。
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw ConfigLoadExceptionFactory.Create(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user