// Copyright Epic Games, Inc. All Rights Reserved. using Microsoft.Extensions.Caching.Memory; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Driver; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; 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; } IMongoCollection Collection; MemoryCache Cache; TimeSpan MaxLatency; public MongoQueryCache(IMongoCollection Collection, TimeSpan MaxLatency) { this.Collection = Collection; MemoryCacheOptions Options = new MemoryCacheOptions(); this.Cache = new MemoryCache(Options); this.MaxLatency = MaxLatency; } public void Dispose() { Cache.Dispose(); } async Task RefreshAsync(QueryState State, FilterDefinition Filter) { State.Results = await Collection.Find(Filter).ToListAsync(); State.Timer.Restart(); } public async Task> FindAsync(FilterDefinition Filter, int Index, int Count) { BsonDocument Rendered = Filter.Render(BsonSerializer.LookupSerializer(), BsonSerializer.SerializerRegistry); 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)); } if (State.QueryTask != null && (State.Results == null || State.Timer.Elapsed > MaxLatency)) { await State.QueryTask; } return State.Results!; } } }