// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace HordeServer.Utilities
{
///
/// Caches queries against a given collection
///
/// The document type
class MongoQueryCache : IDisposable
{
class QueryState
{
public Stopwatch _timer = Stopwatch.StartNew();
public List? _results;
public Task? _queryTask;
}
readonly IMongoCollection _collection;
readonly MemoryCache _cache;
readonly TimeSpan _maxLatency;
public MongoQueryCache(IMongoCollection collection, TimeSpan maxLatency)
{
_collection = collection;
MemoryCacheOptions options = new MemoryCacheOptions();
_cache = new MemoryCache(options);
_maxLatency = maxLatency;
}
public void Dispose()
{
_cache.Dispose();
}
async Task RefreshAsync(QueryState state, FilterDefinition filter, CancellationToken cancellationToken = default)
{
state._results = await _collection.Find(filter).ToListAsync(cancellationToken);
state._timer.Restart();
}
public async Task> FindAsync(FilterDefinition filter, int index, int count)
{
BsonDocument rendered = filter.Render(BsonSerializer.LookupSerializer(), BsonSerializer.SerializerRegistry, MongoDB.Driver.Linq.LinqProvider.V2);
BsonDocument document = new BsonDocument { new BsonElement("filter", rendered), new BsonElement("index", index), new BsonElement("count", count) };
string filterKey = document.ToString();
QueryState? state;
if (!_cache.TryGetValue(filterKey, out state) || state == null)
{
state = new QueryState();
using (ICacheEntry cacheEntry = _cache.CreateEntry(filterKey))
{
cacheEntry.SetSlidingExpiration(TimeSpan.FromMinutes(5.0));
cacheEntry.SetValue(state);
}
}
if (state._queryTask != null && state._queryTask.IsCompleted)
{
await state._queryTask;
state._queryTask = null;
}
if (state._queryTask == null && (state._results == null || state._timer.Elapsed > _maxLatency))
{
state._queryTask = Task.Run(() => RefreshAsync(state, filter, CancellationToken.None), CancellationToken.None);
}
if (state._queryTask != null && (state._results == null || state._timer.Elapsed > _maxLatency))
{
await state._queryTask;
}
return state._results!;
}
}
}