Adjusted cache to allow for throttling and locking.
This commit is contained in:
@@ -1,12 +1,19 @@
|
|||||||
using Harmonia.Core.Imaging;
|
using Harmonia.Core.Imaging;
|
||||||
using Harmonia.Core.Models;
|
using Harmonia.Core.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
namespace Harmonia.Core.Caching;
|
namespace Harmonia.Core.Caching;
|
||||||
|
|
||||||
public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : MemoryCache<Song, SongPictureInfo>, IAudioImageCache
|
public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : MemoryCache<Song, SongPictureInfo>, IAudioImageCache
|
||||||
{
|
{
|
||||||
protected override long? SizeLimit => 2000000000;
|
protected override int MaxConcurrentRequests => 8;
|
||||||
protected override double CompactionPercentage => 0.2;
|
|
||||||
|
protected override MemoryCacheOptions Options => new()
|
||||||
|
{
|
||||||
|
SizeLimit = 2000000000,
|
||||||
|
CompactionPercentage = 0.2,
|
||||||
|
};
|
||||||
|
|
||||||
protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600);
|
protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600);
|
||||||
|
|
||||||
protected override object? GetKey(Song key)
|
protected override object? GetKey(Song key)
|
||||||
@@ -23,14 +30,11 @@ public class AudioImageMemoryCache(IAudioImageExtractor audioImageExtractor) : M
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SongPictureInfo? Fetch(Song key)
|
protected override ValueTask<SongPictureInfo?> FetchAsync(Song key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return audioImageExtractor.ExtractImage(key.FileName);
|
SongPictureInfo? songPictureInfo = audioImageExtractor.ExtractImage(key.FileName);
|
||||||
}
|
|
||||||
|
|
||||||
protected override void AddToCache(object key, SongPictureInfo entry)
|
return ValueTask.FromResult(songPictureInfo);
|
||||||
{
|
|
||||||
base.AddToCache(key, entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override long GetEntrySize(SongPictureInfo entry)
|
protected override long GetEntrySize(SongPictureInfo entry)
|
||||||
|
|||||||
@@ -1,29 +1,88 @@
|
|||||||
namespace Harmonia.Core.Caching;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Harmonia.Core.Caching;
|
||||||
|
|
||||||
public abstract class Cache<TKey, TValue> : ICache<TKey, TValue> where TKey : notnull
|
public abstract class Cache<TKey, TValue> : ICache<TKey, TValue> where TKey : notnull
|
||||||
{
|
{
|
||||||
|
private readonly ConcurrentDictionary<object, SemaphoreSlim> _locks;
|
||||||
|
private readonly SemaphoreSlim _throttler;
|
||||||
|
|
||||||
|
protected abstract int MaxConcurrentRequests { get; }
|
||||||
protected abstract TValue? TryGetValue(object key);
|
protected abstract TValue? TryGetValue(object key);
|
||||||
protected abstract TValue? Fetch(TKey key);
|
protected abstract ValueTask<TValue?> FetchAsync(TKey key, CancellationToken cancellationToken);
|
||||||
protected abstract void AddToCache(object key, TValue value);
|
protected abstract void AddToCache(object key, TValue value);
|
||||||
|
|
||||||
public TValue? Get(TKey key)
|
public Cache()
|
||||||
{
|
{
|
||||||
object? actualKey = GetKey(key);
|
_locks = new();
|
||||||
|
_throttler = new SemaphoreSlim(MaxConcurrentRequests, MaxConcurrentRequests);
|
||||||
if (actualKey == null)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
return TryGetValue(actualKey) ?? Refresh(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TValue? Refresh(TKey key)
|
public async Task<TValue?> GetAsync(TKey key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
object? actualKey = GetKey(key);
|
object? actualKey = GetKey(key);
|
||||||
|
|
||||||
if (actualKey == null)
|
if (actualKey == null)
|
||||||
return default;
|
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<TValue?> 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<TValue?> RefreshAsync(TKey key, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
object? actualKey = GetKey(key);
|
||||||
|
|
||||||
|
if (actualKey == null)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
TValue? value = await FetchAsync(key, cancellationToken);
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return default;
|
return default;
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
public interface ICache<TKey, TValue>
|
public interface ICache<TKey, TValue>
|
||||||
{
|
{
|
||||||
TValue? Get(TKey key);
|
Task<TValue?> GetAsync(TKey key, CancellationToken cancellationToken);
|
||||||
TValue? Refresh(TKey key);
|
Task<TValue?> RefreshAsync(TKey key, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,12 @@ public abstract class MemoryCache<TKey, TValue> : Cache<TKey, TValue> where TKey
|
|||||||
{
|
{
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
|
||||||
protected abstract long? SizeLimit { get; }
|
protected abstract MemoryCacheOptions Options { get; }
|
||||||
protected abstract double CompactionPercentage { get; }
|
|
||||||
protected abstract TimeSpan SlidingExpiration { get; }
|
protected abstract TimeSpan SlidingExpiration { get; }
|
||||||
|
|
||||||
public MemoryCache()
|
public MemoryCache()
|
||||||
{
|
{
|
||||||
MemoryCacheOptions memoryCacheOptions = new()
|
_memoryCache = new MemoryCache(Options);
|
||||||
{
|
|
||||||
SizeLimit = SizeLimit,
|
|
||||||
CompactionPercentage = CompactionPercentage
|
|
||||||
};
|
|
||||||
|
|
||||||
_memoryCache = new MemoryCache(memoryCacheOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TValue? TryGetValue(object key)
|
protected override TValue? TryGetValue(object key)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Harmonia.Core.Engine;
|
using Harmonia.Core.Caching;
|
||||||
|
using Harmonia.Core.Engine;
|
||||||
using Harmonia.Core.Imaging;
|
using Harmonia.Core.Imaging;
|
||||||
using Harmonia.Core.Player;
|
using Harmonia.Core.Player;
|
||||||
using Harmonia.Core.Playlists;
|
using Harmonia.Core.Playlists;
|
||||||
@@ -16,6 +17,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<IAudioPlayer, AudioPlayer>();
|
services.AddSingleton<IAudioPlayer, AudioPlayer>();
|
||||||
services.AddSingleton<ITagResolver, TagLibTagResolver>();
|
services.AddSingleton<ITagResolver, TagLibTagResolver>();
|
||||||
services.AddSingleton<IAudioImageExtractor, AudioImageExtractor>();
|
services.AddSingleton<IAudioImageExtractor, AudioImageExtractor>();
|
||||||
|
services.AddSingleton<IAudioImageCache, AudioImageMemoryCache>();
|
||||||
services.AddSingleton<IAudioFileScanner, AudioFileScanner>();
|
services.AddSingleton<IAudioFileScanner, AudioFileScanner>();
|
||||||
services.AddSingleton<IPlaylistRepository, PlaylistRepository>();
|
services.AddSingleton<IPlaylistRepository, PlaylistRepository>();
|
||||||
services.AddSingleton<IPlaylistManager, PlaylistManager>();
|
services.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|||||||
Reference in New Issue
Block a user