fix(infra): use thread-safe Lazy Task for active AI request deduplication

This commit is contained in:
2026-05-19 19:59:44 +02:00
parent a2a09b5e91
commit 720a5e6091
@@ -31,7 +31,7 @@ public class KnowledgeService : IKnowledgeService
private readonly Tokenizer _tokenizer;
private readonly ILogger<KnowledgeService> _logger;
private const string PromptVersion = "1.3";
private static readonly ConcurrentDictionary<string, Task<Result<KnowledgePacket>>> _activeRequests = new();
private static readonly ConcurrentDictionary<string, Lazy<Task<Result<KnowledgePacket>>>> _activeRequests = new();
public KnowledgeService(
IChatClient chatClient,
@@ -100,10 +100,38 @@ public class KnowledgeService : IKnowledgeService
// Deduplicate concurrent active requests for the exact same hash
var requestKey = $"{tenantId}:{hash}:{traceType}";
var task = _activeRequests.GetOrAdd(requestKey, _ =>
ExecuteAiRequestAndCacheAsync(normalizedText, tenantId, systemPrompt, traceType, ebookId, hash));
var lazyTask = _activeRequests.GetOrAdd(requestKey, k =>
new Lazy<Task<Result<KnowledgePacket>>>(
() => ExecuteAiRequestAndCacheAsync(normalizedText, tenantId, systemPrompt, traceType, ebookId, hash),
System.Threading.LazyThreadSafetyMode.ExecutionAndPublication
));
return await task;
try
{
var result = await lazyTask.Value;
// If the AI call returned a failure, remove it from the active dictionary
// so subsequent retries have a chance to request the AI again.
if (result.IsFailed)
{
_activeRequests.TryRemove(requestKey, out _);
}
return result;
}
catch (Exception)
{
// Evict from active dictionary on hard exceptions to ensure system resiliency
_activeRequests.TryRemove(requestKey, out _);
throw;
}
finally
{
// Once a task successfully finishes and persists to the Persistent Database Cache,
// we evict it from RAM (_activeRequests) since future hits will leverage the DB cache.
_activeRequests.TryRemove(requestKey, out _);
}
}
private async Task<Result<KnowledgePacket>> ExecuteAiRequestAndCacheAsync(