update caching to use automatic timer instead of request based to prevent task cancellation
This commit is contained in:
parent
08bcd23cbc
commit
a88b30562c
@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
|
||||
|
||||
private readonly TimeSpan? _cacheTimeSpan =
|
||||
Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1);
|
||||
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10);
|
||||
|
||||
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
|
||||
IDataValueCache<EFClient, (int, int)> serverStatsCache,
|
||||
@ -36,7 +36,8 @@ namespace IW4MAdmin.Application.Misc
|
||||
_clientHistoryCache = clientHistoryCache;
|
||||
}
|
||||
|
||||
public async Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||
public async Task<(int?, DateTime?)>
|
||||
MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
_snapshotCache.SetCacheItem(async (snapshots, cancellationToken) =>
|
||||
@ -83,7 +84,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
|
||||
|
||||
return (maxClients, maxClientsTime);
|
||||
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan);
|
||||
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true);
|
||||
|
||||
try
|
||||
{
|
||||
@ -107,7 +108,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
cancellationToken);
|
||||
|
||||
return (count, recentCount);
|
||||
}, nameof(_serverStatsCache), _cacheTimeSpan);
|
||||
}, nameof(_serverStatsCache), _cacheTimeSpan, true);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -5,9 +5,10 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Data.Abstractions
|
||||
{
|
||||
public interface IDataValueCache<T, V> where T : class
|
||||
public interface IDataValueCache<TEntityType, TReturnType> where TEntityType : class
|
||||
{
|
||||
void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> itemGetter, string keyName, TimeSpan? expirationTime = null);
|
||||
Task<V> GetCacheItem(string keyName, CancellationToken token = default);
|
||||
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
|
||||
TimeSpan? expirationTime = null, bool autoRefresh = false);
|
||||
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
<Authors />
|
||||
<PackageVersion>1.0.7</PackageVersion>
|
||||
<PackageVersion>1.0.8</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,58 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Data.Helpers
|
||||
{
|
||||
public class DataValueCache<T, V> : IDataValueCache<T, V> where T : class
|
||||
public class DataValueCache<TEntityType, TReturnType> : IDataValueCache<TEntityType, TReturnType>
|
||||
where TEntityType : class
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly Dictionary<string, CacheState> _cacheStates = new Dictionary<string, CacheState>();
|
||||
|
||||
private readonly ConcurrentDictionary<string, CacheState> _cacheStates =
|
||||
new ConcurrentDictionary<string, CacheState>();
|
||||
|
||||
private bool _autoRefresh;
|
||||
private const int DefaultExpireMinutes = 15;
|
||||
private Timer _timer;
|
||||
|
||||
private class CacheState
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public DateTime LastRetrieval { get; set; }
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
public Func<DbSet<T>, CancellationToken, Task<V>> Getter { get; set; }
|
||||
public V Value { get; set; }
|
||||
public Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> Getter { get; set; }
|
||||
public TReturnType Value { get; set; }
|
||||
|
||||
public bool IsExpired => ExpirationTime != TimeSpan.MaxValue &&
|
||||
(DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
|
||||
}
|
||||
|
||||
public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
|
||||
public DataValueCache(ILogger<DataValueCache<TEntityType, TReturnType>> logger,
|
||||
IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
public void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> getter, string key,
|
||||
TimeSpan? expirationTime = null)
|
||||
~DataValueCache()
|
||||
{
|
||||
_timer?.Stop();
|
||||
_timer?.Dispose();
|
||||
}
|
||||
|
||||
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
|
||||
TimeSpan? expirationTime = null, bool autoRefresh = false)
|
||||
{
|
||||
if (_cacheStates.ContainsKey(key))
|
||||
{
|
||||
_logger.LogDebug("Cache key {key} is already added", key);
|
||||
_logger.LogDebug("Cache key {Key} is already added", key);
|
||||
return;
|
||||
}
|
||||
|
||||
var state = new CacheState()
|
||||
var state = new CacheState
|
||||
{
|
||||
Key = key,
|
||||
Getter = getter,
|
||||
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
||||
};
|
||||
|
||||
_cacheStates.Add(key, state);
|
||||
_autoRefresh = autoRefresh;
|
||||
|
||||
_cacheStates.TryAdd(key, state);
|
||||
|
||||
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
|
||||
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public async Task<V> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
|
||||
public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_cacheStates.ContainsKey(keyName))
|
||||
{
|
||||
@ -61,7 +86,9 @@ namespace Data.Helpers
|
||||
|
||||
var state = _cacheStates[keyName];
|
||||
|
||||
if (state.IsExpired || state.Value == null)
|
||||
// when auto refresh is off we want to check the expiration and value
|
||||
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
|
||||
if ((state.IsExpired || state.Value == null) && !_autoRefresh || _autoRefresh && state.Value == null)
|
||||
{
|
||||
await RunCacheUpdate(state, cancellationToken);
|
||||
}
|
||||
@ -73,15 +100,16 @@ namespace Data.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
var set = context.Set<T>();
|
||||
var set = context.Set<TEntityType>();
|
||||
var value = await state.Getter(set, token);
|
||||
state.Value = value;
|
||||
state.LastRetrieval = DateTime.Now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not get cached value for {key}", state.Key);
|
||||
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -20,7 +20,7 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.15.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -123,9 +123,9 @@ namespace SharedLibraryCore.Configuration
|
||||
if (passwords.Length > 1)
|
||||
{
|
||||
var (index, value) =
|
||||
loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection("Other", null,
|
||||
loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection(loc["SETUP_RCON_PASSWORD_MANUAL"], null,
|
||||
passwords.Select(pw =>
|
||||
$"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}").ToArray());
|
||||
$"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}").ToArray());
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
|
@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2021.8.31.1</Version>
|
||||
<Version>2021.11.15.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -19,7 +19,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2021.8.31.1</PackageVersion>
|
||||
<PackageVersion>2021.11.15.1</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
@ -44,7 +44,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.7" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.8" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user