diff --git a/Harmonia.Core/Caching/AudioImageMemoryCache.cs b/Harmonia.Core/Caching/AudioImageMemoryCache.cs index 1ab1510..9a679aa 100644 --- a/Harmonia.Core/Caching/AudioImageMemoryCache.cs +++ b/Harmonia.Core/Caching/AudioImageMemoryCache.cs @@ -1,12 +1,19 @@ using Harmonia.Core.Imaging; using Harmonia.Core.Models; +using Microsoft.Extensions.Caching.Memory; namespace Harmonia.Core.Caching; public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : MemoryCache, IAudioImageCache { - protected override long? SizeLimit => 2000000000; - protected override double CompactionPercentage => 0.2; + protected override int MaxConcurrentRequests => 8; + + protected override MemoryCacheOptions Options => new() + { + SizeLimit = 2000000000, + CompactionPercentage = 0.2, + }; + protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600); protected override object? GetKey(Song key) @@ -23,14 +30,11 @@ public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : M return null; } - protected override SongPictureInfo? Fetch(Song key) + protected override ValueTask FetchAsync(Song key, CancellationToken cancellationToken) { - return audioImageExtractor.ExtractImage(key.FileName); - } + SongPictureInfo? songPictureInfo = audioImageExtractor.ExtractImage(key.FileName); - protected override void AddToCache(object key, SongPictureInfo entry) - { - base.AddToCache(key, entry); + return ValueTask.FromResult(songPictureInfo); } protected override long GetEntrySize(SongPictureInfo entry) diff --git a/Harmonia.Core/Caching/Cache.cs b/Harmonia.Core/Caching/Cache.cs index 721413d..c256fdd 100644 --- a/Harmonia.Core/Caching/Cache.cs +++ b/Harmonia.Core/Caching/Cache.cs @@ -1,29 +1,88 @@ -namespace Harmonia.Core.Caching; +using System.Collections.Concurrent; + +namespace Harmonia.Core.Caching; public abstract class Cache : ICache where TKey : notnull { + private readonly ConcurrentDictionary _locks; + private readonly SemaphoreSlim _throttler; + + protected abstract int MaxConcurrentRequests { get; } protected abstract TValue? TryGetValue(object key); - protected abstract TValue? Fetch(TKey key); + protected abstract ValueTask FetchAsync(TKey key, CancellationToken cancellationToken); protected abstract void AddToCache(object key, TValue value); - public TValue? Get(TKey key) + public Cache() { - object? actualKey = GetKey(key); - - if (actualKey == null) - return default; - - return TryGetValue(actualKey) ?? Refresh(key); + _locks = new(); + _throttler = new SemaphoreSlim(MaxConcurrentRequests, MaxConcurrentRequests); } - public TValue? Refresh(TKey key) + public async Task GetAsync(TKey key, CancellationToken cancellationToken) { object? actualKey = GetKey(key); if (actualKey == null) return default; - TValue? value = Fetch(key); + TValue? value = TryGetValue(actualKey); + + if (value != null) + return value; + + SemaphoreSlim lockObject = _locks.GetOrAdd(actualKey, (key) => new SemaphoreSlim(1, 1)); + + try + { + await _throttler.WaitAsync(cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return default; + + await lockObject.WaitAsync(cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return default; + + return TryGetValue(actualKey) ?? await FetchAsync2(key, cancellationToken); + } + finally + { + lockObject.Release(); + + _locks.TryRemove(lockObject, out _); + + _throttler.Release(); + } + + //return TryGetValue(actualKey) ?? Refresh(key); + } + + private async Task FetchAsync2(TKey key, CancellationToken cancellationToken) + { + object? actualKey = GetKey(key); + + if (actualKey == null) + return default; + + TValue? value = await FetchAsync(key, cancellationToken); + + if (value == null) + return default; + + AddToCache(actualKey, value); + + return value; + } + + public async Task RefreshAsync(TKey key, CancellationToken cancellationToken) + { + object? actualKey = GetKey(key); + + if (actualKey == null) + return default; + + TValue? value = await FetchAsync(key, cancellationToken); if (value == null) return default; diff --git a/Harmonia.Core/Caching/ICache.cs b/Harmonia.Core/Caching/ICache.cs index 584668b..fe27f4a 100644 --- a/Harmonia.Core/Caching/ICache.cs +++ b/Harmonia.Core/Caching/ICache.cs @@ -2,6 +2,6 @@ public interface ICache { - TValue? Get(TKey key); - TValue? Refresh(TKey key); + Task GetAsync(TKey key, CancellationToken cancellationToken); + Task RefreshAsync(TKey key, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Harmonia.Core/Caching/MemoryCache.cs b/Harmonia.Core/Caching/MemoryCache.cs index 5af8900..ea03fc6 100644 --- a/Harmonia.Core/Caching/MemoryCache.cs +++ b/Harmonia.Core/Caching/MemoryCache.cs @@ -6,19 +6,12 @@ public abstract class MemoryCache : Cache where TKey { private readonly IMemoryCache _memoryCache; - protected abstract long? SizeLimit { get; } - protected abstract double CompactionPercentage { get; } + protected abstract MemoryCacheOptions Options { get; } protected abstract TimeSpan SlidingExpiration { get; } public MemoryCache() { - MemoryCacheOptions memoryCacheOptions = new() - { - SizeLimit = SizeLimit, - CompactionPercentage = CompactionPercentage - }; - - _memoryCache = new MemoryCache(memoryCacheOptions); + _memoryCache = new MemoryCache(Options); } protected override TValue? TryGetValue(object key) diff --git a/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs b/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs index 12feaab..f1348a0 100644 --- a/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs +++ b/Harmonia.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Harmonia.Core.Engine; +using Harmonia.Core.Caching; +using Harmonia.Core.Engine; using Harmonia.Core.Imaging; using Harmonia.Core.Player; using Harmonia.Core.Playlists; @@ -16,6 +17,7 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();