|
|
@@ -1,378 +0,0 @@ |
|
|
|
// Copyright (c) .NET Core Community. All rights reserved. |
|
|
|
// Licensed under the MIT License. See License.txt in the project root for license information. |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using System.Threading; |
|
|
|
|
|
|
|
namespace DotNetCore.CAP.Internal |
|
|
|
{ |
|
|
|
#region Cache<T> class |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// This is a generic cache subsystem based on key/value pairs, where key is generic, too. Key must be unique. |
|
|
|
/// Every cache entry has its own timeout. |
|
|
|
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on |
|
|
|
/// <see cref="ThreadPool" /> threads). |
|
|
|
/// </summary> |
|
|
|
// ReSharper disable once InheritdocConsiderUsage |
|
|
|
// ReSharper disable once InconsistentNaming |
|
|
|
internal class Cache<K, T> : IDisposable |
|
|
|
{ |
|
|
|
#region Constructor and class members |
|
|
|
|
|
|
|
private readonly Dictionary<K, T> _cache = new Dictionary<K, T>(); |
|
|
|
private readonly Dictionary<K, Timer> _timers = new Dictionary<K, Timer>(); |
|
|
|
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); |
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
#region IDisposable implementation & Clear |
|
|
|
|
|
|
|
private bool disposed; |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. |
|
|
|
/// </summary> |
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
Dispose(true); |
|
|
|
GC.SuppressFinalize(this); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Releases unmanaged and - optionally - managed resources. |
|
|
|
/// </summary> |
|
|
|
/// <param name="disposing"> |
|
|
|
/// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources. |
|
|
|
/// </param> |
|
|
|
protected virtual void Dispose(bool disposing) |
|
|
|
{ |
|
|
|
if (!disposed) |
|
|
|
{ |
|
|
|
disposed = true; |
|
|
|
|
|
|
|
if (disposing) |
|
|
|
{ |
|
|
|
// Dispose managed resources. |
|
|
|
Clear(); |
|
|
|
_locker.Dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
// Dispose unmanaged resources |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Clears the entire cache and disposes all active timers. |
|
|
|
/// </summary> |
|
|
|
public void Clear() |
|
|
|
{ |
|
|
|
_locker.EnterWriteLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
foreach (var t in _timers.Values) |
|
|
|
{ |
|
|
|
t.Dispose(); |
|
|
|
} |
|
|
|
} |
|
|
|
catch |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
_timers.Clear(); |
|
|
|
_cache.Clear(); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitWriteLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
#region CheckTimer |
|
|
|
|
|
|
|
// Checks whether a specific timer already exists and adds a new one, if not |
|
|
|
private void CheckTimer(K key, TimeSpan? cacheTimeout, bool restartTimerIfExists) |
|
|
|
{ |
|
|
|
Timer timer; |
|
|
|
|
|
|
|
if (_timers.TryGetValue(key, out timer)) |
|
|
|
{ |
|
|
|
if (restartTimerIfExists) |
|
|
|
{ |
|
|
|
timer.Change( |
|
|
|
cacheTimeout ?? Timeout.InfiniteTimeSpan, |
|
|
|
Timeout.InfiniteTimeSpan); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
_timers.Add( |
|
|
|
key, |
|
|
|
new Timer( |
|
|
|
RemoveByTimer, |
|
|
|
key, |
|
|
|
cacheTimeout ?? Timeout.InfiniteTimeSpan, |
|
|
|
Timeout.InfiniteTimeSpan)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void RemoveByTimer(object state) |
|
|
|
{ |
|
|
|
Remove((K) state); |
|
|
|
} |
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
#region AddOrUpdate, Get, Remove, Exists, Clear |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Adds or updates the specified cache-key with the specified cacheObject and applies a specified timeout (in seconds) |
|
|
|
/// to this key. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to add or update.</param> |
|
|
|
/// <param name="cacheObject">The cache object to store.</param> |
|
|
|
/// <param name="cacheTimeout"> |
|
|
|
/// The cache timeout (lifespan) of this object. Must be 1 or greater. |
|
|
|
/// Specify Timeout.Infinite to keep the entry forever. |
|
|
|
/// </param> |
|
|
|
/// <param name="restartTimerIfExists"> |
|
|
|
/// (Optional). If set to <c>true</c>, the timer for this cacheObject will be reset if the object already |
|
|
|
/// exists in the cache. (Default = false). |
|
|
|
/// </param> |
|
|
|
public void AddOrUpdate(K key, T cacheObject, TimeSpan? cacheTimeout, bool restartTimerIfExists = false) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterWriteLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
CheckTimer(key, cacheTimeout, restartTimerIfExists); |
|
|
|
|
|
|
|
if (!_cache.ContainsKey(key)) |
|
|
|
{ |
|
|
|
_cache.Add(key, cacheObject); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
_cache[key] = cacheObject; |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitWriteLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Adds or updates the specified cache-key with the specified cacheObject and applies <c>Timeout.Infinite</c> to this |
|
|
|
/// key. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to add or update.</param> |
|
|
|
/// <param name="cacheObject">The cache object to store.</param> |
|
|
|
public void AddOrUpdate(K key, T cacheObject) |
|
|
|
{ |
|
|
|
AddOrUpdate(key, cacheObject, Timeout.InfiniteTimeSpan); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Gets the cache entry with the specified key or returns <c>default(T)</c> if the key is not found. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to retrieve.</param> |
|
|
|
/// <returns>The object from the cache or <c>default(T)</c>, if not found.</returns> |
|
|
|
public T this[K key] => Get(key); |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Gets the cache entry with the specified key or return <c>default(T)</c> if the key is not found. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to retrieve.</param> |
|
|
|
/// <returns>The object from the cache or <c>default(T)</c>, if not found.</returns> |
|
|
|
public T Get(K key) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
return default(T); |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterReadLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
T rv; |
|
|
|
return _cache.TryGetValue(key, out rv) ? rv : default(T); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitReadLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Tries to gets the cache entry with the specified key. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The key.</param> |
|
|
|
/// <param name="value">(out) The value, if found, or <c>default(T)</c>, if not.</param> |
|
|
|
/// <returns><c>True</c>, if <c>key</c> exists, otherwise <c>false</c>.</returns> |
|
|
|
public bool TryGet(K key, out T value) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
value = default(T); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterReadLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
return _cache.TryGetValue(key, out value); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitReadLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Removes a series of cache entries in a single call for all key that match the specified key pattern. |
|
|
|
/// </summary> |
|
|
|
/// <param name="keyPattern">The key pattern to remove. The Predicate has to return true to get key removed.</param> |
|
|
|
public void Remove(Predicate<K> keyPattern) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterWriteLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
var removers = (from k in _cache.Keys.Cast<K>() |
|
|
|
where keyPattern(k) |
|
|
|
select k).ToList(); |
|
|
|
|
|
|
|
foreach (var workKey in removers) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
_timers[workKey].Dispose(); |
|
|
|
} |
|
|
|
catch |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
_timers.Remove(workKey); |
|
|
|
_cache.Remove(workKey); |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitWriteLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Removes the specified cache entry with the specified key. |
|
|
|
/// If the key is not found, no exception is thrown, the statement is just ignored. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to remove.</param> |
|
|
|
public void Remove(K key) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterWriteLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
if (_cache.ContainsKey(key)) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
_timers[key].Dispose(); |
|
|
|
} |
|
|
|
catch |
|
|
|
{ |
|
|
|
} |
|
|
|
|
|
|
|
_timers.Remove(key); |
|
|
|
_cache.Remove(key); |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitWriteLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Checks if a specified key exists in the cache. |
|
|
|
/// </summary> |
|
|
|
/// <param name="key">The cache-key to check.</param> |
|
|
|
/// <returns><c>True</c> if the key exists in the cache, otherwise <c>False</c>.</returns> |
|
|
|
public bool Exists(K key) |
|
|
|
{ |
|
|
|
if (disposed) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
_locker.EnterReadLock(); |
|
|
|
try |
|
|
|
{ |
|
|
|
return _cache.ContainsKey(key); |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
_locker.ExitReadLock(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#endregion |
|
|
|
} |
|
|
|
|
|
|
|
#endregion |
|
|
|
|
|
|
|
#region Other Cache classes (derived) |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// This is a generic cache subsystem based on key/value pairs, where key is a string. |
|
|
|
/// You can add any item to this cache as long as the key is unique, so treat keys as something like namespaces and |
|
|
|
/// build them with a |
|
|
|
/// specific system/syntax in your application. |
|
|
|
/// Every cache entry has its own timeout. |
|
|
|
/// Cache is thread safe and will delete expired entries on its own using System.Threading.Timers (which run on |
|
|
|
/// <see cref="ThreadPool" /> threads). |
|
|
|
/// </summary> |
|
|
|
/// <summary> |
|
|
|
/// The non-generic Cache class instanciates a Cache{object} that can be used with any type of (mixed) contents. |
|
|
|
/// It also publishes a static <c>.Global</c> member, so a cache can be used even without creating a dedicated |
|
|
|
/// instance. |
|
|
|
/// The <c>.Global</c> member is lazy instanciated. |
|
|
|
/// </summary> |
|
|
|
internal class CapCache : Cache<string, object> |
|
|
|
{ |
|
|
|
#region Static Global Cache instance |
|
|
|
|
|
|
|
private static readonly Lazy<CapCache> global = new Lazy<CapCache>(); |
|
|
|
|
|
|
|
/// <summary> |
|
|
|
/// Gets the global shared cache instance valid for the entire process. |
|
|
|
/// </summary> |
|
|
|
/// <value> |
|
|
|
/// The global shared cache instance. |
|
|
|
/// </value> |
|
|
|
public static CapCache Global => global.Value; |
|
|
|
|
|
|
|
#endregion |
|
|
|
} |
|
|
|
|
|
|
|
#endregion |
|
|
|
} |