using System.Threading.Channels;
namespace Cleanuparr.Infrastructure.Features.DryRun;
///
/// Counts dry runs in flight.
///
public sealed class DryRunActivity
{
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly Channel _purgeRequests = Channel.CreateBounded(
new BoundedChannelOptions(1) { FullMode = BoundedChannelFullMode.DropWrite });
private int _active;
///
/// Whether any dry run is in flight.
///
public bool IsActive => Volatile.Read(ref _active) > 0;
///
/// Marks the start of a dry run.
/// Waits for any in-progress purge to finish first.
///
public async Task EnterAsync()
{
await _gate.WaitAsync();
try
{
Interlocked.Increment(ref _active);
}
finally
{
_gate.Release();
}
}
///
/// Marks the end of a dry run.
/// Requests a purge once no dry run is left active.
///
public void Exit()
{
if (Interlocked.Decrement(ref _active) == 0)
{
RequestPurge();
}
}
///
/// Requests a purge.
/// Buffers at most one pending request.
///
public void RequestPurge()
{
_purgeRequests.Writer.TryWrite(true);
}
///
/// Waits for a pending purge request.
///
public ValueTask WaitForPurgeRequestAsync(CancellationToken ct)
{
return _purgeRequests.Reader.ReadAsync(ct);
}
///
/// Runs the given action only if no dry run is in flight.
/// Blocks new dry runs while the action runs.
///
public async Task RunIfIdleAsync(Func action)
{
await _gate.WaitAsync();
try
{
if (Volatile.Read(ref _active) > 0)
{
return false;
}
await action();
return true;
}
finally
{
_gate.Release();
}
}
}