Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 41 additions & 28 deletions src/TickerQ.Utilities/TickerFunctionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ public static void RegisterFunctions(IDictionary<string, (string, TickerTaskPrio
if (functions.Count == 0)
return;

_functionRegistrations += dict =>
lock (_buildLock)
{
foreach (var (key, value) in functions)
_functionRegistrations += dict =>
{
dict.TryAdd(key, value); // Preserves existing entries
}
};
foreach (var (key, value) in functions)
{
dict.TryAdd(key, value); // Preserves existing entries
}
};
}
}

/// <summary>
Expand Down Expand Up @@ -84,13 +87,16 @@ public static void RegisterRequestType(IDictionary<string, (string, Type)> reque
if (requestTypes.Count == 0)
return;

_requestTypeRegistrations += dict =>
lock (_buildLock)
{
foreach (var (key, value) in requestTypes)
_requestTypeRegistrations += dict =>
{
dict.TryAdd(key, value); // Preserves existing entries
}
};
foreach (var (key, value) in requestTypes)
{
dict.TryAdd(key, value); // Preserves existing entries
}
};
}
}

/// <summary>
Expand Down Expand Up @@ -119,13 +125,16 @@ public static void RegisterRequestInfo(IDictionary<string, (string RequestType,
if (requestInfos.Count == 0)
return;

_requestInfoRegistrations += dict =>
lock (_buildLock)
{
foreach (var (key, value) in requestInfos)
_requestInfoRegistrations += dict =>
{
dict.TryAdd(key, value);
}
};
foreach (var (key, value) in requestInfos)
{
dict.TryAdd(key, value);
}
};
}
}

/// <summary>
Expand All @@ -136,22 +145,26 @@ public static void RegisterRequestInfo(IDictionary<string, (string RequestType,
/// <exception cref="ArgumentNullException">Thrown when cronUpdates parameter is null.</exception>
internal static void UpdateCronExpressionsFromIConfiguration(IConfiguration configuration)
{
_functionRegistrations += dict =>
lock (_buildLock)
{
foreach (var (key, value) in dict)
_functionRegistrations += dict =>
{
if (value.cronExpression.StartsWith('%'))
foreach (var (key, value) in dict)
{
var configKey = value.cronExpression.Trim('%');
var mappedCronExpression = configuration[configKey];

if (!string.IsNullOrEmpty(mappedCronExpression))
if (value.cronExpression.StartsWith('%'))
{
dict[key] = (mappedCronExpression, value.Priority, value.Delegate, value.MaxConcurrency);
var configKey = value.cronExpression.Trim('%');
var mappedCronExpression = configuration[configKey];

if (!string.IsNullOrEmpty(mappedCronExpression))
{
dict[key] = (mappedCronExpression, value.Priority, value.Delegate,
value.MaxConcurrency);
}
}
}
}
};
};
}
}

/// <summary>
Expand All @@ -167,7 +180,7 @@ public static void Build()
// Build functions dictionary
if (_functionRegistrations != null)
{
var functionsDict = new Dictionary<string, (string cronExpression, TickerTaskPriority Priority, TickerFunctionDelegate Delegate, int MaxConcurrency)>();
var functionsDict = new Dictionary<string, (string cronExpression, TickerTaskPriority Priority, TickerFunctionDelegate Delegate, int MaxConcurrency)>(TickerFunctions);
_functionRegistrations(functionsDict);
TickerFunctions = functionsDict.ToFrozenDictionary();
_functionRegistrations = null;
Expand All @@ -176,7 +189,7 @@ public static void Build()
// Build request types dictionary
if (_requestTypeRegistrations != null)
{
var requestTypesDict = new Dictionary<string, (string, Type)>();
var requestTypesDict = new Dictionary<string, (string, Type)>(TickerFunctionRequestTypes);
_requestTypeRegistrations(requestTypesDict);
TickerFunctionRequestTypes = requestTypesDict.ToFrozenDictionary();
_requestTypeRegistrations = null;
Expand All @@ -185,7 +198,7 @@ public static void Build()
// Build request info dictionary (string type + example JSON)
if (_requestInfoRegistrations != null)
{
var requestInfoDict = new Dictionary<string, (string RequestType, string RequestExampleJson)>();
var requestInfoDict = new Dictionary<string, (string RequestType, string RequestExampleJson)>(TickerFunctionRequestInfos);
_requestInfoRegistrations(requestInfoDict);
TickerFunctionRequestInfos = requestInfoDict.ToFrozenDictionary();
_requestInfoRegistrations = null;
Expand Down
47 changes: 47 additions & 0 deletions tests/TickerQ.Tests/TickerFunctionProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,29 @@ public void Build_CalledTwice_DoesNotCrashOrLoseData()
Assert.True(TickerFunctionProvider.TickerFunctions.ContainsKey("DoubleFunc"));
Assert.Single(TickerFunctionProvider.TickerFunctions);
}

[Fact]
public void Build_CalledTwiceWithCronUpdateAfterInitialBuild_DoesNotClearFunctions()
{
var functions = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate, int)>
{
["CronRaceFunc"] = ("0 0 1 * *", TickerTaskPriority.Normal, NoOpDelegate, 0)
};

var configuration = Substitute.For<IConfiguration>();

TickerFunctionProvider.RegisterFunctions(functions);

TickerFunctionProvider.UpdateCronExpressionsFromIConfiguration(configuration);
TickerFunctionProvider.Build();

// Simulate another host that UpdateCronExpressionsFromIConfiguration and Build again.
TickerFunctionProvider.UpdateCronExpressionsFromIConfiguration(configuration);
TickerFunctionProvider.Build();

Assert.True(TickerFunctionProvider.TickerFunctions.ContainsKey("CronRaceFunc"));
Assert.Single(TickerFunctionProvider.TickerFunctions);
}

// ---------------------------------------------------------------
// 7. UpdateCronExpressionsFromIConfiguration – mock IConfiguration
Expand All @@ -205,6 +228,30 @@ public void UpdateCronExpressionsFromIConfiguration_UpdatesExpressions()

Assert.Equal("0 30 * * *", TickerFunctionProvider.TickerFunctions["CronFunc"].cronExpression);
}

[Fact]
public void UpdateCronExpressionsFromIConfiguration_AfterInitialBuild_UpdatesExpressions()
{
var functions = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate, int)>
{
["CronRaceFunc"] = ("%CronSettings:Schedule%", TickerTaskPriority.Normal, NoOpDelegate, 0)
};
Comment thread
HulinCedric marked this conversation as resolved.

TickerFunctionProvider.RegisterFunctions(functions);
TickerFunctionProvider.Build();

// Simulate another host that contributes config-based cron update callback.
var configuration = Substitute.For<IConfiguration>();
configuration["CronSettings:Schedule"].Returns("0 30 * * *");

TickerFunctionProvider.UpdateCronExpressionsFromIConfiguration(configuration);

// Second Build should not throw and data should still be present
TickerFunctionProvider.Build();

Assert.True(TickerFunctionProvider.TickerFunctions.ContainsKey("CronRaceFunc"));
Assert.Equal("0 30 * * *", TickerFunctionProvider.TickerFunctions["CronRaceFunc"].cronExpression);
}

[Fact]
public void UpdateCronExpressionsFromIConfiguration_NoConfigValue_KeepsOriginal()
Expand Down
Loading