Fixed: Refreshing Plex Server series in high volume systems

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

@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using FluentAssertions; using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
@ -108,7 +108,7 @@ namespace NzbDrone.Common.Test.CacheTests
public void should_clear_expired_when_they_expire() public void should_clear_expired_when_they_expire()
{ {
int hitCount = 0; int hitCount = 0;
_cachedString = new Cached<string>(); _cachedString = new Cached<string>(rollingExpiry: true);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
@ -123,9 +123,9 @@ namespace NzbDrone.Common.Test.CacheTests
Thread.Sleep(100); Thread.Sleep(100);
} }
Thread.Sleep(1000); Thread.Sleep(2000);
hitCount.Should().BeInRange(3, 7); hitCount.Should().Be(1);
_cachedString.Values.Should().HaveCount(0); _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);
ICached<T> GetCache<T>(Type host, string name); 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); ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null);
void Clear(); void Clear();
ICollection<ICached> Caches { get; } ICollection<ICached> Caches { get; }
@ -43,6 +44,14 @@ namespace NzbDrone.Common.Cache
return (ICached<T>)_cache.Get(host.FullName + "_" + name, () => new Cached<T>()); 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) public ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null)
{ {
Ensure.That(host, () => host).IsNotNull(); Ensure.That(host, () => host).IsNotNull();

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Cache namespace NzbDrone.Common.Cache
{ {
@ -29,40 +31,56 @@ namespace NzbDrone.Common.Cache
} }
private readonly ConcurrentDictionary<string, CacheItem> _store; 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>(); _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(); 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) public T Find(string key)
{ {
CacheItem value; CacheItem cacheItem;
_store.TryGetValue(key, out value); if (!_store.TryGetValue(key, out cacheItem))
{
return default(T);
}
if (value == null) if (cacheItem.IsExpired())
{
if (TryRemove(key, cacheItem))
{ {
return default(T); return default(T);
} }
if (value.IsExpired()) if (!_store.TryGetValue(key, out cacheItem))
{ {
_store.TryRemove(key, out value);
return default(T); 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) public void Remove(string key)
@ -77,20 +95,32 @@ namespace NzbDrone.Common.Cache
{ {
Ensure.That(key, () => key).IsNotNullOrWhiteSpace(); Ensure.That(key, () => key).IsNotNullOrWhiteSpace();
lifeTime = lifeTime ?? _defaultLifeTime;
CacheItem cacheItem; CacheItem cacheItem;
T value;
if (!_store.TryGetValue(key, out cacheItem) || cacheItem.IsExpired()) if (_store.TryGetValue(key, out cacheItem) && !cacheItem.IsExpired())
{
if (_rollingExpiry && lifeTime.HasValue)
{ {
value = function(); _store.TryUpdate(key, new CacheItem(cacheItem.Object, lifeTime), cacheItem);
Set(key, value, lifeTime); ScheduleTryRemove(key, lifeTime.Value);
}
} }
else 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() public void Clear()
@ -100,9 +130,11 @@ namespace NzbDrone.Common.Cache
public void ClearExpired() 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(); 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; _authorRepository = authorRepository;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_authorPathBuilder = authorPathBuilder; _authorPathBuilder = authorPathBuilder;
_cache = cacheManager.GetCache<List<Author>>(GetType()); _cache = cacheManager.GetRollingCache<List<Author>>(GetType(), "authorcache", TimeSpan.FromSeconds(30));
_logger = logger; _logger = logger;
} }

Loading…
Cancel
Save