Adjusted cache to allow for throttling and locking.

This commit is contained in:
2025-02-26 23:10:36 -05:00
parent 6835431d1e
commit c1a7d23096
5 changed files with 89 additions and 31 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);
} }

View File

@@ -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)

View File

@@ -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>();