using System.Collections.Concurrent;
namespace w4c_workflows.Services;
///
/// Serializes concurrent workflow syncs for the same (tenant, repo) within
/// one process. Two overlapping POST /sync calls would otherwise both
/// insert the same deterministic workflow/task ids and collide on the primary key
/// (DbUpdateException). The gate makes the second call wait, so it observes
/// the first call's committed rows and upserts instead of duplicating.
///
/// For multi-replica deployments this is combined with the distributed
/// lease in .
///
public sealed class SyncGate
{
private readonly ConcurrentDictionary _gates = new(StringComparer.Ordinal);
public async Task AcquireAsync(string tenantId, string repoName, CancellationToken ct)
{
var gate = _gates.GetOrAdd(tenantId + "|" + repoName, _ => new SemaphoreSlim(1, 1));
await gate.WaitAsync(ct);
return new Releaser(gate);
}
private sealed class Releaser(SemaphoreSlim gate) : IDisposable
{
private int _released;
public void Dispose()
{
if (Interlocked.Exchange(ref _released, 1) == 0)
gate.Release();
}
}
}