using CryptoExchange.Net.Logging.Extensions;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects
{
///
/// Request coalescer, used to detect multiple identical requests and wait for and return only a single actual request result
///
public class RequestCoalescer
{
private sealed class RequestEntry
{
public RequestCoalescingKey Key { get; }
public CancellationTokenSource CancellationSource { get; } = new();
public Lazy> Request { get; set; } = null!;
public int ListenerCount { get; set; }
public bool Completed { get; set; }
public bool CancellationInProgress { get; set; }
public bool Disposed { get; set; }
public RequestEntry(RequestCoalescingKey key)
{
Key = key;
}
}
private readonly object _sync = new();
private readonly ILogger _logger;
private readonly Dictionary _requests = new();
///
/// ctor
///
public RequestCoalescer(ILogger logger)
{
_logger = logger;
}
///
/// Execute a request, coalescing identical requests into a single request
///
public Task ExecuteAsync(
RequestCoalescingKey key,
Func> requestFactory,
Func cancellationResultFactory,
CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromResult(cancellationResultFactory());
RequestEntry entry;
bool joined;
lock (_sync)
{
joined = _requests.TryGetValue(key, out entry!);
if (!joined)
{
entry = CreateEntry(key, requestFactory);
_requests.Add(key, entry);
}
entry.ListenerCount++;
}
if (joined)
_logger.RestApiRequestsJoined(key.Method, key.Url);
return WaitAsync(
entry,
cancellationResultFactory,
cancellationToken);
}
private RequestEntry CreateEntry(
RequestCoalescingKey key,
Func> requestFactory)
{
var entry = new RequestEntry(key);
entry.Request = new Lazy>(
() => ExecuteCoreAsync(
entry,
async requestCancellationToken =>
(await requestFactory(requestCancellationToken).ConfigureAwait(false))!),
LazyThreadSafetyMode.ExecutionAndPublication);
return entry;
}
private async Task