NEO-115: serialize JsonSchema registry registration for parallel tests.
Recipe and reward-table loaders registered schemas without locking, corrupting SchemaRegistry.Global when xUnit spun up multiple WebApplicationFactory hosts concurrently in CI.pull/154/head
parent
ab315f326b
commit
5f1244843c
|
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Json.Schema;
|
||||
|
||||
namespace NeonSprawl.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe registration for <see cref="SchemaRegistry.Global"/> (JsonSchema.Net registry is not concurrent).
|
||||
/// Used by content catalog loaders when parallel integration tests spin up multiple hosts.
|
||||
/// </summary>
|
||||
internal static class CatalogSchemaRegistry
|
||||
{
|
||||
private static readonly Lock RegisterLock = new();
|
||||
private static readonly ConcurrentDictionary<string, JsonSchema> SchemasByPath =
|
||||
new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>Parses <paramref name="schemaFilePath"/> once per process and registers it under a global lock.</summary>
|
||||
public static JsonSchema GetOrRegisterFromFile(string schemaFilePath)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(schemaFilePath);
|
||||
if (SchemasByPath.TryGetValue(fullPath, out var cached))
|
||||
return cached;
|
||||
|
||||
lock (RegisterLock)
|
||||
{
|
||||
if (SchemasByPath.TryGetValue(fullPath, out cached))
|
||||
return cached;
|
||||
|
||||
var schema = JsonSchema.FromText(File.ReadAllText(fullPath));
|
||||
SchemaRegistry.Global.Register(schema);
|
||||
SchemasByPath[fullPath] = schema;
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,12 +45,8 @@ public static class RecipeDefinitionCatalogLoader
|
|||
if (errors.Count > 0)
|
||||
ThrowIfAny(errors);
|
||||
|
||||
var ioSchemaText = File.ReadAllText(recipeIoRowSchemaPath);
|
||||
var defSchemaText = File.ReadAllText(recipeDefSchemaPath);
|
||||
var ioSchema = JsonSchema.FromText(ioSchemaText);
|
||||
SchemaRegistry.Global.Register(ioSchema);
|
||||
var defSchema = JsonSchema.FromText(defSchemaText);
|
||||
SchemaRegistry.Global.Register(defSchema);
|
||||
var ioSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(recipeIoRowSchemaPath);
|
||||
var defSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(recipeDefSchemaPath);
|
||||
|
||||
var evalOptions = new EvaluationOptions { OutputFormat = OutputFormat.List };
|
||||
|
||||
|
|
|
|||
|
|
@ -44,10 +44,8 @@ public static class RewardTableDefinitionCatalogLoader
|
|||
if (errors.Count > 0)
|
||||
ThrowIfAny(errors);
|
||||
|
||||
var grantRowSchema = JsonSchema.FromText(File.ReadAllText(rewardGrantRowSchemaPath));
|
||||
SchemaRegistry.Global.Register(grantRowSchema);
|
||||
var tableSchema = JsonSchema.FromText(File.ReadAllText(rewardTableSchemaPath));
|
||||
SchemaRegistry.Global.Register(tableSchema);
|
||||
var grantRowSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(rewardGrantRowSchemaPath);
|
||||
var tableSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(rewardTableSchemaPath);
|
||||
|
||||
var evalOptions = new EvaluationOptions { OutputFormat = OutputFormat.List };
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ namespace NeonSprawl.Server.Game.Quests;
|
|||
/// <summary>Loads and validates <c>content/quests/*_quests.json</c> using the same rules as <c>scripts/validate_content.py</c> (NEO-113).</summary>
|
||||
public static class QuestDefinitionCatalogLoader
|
||||
{
|
||||
private static readonly Lock SchemaLoadLock = new();
|
||||
private static JsonSchema? _questObjectiveDefSchema;
|
||||
private static JsonSchema? _questStepDefSchema;
|
||||
private static JsonSchema? _questDefSchema;
|
||||
|
|
@ -352,23 +351,12 @@ public static class QuestDefinitionCatalogLoader
|
|||
string questStepDefSchemaPath,
|
||||
string questDefSchemaPath)
|
||||
{
|
||||
lock (SchemaLoadLock)
|
||||
{
|
||||
if (_questDefSchema is not null)
|
||||
return;
|
||||
if (_questDefSchema is not null)
|
||||
return;
|
||||
|
||||
var objectiveSchema = JsonSchema.FromText(File.ReadAllText(questObjectiveDefSchemaPath));
|
||||
SchemaRegistry.Global.Register(objectiveSchema);
|
||||
_questObjectiveDefSchema = objectiveSchema;
|
||||
|
||||
var stepSchema = JsonSchema.FromText(File.ReadAllText(questStepDefSchemaPath));
|
||||
SchemaRegistry.Global.Register(stepSchema);
|
||||
_questStepDefSchema = stepSchema;
|
||||
|
||||
var defSchema = JsonSchema.FromText(File.ReadAllText(questDefSchemaPath));
|
||||
SchemaRegistry.Global.Register(defSchema);
|
||||
_questDefSchema = defSchema;
|
||||
}
|
||||
_questObjectiveDefSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(questObjectiveDefSchemaPath);
|
||||
_questStepDefSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(questStepDefSchemaPath);
|
||||
_questDefSchema = CatalogSchemaRegistry.GetOrRegisterFromFile(questDefSchemaPath);
|
||||
}
|
||||
|
||||
private static void ThrowIfAny(List<string> errors)
|
||||
|
|
|
|||
Loading…
Reference in New Issue