From b851dfdf156ba8c500fa8a3dce5a9b80bdcbcf0f Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Tue, 25 Oct 2016 10:16:38 +0200 Subject: [PATCH 1/8] Implemented item wrapper for http and memory cache --- build.fsx | 4 +- .../DoubleCache.SystemWebCaching/HttpCache.cs | 46 +++++++++++-------- source/DoubleCache/DoubleCache.csproj | 1 + source/DoubleCache/LocalCache/MemCache.cs | 1 + .../DoubleCacheTests/DoubleCacheTests.csproj | 1 + .../HttpCacheIntegrationTests.cs | 15 +++++- 6 files changed, 48 insertions(+), 20 deletions(-) diff --git a/build.fsx b/build.fsx index f142e70..b2848b8 100644 --- a/build.fsx +++ b/build.fsx @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj" ++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj" let testReferences = !! "source/DoubleCacheTests/*.csproj" -let version = "1.6.0" +let version = "2.0.0-beta.1" let commitHash = Information.getCurrentSHA1(".") let projectName = "DoubleCache" @@ -66,6 +66,7 @@ Target "CreateNuget" (fun _ -> WorkingDir = buildDir Version = version Publish = false + Prerelease = true Dependencies = [ "StackExchange.Redis", "1.0.488" "MsgPack.Cli", "0.6.5" @@ -85,6 +86,7 @@ Target "CreateNuget" (fun _ -> WorkingDir = buildDir Version = version Publish = false + Prerelease = true Dependencies = [ "DoubleCache", version ] diff --git a/source/DoubleCache.SystemWebCaching/HttpCache.cs b/source/DoubleCache.SystemWebCaching/HttpCache.cs index cf333e7..3e6f43e 100644 --- a/source/DoubleCache.SystemWebCaching/HttpCache.cs +++ b/source/DoubleCache.SystemWebCaching/HttpCache.cs @@ -1,14 +1,21 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Web.Caching; -namespace DoubleCache.SystemWebHttpCache +namespace DoubleCache.SystemWebCaching { public class HttpCache : ICacheAside { + internal class CacheItemWrapper + { + internal object Item { get; } + + internal CacheItemWrapper(object item) + { + Item = item; + } + } + private readonly Cache _cache; private readonly TimeSpan? _defaultTtl; @@ -20,12 +27,12 @@ public HttpCache(Cache cache, TimeSpan? defaultTtl = null) public void Add(string key, T item) { - _cache.Add(key, item, null, CalculateExpire(_defaultTtl), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + Add(key,item,_defaultTtl); } public void Add(string key, T item, TimeSpan? timeToLive) { - _cache.Add(key, item, null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + _cache.Add(key, new CacheItemWrapper(item), null, CalculateExpire(timeToLive), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } public T Get(string key, Func dataRetriever) where T : class @@ -35,13 +42,14 @@ public T Get(string key, Func dataRetriever) where T : class public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class { - var item = _cache.Get(key) as T; - if (item != null) - return item; - { - item = dataRetriever.Invoke(); - Add(key, item, timeToLive); - } + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = dataRetriever.Invoke(); + + Add(key, item, timeToLive); + return item; } @@ -52,13 +60,15 @@ public object Get(string key, Type type, Func dataRetriever) public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { - var item = _cache.Get(key); - if (item != null && item.GetType() == type) - return item; + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = dataRetriever.Invoke(); - item = dataRetriever.Invoke(); Add(key, item, timeToLive); - return item.GetType() == type ? item : null; + + return item; } public Task GetAsync(string key, Func> dataRetriever) where T : class diff --git a/source/DoubleCache/DoubleCache.csproj b/source/DoubleCache/DoubleCache.csproj index fc6c56d..11abd89 100644 --- a/source/DoubleCache/DoubleCache.csproj +++ b/source/DoubleCache/DoubleCache.csproj @@ -56,6 +56,7 @@ + diff --git a/source/DoubleCache/LocalCache/MemCache.cs b/source/DoubleCache/LocalCache/MemCache.cs index 731acc4..17bf11e 100644 --- a/source/DoubleCache/LocalCache/MemCache.cs +++ b/source/DoubleCache/LocalCache/MemCache.cs @@ -4,6 +4,7 @@ namespace DoubleCache.LocalCache { + [Obsolete("This implementation does not accept caching of null values, consider using WrappingMemoryCache instead")] public class MemCache : ICacheAside { private readonly TimeSpan? _defaultTtl; diff --git a/source/DoubleCacheTests/DoubleCacheTests.csproj b/source/DoubleCacheTests/DoubleCacheTests.csproj index 8b20834..ea92b93 100644 --- a/source/DoubleCacheTests/DoubleCacheTests.csproj +++ b/source/DoubleCacheTests/DoubleCacheTests.csproj @@ -79,6 +79,7 @@ + diff --git a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs index ae91650..a15e98b 100644 --- a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs @@ -1,7 +1,8 @@ using System; using System.IO; using System.Web; -using DoubleCache.SystemWebHttpCache; +using DoubleCache.SystemWebCaching; +using Shouldly; using Xunit; namespace DoubleCacheTests.IntegrationTests @@ -17,5 +18,17 @@ public HttpCacheIntegrationTests() _cacheImplementation = new HttpCache(context.Cache, TimeSpan.FromMinutes(1)); } + + [Fact] + public void Cache_Null_Returns_Null() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Add(key, null); + + var result = _cacheImplementation.Get(key, () => "a"); + + result.ShouldBeNull(); + } } } From 8caa594d67ab3669de9afdba868b5231dadd1556 Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Tue, 25 Oct 2016 10:17:22 +0200 Subject: [PATCH 2/8] Adjusted default factory to use Wrapping memory cache for null support --- source/DoubleCache/CacheFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DoubleCache/CacheFactory.cs b/source/DoubleCache/CacheFactory.cs index 53999d8..b6ba1d1 100644 --- a/source/DoubleCache/CacheFactory.cs +++ b/source/DoubleCache/CacheFactory.cs @@ -11,7 +11,7 @@ public static ICacheAside CreatePubSubDoubleCache(IConnectionMultiplexer redisCo { var remoteCache = new RedisCache(redisConnection.GetDatabase(), itemSerializer, defaultTtl); return new DoubleCache( - new SubscribingCache(new LocalCache.MemCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)), + new SubscribingCache(new LocalCache.WrappingMemoryCache(defaultTtl), new RedisSubscriber(redisConnection, remoteCache, itemSerializer)), new PublishingCache(remoteCache, new RedisPublisher(redisConnection, itemSerializer))); } } From 6dd11eaf4f0204d3834644bf1ec8a0736cf80f3d Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Tue, 25 Oct 2016 10:21:43 +0200 Subject: [PATCH 3/8] added missing files --- .../LocalCache/WrappingMemoryCache.cs | 112 ++++++++++++++++++ .../WrappingMemoryCacheTests.cs | 29 +++++ 2 files changed, 141 insertions(+) create mode 100644 source/DoubleCache/LocalCache/WrappingMemoryCache.cs create mode 100644 source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs diff --git a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs new file mode 100644 index 0000000..31c5167 --- /dev/null +++ b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs @@ -0,0 +1,112 @@ +using System; +using System.Runtime.Caching; +using System.Threading.Tasks; + +namespace DoubleCache.LocalCache +{ + public class WrappingMemoryCache : ICacheAside + { + internal class CacheItemWrapper + { + internal object Item { get; } + + internal CacheItemWrapper(object item) + { + Item = item; + } + } + + private readonly TimeSpan? _defaultTtl; + + public WrappingMemoryCache(TimeSpan? defaultTtl = null) + { + _defaultTtl = defaultTtl; + } + public void Add(string key, T item) + { + Add(key,item,_defaultTtl); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + var policy = new CacheItemPolicy(); + + if (timeToLive.HasValue) + policy.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(timeToLive.Value); + MemoryCache.Default.Set(key, new CacheItemWrapper(item), policy); + } + + public T Get(string key, Func dataRetriever) where T : class + { + return Get(key, dataRetriever, _defaultTtl); + } + + public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T : class + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = dataRetriever.Invoke(); + Add(key, item, timeToLive); + + return item; + } + + public object Get(string key, Type type, Func dataRetriever) + { + return Get(key, type, dataRetriever, _defaultTtl); + } + + public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = dataRetriever.Invoke(); + Add(key, item, timeToLive); + return item.GetType() == type ? item : null; + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return GetAsync(key, type, dataRetriever, _defaultTtl); + } + + public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + var item = await dataRetriever.Invoke(); + Add(key, item, timeToLive); + return item.GetType() == type ? item : null; + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return GetAsync(key, dataRetriever, _defaultTtl); + } + + public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + var wrapper = MemoryCache.Default.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = await dataRetriever.Invoke(); + Add(key, item, timeToLive); + + return item; + } + + public void Remove(string key) + { + MemoryCache.Default.Remove(key); + } + + public TimeSpan? DefaultTtl { get { return _defaultTtl; } } + } +} diff --git a/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs new file mode 100644 index 0000000..1e3430a --- /dev/null +++ b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs @@ -0,0 +1,29 @@ +using System; +using DoubleCache.LocalCache; +using Shouldly; +using Xunit; + +namespace DoubleCacheTests.IntegrationTests +{ + [Trait("Category", "Integration")] + public class WrappingMemoryCacheTests : CacheImplementationTests + { + public WrappingMemoryCacheTests() + { + _key = Guid.NewGuid().ToString(); + _cacheImplementation = new WrappingMemoryCache(TimeSpan.FromMinutes(1)); + } + + [Fact] + public void Cache_Null_Returns_Null() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Add(key,null); + + var result = _cacheImplementation.Get(key,() => "a" ); + + result.ShouldBeNull(); + } + } +} From 5b7078eed0111d6888d14a8b305fe3e58537f74a Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Tue, 25 Oct 2016 10:31:19 +0200 Subject: [PATCH 4/8] Correctly unwraps item for http cache --- build.fsx | 2 -- .../DoubleCache.SystemWebCaching/HttpCache.cs | 29 +++++++++++-------- source/DoubleCache/Properties/AssemblyInfo.cs | 16 +++++----- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/build.fsx b/build.fsx index b2848b8..427483d 100644 --- a/build.fsx +++ b/build.fsx @@ -66,7 +66,6 @@ Target "CreateNuget" (fun _ -> WorkingDir = buildDir Version = version Publish = false - Prerelease = true Dependencies = [ "StackExchange.Redis", "1.0.488" "MsgPack.Cli", "0.6.5" @@ -86,7 +85,6 @@ Target "CreateNuget" (fun _ -> WorkingDir = buildDir Version = version Publish = false - Prerelease = true Dependencies = [ "DoubleCache", version ] diff --git a/source/DoubleCache.SystemWebCaching/HttpCache.cs b/source/DoubleCache.SystemWebCaching/HttpCache.cs index 3e6f43e..5827228 100644 --- a/source/DoubleCache.SystemWebCaching/HttpCache.cs +++ b/source/DoubleCache.SystemWebCaching/HttpCache.cs @@ -78,13 +78,14 @@ public Task GetAsync(string key, Func> dataRetriever) where T : cl public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class { - var item = _cache.Get(key) as T; - if (item != null) - return item; - { - item = await dataRetriever.Invoke(); - Add(key, item, timeToLive); - } + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item as T; + + var item = await dataRetriever.Invoke(); + + Add(key, item, timeToLive); + return item; } @@ -95,13 +96,17 @@ public Task GetAsync(string key, Type type, Func> dataRetri public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) { - var item = _cache.Get(key); - if (item != null && item.GetType() == type) - return item; + var wrapper = _cache.Get(key) as CacheItemWrapper; + if (wrapper != null) + return wrapper.Item; + + + var item = await dataRetriever.Invoke(); + item = item.GetType() == type ? item : null; - item = await dataRetriever.Invoke(); Add(key, item, timeToLive); - return item.GetType() == type ? item : null; + + return item; } public void Remove(string key) diff --git a/source/DoubleCache/Properties/AssemblyInfo.cs b/source/DoubleCache/Properties/AssemblyInfo.cs index dc2b2c2..3afe9b8 100644 --- a/source/DoubleCache/Properties/AssemblyInfo.cs +++ b/source/DoubleCache/Properties/AssemblyInfo.cs @@ -6,19 +6,19 @@ [assembly: AssemblyDescriptionAttribute("Layered distributed cache-aside implementation")] [assembly: GuidAttribute("505f87a8-3062-4070-af1f-cd7358ccd06a")] [assembly: AssemblyProductAttribute("DoubleCache")] -[assembly: AssemblyVersionAttribute("1.5.0")] -[assembly: AssemblyInformationalVersionAttribute("1.5.0")] -[assembly: AssemblyFileVersionAttribute("1.5.0")] -[assembly: AssemblyMetadataAttribute("githash","54c5137866e8115563eb483c019c1ee0fb4c509e")] +[assembly: AssemblyVersionAttribute("2.0.0")] +[assembly: AssemblyInformationalVersionAttribute("2.0.0-beta.1")] +[assembly: AssemblyFileVersionAttribute("2.0.0")] +[assembly: AssemblyMetadataAttribute("githash","6dd11eaf4f0204d3834644bf1ec8a0736cf80f3d")] namespace System { internal static class AssemblyVersionInformation { internal const System.String AssemblyTitle = "DoubleCache"; internal const System.String AssemblyDescription = "Layered distributed cache-aside implementation"; internal const System.String Guid = "505f87a8-3062-4070-af1f-cd7358ccd06a"; internal const System.String AssemblyProduct = "DoubleCache"; - internal const System.String AssemblyVersion = "1.5.0"; - internal const System.String AssemblyInformationalVersion = "1.5.0"; - internal const System.String AssemblyFileVersion = "1.5.0"; - internal const System.String AssemblyMetadata_githash = "54c5137866e8115563eb483c019c1ee0fb4c509e"; + internal const System.String AssemblyVersion = "2.0.0"; + internal const System.String AssemblyInformationalVersion = "2.0.0-beta.1"; + internal const System.String AssemblyFileVersion = "2.0.0"; + internal const System.String AssemblyMetadata_githash = "6dd11eaf4f0204d3834644bf1ec8a0736cf80f3d"; } } From 859b9c2fc37a55e99a1e40abee11d15d1118405c Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Tue, 25 Oct 2016 10:51:00 +0200 Subject: [PATCH 5/8] Updated readme and build --- README.md | 11 ++++++++++- build.fsx | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index baadeee..896b55a 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,19 @@ public interface ICacheAside The Add and remove methods are implemented with fire and forget, hence it does not need to be Async as this is handled by the StackExchange.Redis client. DoubleCache comes with the following implementations of this interface -* LocalCache.MemCache - using System.Runtime.Memory +* *[obsolete]* LocalCache.MemCache - using System.Runtime.Memory, does not support null items. +* LocalCache.WrappingMemoryCache - Allows storage of null items in Memory cache.* +* SystemWebCaching.HttpCache - An in memory cache using HttpContext.Cache * Redis.RedisCache - using StackExchange.Redis client +* Redis.RedisStaleCache - Use redis as a stale cache in order to mitigate cache stampede. * SubscribingCache - a decorator supporting push notifications of cache updates * PublishingCache - a decorator publishing cache changes * DoubleCache - a decorator wrapping a local and a remote cache +\* using a custom proxy object holding the cache items. This is transparent to the client. + Depending on your cache need, you can combine these implementations and decorators as you need. The most complete example would be a DoubleCache which takes a local cache decorated with a SubscribingCache and a RedisCache decorated with a PublishingCache. This will result in a local cache that will be in sync with the other local caches; if a value isn't found the value will be retrieved from Redis before ultimately being resolved using the func provided to the cache. + +###Redis Stale Cache +Extends TTL for all objects stored in Redis. Verifies TTL when reading data from redis. If this is less than the default TTL on the redis cache, the cached item will be returned and a new fetch from the dataretriever will be extecuted on a separate thread. Storing the result in the cache. + diff --git a/build.fsx b/build.fsx index 427483d..0295525 100644 --- a/build.fsx +++ b/build.fsx @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj" ++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj" let testReferences = !! "source/DoubleCacheTests/*.csproj" -let version = "2.0.0-beta.1" +let version = "2.0.0-beta1" let commitHash = Information.getCurrentSHA1(".") let projectName = "DoubleCache" From d628681fbe19e73a0803c65cb2e89f9f110d4887 Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Wed, 26 Oct 2016 14:17:42 +0200 Subject: [PATCH 6/8] Use type to get qualified name --- source/DoubleCache/PublishingCache.cs | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/source/DoubleCache/PublishingCache.cs b/source/DoubleCache/PublishingCache.cs index 5a18699..c9cb616 100644 --- a/source/DoubleCache/PublishingCache.cs +++ b/source/DoubleCache/PublishingCache.cs @@ -17,20 +17,20 @@ public PublishingCache(ICacheAside cache, ICachePublisher cachePublisher) public void Add(string key, T item) { _cache.Add(key, item); - _cachePublisher.NotifyUpdate(key, item.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key,typeof(T).AssemblyQualifiedName); } public void Add(string key, T item, TimeSpan? timeToLive) { _cache.Add(key, item, timeToLive); - _cachePublisher.NotifyUpdate(key, item.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName); } public T Get(string key, Func dataRetriever) where T : class { return _cache.Get(key, () => { var result = dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName); return result; }); } @@ -39,7 +39,8 @@ public T Get(string key, Func dataRetriever, TimeSpan? timeToLive) where T { return _cache.Get(key, () => { var result = dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName, timeToLive); + var name = typeof(T).AssemblyQualifiedName; + _cachePublisher.NotifyUpdate(key, name, timeToLive); return result; }, timeToLive); } @@ -48,7 +49,7 @@ public object Get(string key, Type type, Func dataRetriever) { return _cache.Get(key, type, () => { var result = dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName); + _cachePublisher.NotifyUpdate(key, type.AssemblyQualifiedName); return result; }); } @@ -57,7 +58,7 @@ public object Get(string key, Type type, Func dataRetriever, TimeSpan? t { return _cache.Get(key, type, () => { var result = dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName, timeToLive); + _cachePublisher.NotifyUpdate(key,type.AssemblyQualifiedName, timeToLive); return result; }, timeToLive); } @@ -65,7 +66,7 @@ public object Get(string key, Type type, Func dataRetriever, TimeSpan? t public Task GetAsync(string key, Type type, Func> dataRetriever) { Func> wrappedAction = async () => { - var result = await dataRetriever.Invoke(); + var result = await dataRetriever.Invoke().ConfigureAwait(false); var qualifiedTypeName = result.GetType().AssemblyQualifiedName; _cachePublisher.NotifyUpdate(key, qualifiedTypeName); return result; @@ -77,8 +78,8 @@ public Task GetAsync(string key, Type type, Func> dataRetri public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) { Func> wrappedAction = async () => { - var result = await dataRetriever.Invoke(); - var qualifiedTypeName = result.GetType().AssemblyQualifiedName; + var result = await dataRetriever.Invoke().ConfigureAwait(false); + var qualifiedTypeName = type.AssemblyQualifiedName; _cachePublisher.NotifyUpdate(key, qualifiedTypeName); return result; }; @@ -89,17 +90,18 @@ public Task GetAsync(string key, Type type, Func> dataRetri public Task GetAsync(string key, Func> dataRetriever) where T : class { return _cache.GetAsync(key, async() => { - var result = await dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName); + var result = await dataRetriever.Invoke().ConfigureAwait(false); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName); return result; }); } public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class { - return _cache.GetAsync(key, async () => { - var result = await dataRetriever.Invoke(); - _cachePublisher.NotifyUpdate(key, result.GetType().AssemblyQualifiedName, timeToLive); + return _cache.GetAsync(key, async () => + { + var result = await dataRetriever.Invoke().ConfigureAwait(false); + _cachePublisher.NotifyUpdate(key, typeof(T).AssemblyQualifiedName, timeToLive); return result; }, timeToLive); } From 60de701fc6210103794ea9dc55c8075864812e62 Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Wed, 26 Oct 2016 14:17:57 +0200 Subject: [PATCH 7/8] ConfigureAwaitFalse --- build.fsx | 2 +- source/DoubleCache/LocalCache/MemCache.cs | 4 +- .../LocalCache/WrappingMemoryCache.cs | 6 +- source/DoubleCache/Properties/AssemblyInfo.cs | 8 +- source/DoubleCache/Redis/RedisCache.cs | 10 +- source/DoubleCache/Redis/RedisStaleCache.cs | 16 +- source/DoubleCache/SubscribingCache.cs | 150 +++++++++--------- source/DoubleCacheTests/DoubleCacheTests.cs | 2 +- 8 files changed, 99 insertions(+), 99 deletions(-) diff --git a/build.fsx b/build.fsx index 0295525..b84758c 100644 --- a/build.fsx +++ b/build.fsx @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj" ++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj" let testReferences = !! "source/DoubleCacheTests/*.csproj" -let version = "2.0.0-beta1" +let version = "2.0.0-beta2" let commitHash = Information.getCurrentSHA1(".") let projectName = "DoubleCache" diff --git a/source/DoubleCache/LocalCache/MemCache.cs b/source/DoubleCache/LocalCache/MemCache.cs index 17bf11e..12d7b66 100644 --- a/source/DoubleCache/LocalCache/MemCache.cs +++ b/source/DoubleCache/LocalCache/MemCache.cs @@ -75,7 +75,7 @@ public async Task GetAsync(string key, Type type, Func> dat if (item != null && item.GetType() == type) return item; - item = await dataRetriever.Invoke(); + item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); return item.GetType() == type ? item : null; } @@ -91,7 +91,7 @@ public async Task GetAsync(string key, Func> dataRetriever, TimeSp if (item != null) return item; { - item = await dataRetriever.Invoke(); + item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); } return item; diff --git a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs index 31c5167..448998a 100644 --- a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs +++ b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs @@ -80,7 +80,7 @@ public async Task GetAsync(string key, Type type, Func> dat if (wrapper != null) return wrapper.Item; - var item = await dataRetriever.Invoke(); + var item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); return item.GetType() == type ? item : null; } @@ -96,8 +96,8 @@ public async Task GetAsync(string key, Func> dataRetriever, TimeSp if (wrapper != null) return wrapper.Item as T; - var item = await dataRetriever.Invoke(); - Add(key, item, timeToLive); + var item = await dataRetriever.Invoke().ConfigureAwait(false); + Add(key, item, timeToLive); return item; } diff --git a/source/DoubleCache/Properties/AssemblyInfo.cs b/source/DoubleCache/Properties/AssemblyInfo.cs index 3afe9b8..d544560 100644 --- a/source/DoubleCache/Properties/AssemblyInfo.cs +++ b/source/DoubleCache/Properties/AssemblyInfo.cs @@ -7,9 +7,9 @@ [assembly: GuidAttribute("505f87a8-3062-4070-af1f-cd7358ccd06a")] [assembly: AssemblyProductAttribute("DoubleCache")] [assembly: AssemblyVersionAttribute("2.0.0")] -[assembly: AssemblyInformationalVersionAttribute("2.0.0-beta.1")] +[assembly: AssemblyInformationalVersionAttribute("2.0.0-beta2")] [assembly: AssemblyFileVersionAttribute("2.0.0")] -[assembly: AssemblyMetadataAttribute("githash","6dd11eaf4f0204d3834644bf1ec8a0736cf80f3d")] +[assembly: AssemblyMetadataAttribute("githash","859b9c2fc37a55e99a1e40abee11d15d1118405c")] namespace System { internal static class AssemblyVersionInformation { internal const System.String AssemblyTitle = "DoubleCache"; @@ -17,8 +17,8 @@ internal static class AssemblyVersionInformation { internal const System.String Guid = "505f87a8-3062-4070-af1f-cd7358ccd06a"; internal const System.String AssemblyProduct = "DoubleCache"; internal const System.String AssemblyVersion = "2.0.0"; - internal const System.String AssemblyInformationalVersion = "2.0.0-beta.1"; + internal const System.String AssemblyInformationalVersion = "2.0.0-beta2"; internal const System.String AssemblyFileVersion = "2.0.0"; - internal const System.String AssemblyMetadata_githash = "6dd11eaf4f0204d3834644bf1ec8a0736cf80f3d"; + internal const System.String AssemblyMetadata_githash = "859b9c2fc37a55e99a1e40abee11d15d1118405c"; } } diff --git a/source/DoubleCache/Redis/RedisCache.cs b/source/DoubleCache/Redis/RedisCache.cs index 2064cc9..64459f6 100644 --- a/source/DoubleCache/Redis/RedisCache.cs +++ b/source/DoubleCache/Redis/RedisCache.cs @@ -82,11 +82,11 @@ public Task GetAsync(string key, Type type, Func> dataRetri public async Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) { - var packedBytes = await _database.StringGetAsync(key); + var packedBytes = await _database.StringGetAsync(key).ConfigureAwait(false); if (!packedBytes.IsNull) return _itemSerializer.Deserialize(packedBytes, type); - - var item = await dataRetriever.Invoke(); + + var item = await dataRetriever.Invoke().ConfigureAwait(false); if (item != null && item.GetType() == type) { Add(key, item, timeToLive); @@ -103,11 +103,11 @@ public Task GetAsync(string key, Func> dataRetriever) where T : cl public async Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class { - var packedBytes = await _database.StringGetAsync(key); + var packedBytes = await _database.StringGetAsync(key).ConfigureAwait(false); if (!packedBytes.IsNull) return _itemSerializer.Deserialize(packedBytes); - var item = await dataRetriever.Invoke(); + var item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); return item; } diff --git a/source/DoubleCache/Redis/RedisStaleCache.cs b/source/DoubleCache/Redis/RedisStaleCache.cs index 955fc5e..ce33a2e 100644 --- a/source/DoubleCache/Redis/RedisStaleCache.cs +++ b/source/DoubleCache/Redis/RedisStaleCache.cs @@ -119,21 +119,21 @@ public async Task GetAsync(string key, Func> dataRetriever, TimeSp if (timeToLive.HasValue) staleTtl = timeToLive.Value.Add(_staleDuration); - var item = await _redisCache.GetAsync(key, dataRetriever, staleTtl); - var ttl = await _database.KeyTimeToLiveAsync(key); + var item = await _redisCache.GetAsync(key, dataRetriever, staleTtl).ConfigureAwait(false); + var ttl = await _database.KeyTimeToLiveAsync(key).ConfigureAwait(false); if (!ttl.HasValue || ttl.Value < _staleDuration) { ttl = ttl == null ? _staleDuration.Add(_staleDuration) : ttl.Value.Add(_staleDuration); - await _database.KeyExpireAsync(key, ttl); + await _database.KeyExpireAsync(key, ttl).ConfigureAwait(false); ThreadPool.QueueUserWorkItem(async o => { try { - _redisCache.Add(key, await dataRetriever.Invoke(), staleTtl); + _redisCache.Add(key, await dataRetriever.Invoke().ConfigureAwait(false), staleTtl); } catch { //make sure we do not crash. @@ -154,21 +154,21 @@ public async Task GetAsync(string key, Type type, Func> dat if (timeToLive.HasValue) staleTtl = timeToLive.Value.Add(_staleDuration); - var item = await _redisCache.GetAsync(key, type, dataRetriever, staleTtl); - var ttl = await _database.KeyTimeToLiveAsync(key); + var item = await _redisCache.GetAsync(key, type, dataRetriever, staleTtl).ConfigureAwait(false); + var ttl = await _database.KeyTimeToLiveAsync(key).ConfigureAwait(false); if (!ttl.HasValue || ttl.Value < _staleDuration) { ttl = ttl == null ? _staleDuration.Add(_staleDuration) : ttl.Value.Add(_staleDuration); - await _database.KeyExpireAsync(key, ttl); + await _database.KeyExpireAsync(key, ttl).ConfigureAwait(false); ThreadPool.QueueUserWorkItem(async o => { try { - _redisCache.Add(key, await dataRetriever.Invoke(), timeToLive); + _redisCache.Add(key, await dataRetriever.Invoke().ConfigureAwait(false), timeToLive); } catch { //make sure we do not crash. diff --git a/source/DoubleCache/SubscribingCache.cs b/source/DoubleCache/SubscribingCache.cs index e155559..0427d61 100644 --- a/source/DoubleCache/SubscribingCache.cs +++ b/source/DoubleCache/SubscribingCache.cs @@ -1,33 +1,33 @@ -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace DoubleCache -{ - public class SubscribingCache : ICacheAside - { - private readonly ICacheAside _cache; - private readonly ICacheSubscriber _cacheSubscriber; - private readonly ConcurrentDictionary _knownTypes; - - public SubscribingCache(ICacheAside cache, ICacheSubscriber cacheSubscriber) - { - _knownTypes = new ConcurrentDictionary(); - _cache = cache; - _cacheSubscriber = cacheSubscriber; - - _cacheSubscriber.CacheUpdate += OnCacheUpdate; - _cacheSubscriber.CacheDelete += OnCacheDelete; - } - - public void Add(string key, T item) - { - _cache.Add(key, item); - } - - public void Add(string key, T item, TimeSpan? timeToLive) - { - _cache.Add(key, item, timeToLive); +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace DoubleCache +{ + public class SubscribingCache : ICacheAside + { + private readonly ICacheAside _cache; + private readonly ICacheSubscriber _cacheSubscriber; + private readonly ConcurrentDictionary _knownTypes; + + public SubscribingCache(ICacheAside cache, ICacheSubscriber cacheSubscriber) + { + _knownTypes = new ConcurrentDictionary(); + _cache = cache; + _cacheSubscriber = cacheSubscriber; + + _cacheSubscriber.CacheUpdate += OnCacheUpdate; + _cacheSubscriber.CacheDelete += OnCacheDelete; + } + + public void Add(string key, T item) + { + _cache.Add(key, item); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + _cache.Add(key, item, timeToLive); } public T Get(string key, Func dataRetriever) where T : class @@ -48,43 +48,43 @@ public object Get(string key, Type type, Func dataRetriever) public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { return _cache.Get(key, type, dataRetriever, timeToLive); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever) - { - return _cache.GetAsync(key, type, dataRetriever); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) - { - return _cache.GetAsync(key, type, dataRetriever, timeToLive); - } - - public Task GetAsync(string key, Func> dataRetriever) where T : class - { - return _cache.GetAsync(key, dataRetriever); - } - - public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class - { - return _cache.GetAsync(key, dataRetriever, timeToLive); - } - - private async void OnCacheUpdate(object sender, CacheUpdateNotificationArgs e) - { - await CacheUpdateAction(sender, e); - } - private async Task CacheUpdateAction(object sender, CacheUpdateNotificationArgs e) - { - var remoteItem = await _cacheSubscriber.GetAsync(e.Key, _knownTypes.GetOrAdd(e.Type, Type.GetType(e.Type))); - - if (remoteItem != null) - { - if (e.SpecificTimeToLive != null) - Add(e.Key, remoteItem, e.SpecificTimeToLive._timeToLive); - else - Add(e.Key, remoteItem); - } + } + + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return _cache.GetAsync(key, type, dataRetriever); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + return _cache.GetAsync(key, type, dataRetriever, timeToLive); + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return _cache.GetAsync(key, dataRetriever); + } + + public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + return _cache.GetAsync(key, dataRetriever, timeToLive); + } + + private async void OnCacheUpdate(object sender, CacheUpdateNotificationArgs e) + { + await CacheUpdateAction(sender, e).ConfigureAwait(false); + } + private async Task CacheUpdateAction(object sender, CacheUpdateNotificationArgs e) + { + var remoteItem = await _cacheSubscriber.GetAsync(e.Key, _knownTypes.GetOrAdd(e.Type, Type.GetType(e.Type))).ConfigureAwait(false); ; + + if (remoteItem != null) + { + if (e.SpecificTimeToLive != null) + Add(e.Key, remoteItem, e.SpecificTimeToLive._timeToLive); + else + Add(e.Key, remoteItem); + } } private void OnCacheDelete(object sender, CacheUpdateNotificationArgs e) @@ -92,11 +92,11 @@ private void OnCacheDelete(object sender, CacheUpdateNotificationArgs e) Remove(e.Key); } - public void Remove(string key) - { - _cache.Remove(key); - } - - public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } - } -} + public void Remove(string key) + { + _cache.Remove(key); + } + + public TimeSpan? DefaultTtl { get { return _cache.DefaultTtl; } } + } +} diff --git a/source/DoubleCacheTests/DoubleCacheTests.cs b/source/DoubleCacheTests/DoubleCacheTests.cs index 2740b76..c55d33c 100644 --- a/source/DoubleCacheTests/DoubleCacheTests.cs +++ b/source/DoubleCacheTests/DoubleCacheTests.cs @@ -81,7 +81,7 @@ public void GetGeneric_WithTimeToLive_CalledOnLocal() [Fact] public async Task GetAsync_CalledOnLocal() { - await _doubleCache.GetAsync("A", typeof(string), null); + await _doubleCache.GetAsync("A", typeof(string), null).ConfigureAwait(false); A.CallTo(() => _local.GetAsync("A", A.Ignored, A>>.Ignored)).MustHaveHappened(Repeated.Exactly.Once); A.CallTo(() => _remote.GetAsync("A", A.Ignored, A>>.Ignored)).MustNotHaveHappened(); From 501ce95ad2336ff38ce5ecc39fb6efd7dc9f2486 Mon Sep 17 00:00:00 2001 From: Harald Schult Ulriksen Date: Thu, 27 Oct 2016 10:24:24 +0200 Subject: [PATCH 8/8] Null caching verification --- build.fsx | 2 +- source/DoubleCache/DoubleCache.cs | 122 +++++++++--------- source/DoubleCache/LocalCache/MemCache.cs | 2 +- .../LocalCache/WrappingMemoryCache.cs | 2 +- .../BinaryFormatterItemSerializer.cs | 21 +++ .../DoubleCacheTests/DoubleCacheTests.csproj | 1 + .../CacheImplementationTests.cs | 13 +- .../HttpCacheIntegrationTests.cs | 12 -- .../IntegrationTests/MemoryCacheTests.cs | 7 + .../IntegrationTests/PubSubDoubleCacheTest.cs | 19 +++ .../WrappingMemoryCacheTests.cs | 12 -- .../Serialization/ItemSerializerTests.cs | 69 +++++----- 12 files changed, 161 insertions(+), 121 deletions(-) create mode 100644 source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs diff --git a/build.fsx b/build.fsx index b84758c..1fe0269 100644 --- a/build.fsx +++ b/build.fsx @@ -13,7 +13,7 @@ let references = !! "source/DoubleCache/*.csproj" ++ "source/DoubleCache.SystemWebCaching/DoubleCache.SystemWebCaching.csproj" let testReferences = !! "source/DoubleCacheTests/*.csproj" -let version = "2.0.0-beta2" +let version = "2.0.0-beta4" let commitHash = Information.getCurrentSHA1(".") let projectName = "DoubleCache" diff --git a/source/DoubleCache/DoubleCache.cs b/source/DoubleCache/DoubleCache.cs index 60e85ac..af77f7f 100644 --- a/source/DoubleCache/DoubleCache.cs +++ b/source/DoubleCache/DoubleCache.cs @@ -1,29 +1,29 @@ -using System; -using System.Threading.Tasks; - -namespace DoubleCache -{ - public class DoubleCache : ICacheAside - { - private readonly ICacheAside _localCache; - private readonly ICacheAside _remoteCache; - - public DoubleCache(ICacheAside localCache,ICacheAside remoteCache) - { - _localCache = localCache; - _remoteCache = remoteCache; - } - - public void Add(string key, T item) - { - _localCache.Add(key, item); - _remoteCache.Add(key, item); - } - - public void Add(string key, T item, TimeSpan? timeToLive) - { - _localCache.Add(key, item, timeToLive); - _remoteCache.Add(key, item, timeToLive); +using System; +using System.Threading.Tasks; + +namespace DoubleCache +{ + public class DoubleCache : ICacheAside + { + private readonly ICacheAside _localCache; + private readonly ICacheAside _remoteCache; + + public DoubleCache(ICacheAside localCache,ICacheAside remoteCache) + { + _localCache = localCache; + _remoteCache = remoteCache; + } + + public void Add(string key, T item) + { + _localCache.Add(key, item); + _remoteCache.Add(key, item); + } + + public void Add(string key, T item, TimeSpan? timeToLive) + { + _localCache.Add(key, item, timeToLive); + _remoteCache.Add(key, item, timeToLive); } public T Get(string key, Func dataRetriever) where T : class @@ -45,41 +45,41 @@ public object Get(string key, Type type, Func dataRetriever) public object Get(string key, Type type, Func dataRetriever, TimeSpan? timeToLive) { return _localCache.Get(key, type, () => _remoteCache.Get(key, type, dataRetriever, timeToLive), timeToLive); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever) - { - return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever)); - } - - public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) - { - return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), timeToLive); - } - - public Task GetAsync(string key, Func> dataRetriever) where T : class - { - return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever)); - } - - public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class - { - return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever),timeToLive); } - public void Remove(string key) - { - _localCache.Remove(key); - _remoteCache.Remove(key); - } - - public TimeSpan? DefaultTtl { get - { - return _localCache.DefaultTtl > _remoteCache.DefaultTtl - ? _localCache.DefaultTtl - : _remoteCache.DefaultTtl; - } } - + public Task GetAsync(string key, Type type, Func> dataRetriever) + { + return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever)); + } + + public Task GetAsync(string key, Type type, Func> dataRetriever, TimeSpan? timeToLive) + { + return _localCache.GetAsync(key, type, () => _remoteCache.GetAsync(key, type, dataRetriever), timeToLive); + } + + public Task GetAsync(string key, Func> dataRetriever) where T : class + { + return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever)); + } + + public Task GetAsync(string key, Func> dataRetriever, TimeSpan? timeToLive) where T : class + { + return _localCache.GetAsync(key, () => _remoteCache.GetAsync(key, dataRetriever),timeToLive); + } + + public void Remove(string key) + { + _localCache.Remove(key); + _remoteCache.Remove(key); + } + + public TimeSpan? DefaultTtl { get + { + return _localCache.DefaultTtl > _remoteCache.DefaultTtl + ? _localCache.DefaultTtl + : _remoteCache.DefaultTtl; + } } + - } -} + } +} diff --git a/source/DoubleCache/LocalCache/MemCache.cs b/source/DoubleCache/LocalCache/MemCache.cs index 12d7b66..044b5d7 100644 --- a/source/DoubleCache/LocalCache/MemCache.cs +++ b/source/DoubleCache/LocalCache/MemCache.cs @@ -8,7 +8,7 @@ namespace DoubleCache.LocalCache public class MemCache : ICacheAside { private readonly TimeSpan? _defaultTtl; - + public MemCache(TimeSpan? defaultTtl = null) { _defaultTtl = defaultTtl; diff --git a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs index 448998a..8b0e1d2 100644 --- a/source/DoubleCache/LocalCache/WrappingMemoryCache.cs +++ b/source/DoubleCache/LocalCache/WrappingMemoryCache.cs @@ -82,7 +82,7 @@ public async Task GetAsync(string key, Type type, Func> dat var item = await dataRetriever.Invoke().ConfigureAwait(false); Add(key, item, timeToLive); - return item.GetType() == type ? item : null; + return item == null || item.GetType() == type ? item : null; } public Task GetAsync(string key, Func> dataRetriever) where T : class diff --git a/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs b/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs index 77b54bc..a90a55a 100644 --- a/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs +++ b/source/DoubleCache/Serialization/BinaryFormatterItemSerializer.cs @@ -1,13 +1,21 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; namespace DoubleCache.Serialization { public class BinaryFormatterItemSerializer : IItemSerializer { + private static ConcurrentDictionary _typeCache = new ConcurrentDictionary(); + public byte[] Serialize(T item) { + if (item == null) + return new byte[0]; + var formatter = new BinaryFormatter(); byte[] itemBytes; @@ -23,6 +31,9 @@ public byte[] Serialize(T item) public object Deserialize(byte[] bytes, Type type) { + if (bytes.Length == 0) + return type.IsValueType ? Activator.CreateInstance(type) : null; + var formatter = new BinaryFormatter(); object item; @@ -36,6 +47,9 @@ public object Deserialize(byte[] bytes, Type type) public T Deserialize(Stream stream) { + if (stream.Length == 0) + return default(T); + var formatter = new BinaryFormatter(); return (T)formatter.Deserialize(stream); @@ -44,6 +58,9 @@ public T Deserialize(Stream stream) public T Deserialize(byte[] bytes) { + if (bytes.Length == 0) + return default(T); + var formatter = new BinaryFormatter(); object item; @@ -54,6 +71,10 @@ public T Deserialize(byte[] bytes) return (T)item; } + private static T GetDefault() + { + return default(T); + } } } diff --git a/source/DoubleCacheTests/DoubleCacheTests.csproj b/source/DoubleCacheTests/DoubleCacheTests.csproj index ea92b93..0a84175 100644 --- a/source/DoubleCacheTests/DoubleCacheTests.csproj +++ b/source/DoubleCacheTests/DoubleCacheTests.csproj @@ -78,6 +78,7 @@ + diff --git a/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs b/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs index de14215..67186e7 100644 --- a/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/CacheImplementationTests.cs @@ -12,7 +12,6 @@ public abstract class CacheImplementationTests protected string _key; protected ICacheAside _cacheImplementation; - [Fact] public void Get_ExistingValue_ReturnsValue() { @@ -252,5 +251,17 @@ public async Task Remove_ExistingKey_DeletesValue() A.CallTo(() => func.Invoke()).MustHaveHappened(Repeated.Exactly.Once); result.ShouldBe("B"); } + + [Fact] + public virtual void Cache_Null_Returns_Null() + { + var key = Guid.NewGuid().ToString(); + + _cacheImplementation.Add(key, null); + + var result = _cacheImplementation.Get(key, () => "a"); + + result.ShouldBeNull(); + } } } diff --git a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs index a15e98b..f9b5eeb 100644 --- a/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/HttpCacheIntegrationTests.cs @@ -18,17 +18,5 @@ public HttpCacheIntegrationTests() _cacheImplementation = new HttpCache(context.Cache, TimeSpan.FromMinutes(1)); } - - [Fact] - public void Cache_Null_Returns_Null() - { - var key = Guid.NewGuid().ToString(); - - _cacheImplementation.Add(key, null); - - var result = _cacheImplementation.Get(key, () => "a"); - - result.ShouldBeNull(); - } } } diff --git a/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs b/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs index 2d6bb58..d864f66 100644 --- a/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/MemoryCacheTests.cs @@ -1,5 +1,7 @@ using System; using DoubleCache.LocalCache; +using FakeItEasy; +using Shouldly; using Xunit; namespace DoubleCacheTests.IntegrationTests @@ -12,5 +14,10 @@ public MemoryCacheIntegrationTests() _key = Guid.NewGuid().ToString(); _cacheImplementation = new MemCache(TimeSpan.FromMinutes(1)); } + + public override void Cache_Null_Returns_Null() + { + Should.Throw(() => base.Cache_Null_Returns_Null()); + } } } diff --git a/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs b/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs new file mode 100644 index 0000000..454dffa --- /dev/null +++ b/source/DoubleCacheTests/IntegrationTests/PubSubDoubleCacheTest.cs @@ -0,0 +1,19 @@ +using System; +using DoubleCache; +using DoubleCache.Serialization; +using Xunit; + +namespace DoubleCacheTests.IntegrationTests +{ + + [Trait("Category", "Integration")] + public class PubSubDoubleCacheTest : CacheImplementationTests, IClassFixture + { + public PubSubDoubleCacheTest(RedisFixture fixture) + { + _key = Guid.NewGuid().ToString(); + _cacheImplementation = CacheFactory.CreatePubSubDoubleCache(fixture.ConnectionMultiplexer, + new BinaryFormatterItemSerializer(),TimeSpan.FromMinutes(1)); + } + } +} diff --git a/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs index 1e3430a..8c34626 100644 --- a/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs +++ b/source/DoubleCacheTests/IntegrationTests/WrappingMemoryCacheTests.cs @@ -13,17 +13,5 @@ public WrappingMemoryCacheTests() _key = Guid.NewGuid().ToString(); _cacheImplementation = new WrappingMemoryCache(TimeSpan.FromMinutes(1)); } - - [Fact] - public void Cache_Null_Returns_Null() - { - var key = Guid.NewGuid().ToString(); - - _cacheImplementation.Add(key,null); - - var result = _cacheImplementation.Get(key,() => "a" ); - - result.ShouldBeNull(); - } } } diff --git a/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs b/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs index 35fac8c..c42c5a6 100644 --- a/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs +++ b/source/DoubleCacheTests/Serialization/ItemSerializerTests.cs @@ -7,40 +7,45 @@ namespace DoubleCacheTests.Serialization { - public abstract class ItemSerializerTests - { + public abstract class ItemSerializerTests + { protected IItemSerializer serializer; - [Theory] + [Theory] [InlineData("a")] - public void RoundtripSerializeGeneric(T input) - { - var result = serializer.Deserialize(serializer.Serialize(input)); - - result.ShouldBe(input); - } - [Theory] - [InlineData("a")] - [CacheNotificationData("test","test",1)] - public void RoundtripSerialize(T input) - { - var result = serializer.Deserialize(serializer.Serialize(input), typeof(T)); - - result.ShouldBeOfType(); - - if (result is string) - result.ShouldBe(input); - } - - [Theory] - [InlineData("a")] - public void RoundtripDeserializeStream(T input) - { - using (var ms = new MemoryStream(serializer.Serialize(input))) - { - var result = serializer.Deserialize(ms); - result.ShouldBe(input); - } - } + public void RoundtripSerializeGeneric(T input) + { + var result = serializer.Deserialize(serializer.Serialize(input)); + + result.ShouldBe(input); + } + [Theory] + [InlineData("a")] + [InlineData(null)] + [CacheNotificationData("test","test",1)] + public void RoundtripSerialize(T input) + { + var result = serializer.Deserialize(serializer.Serialize(input), typeof(T)); + + if (input == null) + result.ShouldBeNull(); + else + { + result.ShouldBeOfType(); + if (result is string) + result.ShouldBe(input); + } + } + + [Theory] + [InlineData("a")] + public void RoundtripDeserializeStream(T input) + { + using (var ms = new MemoryStream(serializer.Serialize(input))) + { + var result = serializer.Deserialize(ms); + result.ShouldBe(input); + } + } } }