using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace StellaOps.Microservice;
///
/// Tracks in-flight requests and manages their cancellation tokens.
///
public sealed class InflightRequestTracker : IDisposable
{
private readonly ConcurrentDictionary _inflight = new();
private readonly ILogger _logger;
private bool _disposed;
///
/// Initializes a new instance of the class.
///
public InflightRequestTracker(ILogger logger)
{
_logger = logger;
}
///
/// Gets the count of in-flight requests.
///
public int Count => _inflight.Count;
///
/// Starts tracking a request and returns a cancellation token for it.
///
/// The correlation ID of the request.
/// A cancellation token that will be triggered if the request is cancelled.
public CancellationToken Track(Guid correlationId)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var cts = new CancellationTokenSource();
var request = new InflightRequest(cts);
if (!_inflight.TryAdd(correlationId, request))
{
cts.Dispose();
throw new InvalidOperationException($"Request {correlationId} is already being tracked");
}
_logger.LogDebug("Started tracking request {CorrelationId}", correlationId);
return cts.Token;
}
///
/// Cancels a specific request.
///
/// The correlation ID of the request to cancel.
/// The reason for cancellation.
/// True if the request was found and cancelled; otherwise false.
public bool Cancel(Guid correlationId, string? reason)
{
if (_inflight.TryGetValue(correlationId, out var request))
{
try
{
request.Cts.Cancel();
_logger.LogInformation(
"Cancelled request {CorrelationId}: {Reason}",
correlationId,
reason ?? "Unknown");
return true;
}
catch (ObjectDisposedException)
{
// CTS was already disposed, request completed
return false;
}
}
_logger.LogDebug(
"Cannot cancel request {CorrelationId}: not found (may have already completed)",
correlationId);
return false;
}
///
/// Marks a request as completed and removes it from tracking.
///
/// The correlation ID of the completed request.
public void Complete(Guid correlationId)
{
if (_inflight.TryRemove(correlationId, out var request))
{
request.Cts.Dispose();
_logger.LogDebug("Completed request {CorrelationId}", correlationId);
}
}
///
/// Cancels all in-flight requests.
///
/// The reason for cancellation.
public void CancelAll(string reason)
{
var count = 0;
foreach (var kvp in _inflight)
{
try
{
kvp.Value.Cts.Cancel();
count++;
}
catch (ObjectDisposedException)
{
// Already disposed
}
}
_logger.LogInformation("Cancelled {Count} in-flight requests: {Reason}", count, reason);
// Clear and dispose all
foreach (var kvp in _inflight)
{
if (_inflight.TryRemove(kvp.Key, out var request))
{
request.Cts.Dispose();
}
}
}
///
public void Dispose()
{
if (_disposed) return;
_disposed = true;
CancelAll("Disposing tracker");
}
private sealed class InflightRequest
{
public CancellationTokenSource Cts { get; }
public InflightRequest(CancellationTokenSource cts)
{
Cts = cts;
}
}
}