Fixed: Refreshing Plex Server series in high volume systems

(cherry picked from commit 3fb55e9defdb90ab807fcacff249193a4d6114d5)
pull/1392/head
Taloth Saldono 4 years ago committed by ta264
parent 7b0802cfd6
commit a5b1711827

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
@ -108,7 +108,7 @@ namespace NzbDrone.Common.Test.CacheTests
public void should_clear_expired_when_they_expire()
{
int hitCount = 0;
_cachedString = new Cached<string>();
_cachedString = new Cached<string>(rollingExpiry: true);
for (int i = 0; i < 10; i++)
{
@ -123,9 +123,9 @@ namespace NzbDrone.Common.Test.CacheTests
Thread.Sleep(100);
}
Thread.Sleep(1000);
Thread.Sleep(2000);
hitCount.Should().BeInRange(3, 7);
hitCount.Should().Be(1);
_cachedString.Values.Should().HaveCount(0);
}
}

@ -8,6 +8,7 @@ namespace NzbDrone.Common.Cache
{
ICached<T> GetCache<T>(Type host);
ICached<T> GetCache<T>(Type host, string name);
ICached<T> GetRollingCache<T>(Type host, string name, TimeSpan defaultLifeTime);
ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null);
void Clear();
ICollection<ICached> Caches { get; }
@ -43,6 +44,14 @@ namespace NzbDrone.Common.Cache
return (ICached<T>)_cache.Get(host.FullName + "_" + name, () => new Cached<T>());
}
public ICached<T> GetRollingCache<T>(Type host, string name, TimeSpan defaultLifeTime)
{
Ensure.That(host, () => host).IsNotNull();
Ensure.That(name, () => name).IsNotNullOrWhiteSpace();
return (ICached<T>)_cache.Get(host.FullName + "_" + name, () => new Cached<T>(defaultLifeTime, true));
}
public ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null)
{
Ensure.That(host, () => host).IsNotNull();

@ -1,8 +1,10 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache
{
@ -29,40 +31,56 @@ namespace NzbDrone.Common.Cache
}
private readonly ConcurrentDictionary<string, CacheItem> _store;
private readonly TimeSpan? _defaultLifeTime;
private readonly bool _rollingExpiry;
public Cached()
public Cached(TimeSpan? defaultLifeTime = null, bool rollingExpiry = false)
{
_store = new ConcurrentDictionary<string, CacheItem>();
_defaultLifeTime = defaultLifeTime;
_rollingExpiry = rollingExpiry;
}
public void Set(string key, T value, TimeSpan? lifetime = null)
public void Set(string key, T value, TimeSpan? lifeTime = null)
{
Ensure.That(key, () => key).IsNotNullOrWhiteSpace();
_store[key] = new CacheItem(value, lifetime);
if (lifetime != null)
_store[key] = new CacheItem(value, lifeTime ?? _defaultLifeTime);
if (lifeTime != null)
{
System.Threading.Tasks.Task.Delay(lifetime.Value).ContinueWith(t => _store.TryRemove(key, out var temp));
ScheduleTryRemove(key, lifeTime.Value);
}
}
public T Find(string key)
{
CacheItem value;
_store.TryGetValue(key, out value);
if (value == null)
CacheItem cacheItem;
if (!_store.TryGetValue(key, out cacheItem))
{
return default(T);
}
if (value.IsExpired())
if (cacheItem.IsExpired())
{
_store.TryRemove(key, out value);
return default(T);
if (TryRemove(key, cacheItem))
{
return default(T);
}
if (!_store.TryGetValue(key, out cacheItem))
{
return default(T);
}
}
if (_rollingExpiry && _defaultLifeTime.HasValue)
{
_store.TryUpdate(key, new CacheItem(cacheItem.Object, _defaultLifeTime.Value), cacheItem);
ScheduleTryRemove(key, _defaultLifeTime.Value);
}
return value.Object;
return cacheItem.Object;
}
public void Remove(string key)
@ -77,20 +95,32 @@ namespace NzbDrone.Common.Cache
{
Ensure.That(key, () => key).IsNotNullOrWhiteSpace();
lifeTime = lifeTime ?? _defaultLifeTime;
CacheItem cacheItem;
T value;
if (!_store.TryGetValue(key, out cacheItem) || cacheItem.IsExpired())
if (_store.TryGetValue(key, out cacheItem) && !cacheItem.IsExpired())
{
value = function();
Set(key, value, lifeTime);
if (_rollingExpiry && lifeTime.HasValue)
{
_store.TryUpdate(key, new CacheItem(cacheItem.Object, lifeTime), cacheItem);
ScheduleTryRemove(key, lifeTime.Value);
}
}
else
{
value = cacheItem.Object;
var newCacheItem = new CacheItem(function(), lifeTime);
if (cacheItem != null && _store.TryUpdate(key, newCacheItem, cacheItem))
{
cacheItem = newCacheItem;
}
else
{
cacheItem = _store.GetOrAdd(key, newCacheItem);
}
}
return value;
return cacheItem.Object;
}
public void Clear()
@ -100,9 +130,11 @@ namespace NzbDrone.Common.Cache
public void ClearExpired()
{
foreach (var cached in _store.Where(c => c.Value.IsExpired()))
var collection = (ICollection<KeyValuePair<string, CacheItem>>)_store;
foreach (var cached in _store.Where(c => c.Value.IsExpired()).ToList())
{
Remove(cached.Key);
collection.Remove(cached);
}
}
@ -113,5 +145,23 @@ namespace NzbDrone.Common.Cache
return _store.Values.Select(c => c.Object).ToList();
}
}
private bool TryRemove(string key, CacheItem value)
{
var collection = (ICollection<KeyValuePair<string, CacheItem>>)_store;
return collection.Remove(new KeyValuePair<string, CacheItem>(key, value));
}
private void ScheduleTryRemove(string key, TimeSpan lifeTime)
{
Task.Delay(lifeTime).ContinueWith(t =>
{
if (_store.TryGetValue(key, out var cacheItem) && cacheItem.IsExpired())
{
_store.TryRemove(key, out _);
}
});
}
}
}

@ -49,7 +49,7 @@ namespace NzbDrone.Core.Books
_authorRepository = authorRepository;
_eventAggregator = eventAggregator;
_authorPathBuilder = authorPathBuilder;
_cache = cacheManager.GetCache<List<Author>>(GetType());
_cache = cacheManager.GetRollingCache<List<Author>>(GetType(), "authorcache", TimeSpan.FromSeconds(30));
_logger = logger;
}

Loading…
Cancel
Save