mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +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.Core.Abstractions.Events;
|
||||||
using GFramework.Game.Abstractions.Config;
|
using GFramework.Game.Abstractions.Config;
|
||||||
using GFramework.Game.Config;
|
using GFramework.Game.Config;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
namespace GFramework.Game.Tests.Config;
|
namespace GFramework.Game.Tests.Config;
|
||||||
|
|
||||||
@ -2828,6 +2829,88 @@ public class YamlConfigLoaderTests
|
|||||||
Throws.InstanceOf<OperationCanceledException>());
|
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>
|
/// <summary>
|
||||||
/// 验证依赖关系仅来自 <c>contains</c> 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。
|
/// 验证依赖关系仅来自 <c>contains</c> 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -484,7 +484,7 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
referenceUsages,
|
referenceUsages,
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
return BuildLoadResult(directoryPath, schema, values, referenceUsages);
|
return BuildLoadResult(directoryPath, schema, values, referenceUsages, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetValidatedDirectoryPath(string rootPath)
|
private string GetValidatedDirectoryPath(string rootPath)
|
||||||
@ -526,7 +526,7 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var yaml = await ReadYamlAsync(directoryPath, file, schema, cancellationToken).ConfigureAwait(false);
|
var yaml = await ReadYamlAsync(directoryPath, file, schema, cancellationToken).ConfigureAwait(false);
|
||||||
CollectReferenceUsages(referenceUsages, schema, file, yaml);
|
CollectReferenceUsages(referenceUsages, schema, file, yaml);
|
||||||
values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml));
|
values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
@ -592,10 +592,12 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
string directoryPath,
|
string directoryPath,
|
||||||
string file,
|
string file,
|
||||||
YamlConfigSchema? schema,
|
YamlConfigSchema? schema,
|
||||||
string yaml)
|
string yaml,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var value = deserializer.Deserialize<TValue>(yaml);
|
var value = deserializer.Deserialize<TValue>(yaml);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
@ -604,6 +606,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
|
|
||||||
throw new InvalidOperationException("YAML content was deserialized to null.");
|
throw new InvalidOperationException("YAML content was deserialized to null.");
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// 同步反序列化阶段也要透传会话级取消,避免把停止加载误报为 YAML 解析失败。
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
@ -622,10 +629,12 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
string directoryPath,
|
string directoryPath,
|
||||||
YamlConfigSchema? schema,
|
YamlConfigSchema? schema,
|
||||||
List<TValue> values,
|
List<TValue> values,
|
||||||
List<YamlConfigReferenceUsage> referenceUsages)
|
List<YamlConfigReferenceUsage> referenceUsages,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var table = new InMemoryConfigTable<TKey, TValue>(values, _keySelector, _comparer);
|
var table = new InMemoryConfigTable<TKey, TValue>(values, _keySelector, _comparer);
|
||||||
return new YamlTableLoadResult(
|
return new YamlTableLoadResult(
|
||||||
Name,
|
Name,
|
||||||
@ -633,6 +642,11 @@ public sealed class YamlConfigLoader : IConfigLoader
|
|||||||
schema?.ReferencedTableNames ?? Array.Empty<string>(),
|
schema?.ReferencedTableNames ?? Array.Empty<string>(),
|
||||||
referenceUsages);
|
referenceUsages);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// 构建最终配置表时继续保留原始取消语义,避免热重载把提交前取消记录成构表失败。
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
throw ConfigLoadExceptionFactory.Create(
|
throw ConfigLoadExceptionFactory.Create(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user