From 8fa038c68953566a9bc90cd3cc61d0d43d707a9e Mon Sep 17 00:00:00 2001 From: maca88 Date: Fri, 15 Jun 2018 17:42:06 +0200 Subject: [PATCH 01/24] Added Redis cache provider --- AsyncGenerator.yml | 93 +++ .../Async/CacheFixture.cs | 26 + .../CacheFixture.cs | 27 + NHibernate.Caches.Everything.sln | 472 +++++++-------- .../App.config | 44 ++ .../Async/Caches/DistributedRedisCache.cs | 100 ++++ .../Async/RedisCacheDefaultStrategyFixture.cs | 122 ++++ .../Async/RedisCacheFixture.cs | 196 +++++++ .../Caches/DistributedRedisCache.cs | 114 ++++ .../DistributedRedisCacheFixture.cs | 31 + ...Hibernate.Caches.StackExRedis.Tests.csproj | 29 + .../Program.cs | 12 + .../Properties/AssemblyInfo.cs | 3 + .../DistributedRedisCacheProvider.cs | 57 ++ .../RedisCacheDefaultStrategyFixture.cs | 121 ++++ .../RedisCacheFastStrategyFixture.cs | 34 ++ .../RedisCacheFixture.cs | 193 +++++++ .../RedisCacheProviderFixture.cs | 53 ++ .../TestsContext.cs | 42 ++ .../AbstractRegionStrategy.cs | 541 ++++++++++++++++++ .../Async/AbstractRegionStrategy.cs | 426 ++++++++++++++ .../Async/DefaultRegionStrategy.cs | 209 +++++++ .../Async/FastRegionStrategy.cs | 30 + .../Async/NHibernateTextWriter.cs | 59 ++ .../Async/RedisCache.cs | 139 +++++ .../Async/RedisKeyLocker.cs | 260 +++++++++ .../BinaryRedisSerializer.cs | 38 ++ .../CacheConfig.cs | 31 + .../ConfigurationHelper.cs | 129 +++++ .../DefaultCacheLockRetryDelayProvider.cs | 18 + .../DefaultCacheLockValueProvider.cs | 14 + .../DefaultCacheRegionStrategyFactory.cs | 18 + .../DefaultRegionStrategy.cs | 403 +++++++++++++ .../FastRegionStrategy.cs | 129 +++++ .../ICacheLockRetryDelayProvider.cs | 18 + .../ICacheLockValueProvider.cs | 15 + .../ICacheRegionStrategyFactory.cs | 21 + .../IRedisSerializer.cs | 24 + .../NHibernate.Caches.StackExRedis.csproj | 33 ++ .../NHibernateTextWriter.cs | 40 ++ .../RedisCache.cs | 117 ++++ .../RedisCacheConfiguration.cs | 67 +++ .../RedisCacheLockConfiguration.cs | 66 +++ .../RedisCacheProvider.cs | 232 ++++++++ .../RedisCacheRegionConfiguration.cs | 97 ++++ .../RedisEnvironment.cs | 92 +++ .../RedisKeyLocker.cs | 244 ++++++++ .../RedisSectionHandler.cs | 109 ++++ .../RegionConfig.cs | 64 +++ 49 files changed, 5223 insertions(+), 229 deletions(-) create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index c5118e76..43b2d396 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -28,6 +28,57 @@ registerPlugin: - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover assemblyName: AsyncGenerator.Core +- filePath: StackExRedis\NHibernate.Caches.StackExRedis\NHibernate.Caches.StackExRedis.csproj + targetFramework: net461 + concurrentRun: true + applyChanges: true + analyzation: + methodConversion: + - conversion: Ignore + hasAttributeName: ObsoleteAttribute + - conversion: Smart + containingTypeName: AbstractRegionStrategy + callForwarding: true + cancellationTokens: + guards: true + methodParameter: + - parameter: Required + requiresCancellationToken: + - containingType: NHibernate.Caches.StackExRedis.RedisCache + name: GetMany + - containingType: NHibernate.Caches.StackExRedis.RedisCache + name: PutMany + - containingType: NHibernate.Caches.StackExRedis.RedisCache + name: RemoveMany + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: Get + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: GetMany + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: Put + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: PutMany + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: Remove + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: RemoveMany + - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker + name: Unlock + - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker + name: UnlockMany + scanMethodBody: true + searchAsyncCounterpartsInInheritedTypes: true + scanForMissingAsyncMembers: + - all: true + transformation: + configureAwaitArgument: false + localFunctions: true + asyncLock: + type: NHibernate.Util.AsyncLock + methodName: LockAsync + registerPlugin: + - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover + assemblyName: AsyncGenerator.Core - filePath: NHibernate.Caches.Common.Tests\NHibernate.Caches.Common.Tests.csproj targetFramework: net461 concurrentRun: true @@ -356,6 +407,48 @@ registerPlugin: - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder assemblyName: AsyncGenerator.Core +- filePath: StackExRedis\NHibernate.Caches.StackExRedis.Tests\NHibernate.Caches.StackExRedis.Tests.csproj + targetFramework: net461 + concurrentRun: true + applyChanges: true + analyzation: + methodConversion: + - conversion: Ignore + hasAttributeName: OneTimeSetUpAttribute + - conversion: Ignore + hasAttributeName: OneTimeTearDownAttribute + - conversion: Ignore + hasAttributeName: SetUpAttribute + - conversion: Ignore + hasAttributeName: TearDownAttribute + - conversion: Smart + hasAttributeName: TestAttribute + - conversion: Smart + hasAttributeName: TheoryAttribute + preserveReturnType: + - hasAttributeName: TestAttribute + - hasAttributeName: TheoryAttribute + typeConversion: + - conversion: Ignore + hasAttributeName: IgnoreAttribute + - conversion: Partial + hasAttributeName: TestFixtureAttribute + - conversion: Partial + anyBaseTypeRule: HasTestFixtureAttribute + ignoreSearchForMethodReferences: + - hasAttributeName: TheoryAttribute + - hasAttributeName: TestAttribute + cancellationTokens: + withoutCancellationToken: + - hasAttributeName: TestAttribute + - hasAttributeName: TheoryAttribute + scanMethodBody: true + searchAsyncCounterpartsInInheritedTypes: true + scanForMissingAsyncMembers: + - all: true + registerPlugin: + - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder + assemblyName: AsyncGenerator.Core - filePath: SysCache\NHibernate.Caches.SysCache.Tests\NHibernate.Caches.SysCache.Tests.csproj targetFramework: net461 concurrentRun: true diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index 2d46633d..b6987ad2 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -61,6 +61,32 @@ public async Task TestRemoveAsync() Assert.That(item, Is.Null, "item still exists in cache after remove"); } + [Test] + public async Task TestLockUnlockAsync() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + const string key = "keyTestLock"; + const string value = "valueLock"; + + var cache = GetDefaultCache(); + + // add the item + await (cache.PutAsync(key, value, CancellationToken.None)); + + await (cache.LockAsync(key, CancellationToken.None)); + Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None)); + + await (Task.Delay(cache.Timeout / Timestamper.OneMs)); + + for (var i = 0; i < 2; i++) + { + await (cache.LockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, CancellationToken.None)); + } + } + [Test] public async Task TestClearAsync() { diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 577cedd4..04af24b3 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -10,6 +10,7 @@ namespace NHibernate.Caches.Common.Tests public abstract partial class CacheFixture : Fixture { protected virtual bool SupportsSlidingExpiration => false; + protected virtual bool SupportsLocking => false; protected virtual bool SupportsDistinguishingKeysWithSameStringRepresentationAndHashcode => true; protected virtual bool SupportsClear => true; @@ -54,6 +55,32 @@ public void TestRemove() Assert.That(item, Is.Null, "item still exists in cache after remove"); } + [Test] + public void TestLockUnlock() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + const string key = "keyTestLock"; + const string value = "valueLock"; + + var cache = GetDefaultCache(); + + // add the item + cache.Put(key, value); + + cache.Lock(key); + Assert.Throws(() => cache.Lock(key)); + + Thread.Sleep(cache.Timeout / Timestamper.OneMs); + + for (var i = 0; i < 2; i++) + { + cache.Lock(key); + cache.Unlock(key); + } + } + [Test] public void TestClear() { diff --git a/NHibernate.Caches.Everything.sln b/NHibernate.Caches.Everything.sln index 7448ffcb..2acec460 100644 --- a/NHibernate.Caches.Everything.sln +++ b/NHibernate.Caches.Everything.sln @@ -1,229 +1,243 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2002 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{9BC335BB-4F31-44B4-9C2D-5A97B4742675}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{55271617-8CB8-4225-B338-069033160497}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{132CB859-841B-48C6-AA58-41BE77727E0D}" - ProjectSection(SolutionItems) = preProject - buildcommon.xml = buildcommon.xml - NHibernate.Caches.snk = NHibernate.Caches.snk - readme.md = readme.md - NHibernate.Caches.props = NHibernate.Caches.props - default.build = default.build - AsyncGenerator.yml = AsyncGenerator.yml - appveyor.yml = appveyor.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache", "SysCache\NHibernate.Caches.SysCache\NHibernate.Caches.SysCache.csproj", "{2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache2", "SysCache2\NHibernate.Caches.SysCache2\NHibernate.Caches.SysCache2.csproj", "{F5852ECD-C05B-4C98-B300-702BA8D8244C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Prevalence", "Prevalence\NHibernate.Caches.Prevalence\NHibernate.Caches.Prevalence.csproj", "{6008B514-E6E4-4485-A188-2D3FE802EC95}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.MemCache", "MemCache\NHibernate.Caches.MemCache\NHibernate.Caches.MemCache.csproj", "{A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Velocity", "Velocity\NHibernate.Caches.Velocity\NHibernate.Caches.Velocity.csproj", "{09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache.Tests", "SysCache\NHibernate.Caches.SysCache.Tests\NHibernate.Caches.SysCache.Tests.csproj", "{1EA6533B-EDC0-4585-927B-65A8A607826A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.MemCache.Tests", "MemCache\NHibernate.Caches.MemCache.Tests\NHibernate.Caches.MemCache.Tests.csproj", "{650E352A-EAE4-4D63-9A89-D7DD7FA4630F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SharedCache", "SharedCache\NHibernate.Caches.SharedCache\NHibernate.Caches.SharedCache.csproj", "{F67ECF65-16D8-400D-A3D7-93A359C26A76}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SharedCache.Tests", "SharedCache\NHibernate.Caches.SharedCache.Tests\NHibernate.Caches.SharedCache.Tests.csproj", "{D54BB1AC-274D-4BD7-B89D-05A61078C83F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Prevalence.Tests", "Prevalence\NHibernate.Caches.Prevalence.Tests\NHibernate.Caches.Prevalence.Tests.csproj", "{07372137-0714-485A-AB07-0A0A872D3444}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Velocity.Tests", "Velocity\NHibernate.Caches.Velocity.Tests\NHibernate.Caches.Velocity.Tests.csproj", "{CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.EnyimMemcached", "EnyimMemcached\NHibernate.Caches.EnyimMemcached\NHibernate.Caches.EnyimMemcached.csproj", "{8B477613-1694-4467-A351-E43349D14527}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.EnyimMemcached.Tests", "EnyimMemcached\NHibernate.Caches.EnyimMemcached.Tests\NHibernate.Caches.EnyimMemcached.Tests.csproj", "{49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache2.Tests", "SysCache2\NHibernate.Caches.SysCache2.Tests\NHibernate.Caches.SysCache2.Tests.csproj", "{F56E6488-DE96-4D1A-9913-D36CBC16E945}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.RtMemoryCache", "RtMemoryCache\NHibernate.Caches.RtMemoryCache\NHibernate.Caches.RtMemoryCache.csproj", "{C0295AB5-00FB-4B45-82F6-2060080D01F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.RtMemoryCache.Tests", "RtMemoryCache\NHibernate.Caches.RtMemoryCache.Tests\NHibernate.Caches.RtMemoryCache.Tests.csproj", "{0B67ADE8-929E-46D2-A4E6-C0D621378EAE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Common.Tests", "NHibernate.Caches.Common.Tests\NHibernate.Caches.Common.Tests.csproj", "{D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreMemoryCache", "CoreMemoryCache\NHibernate.Caches.CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.csproj", "{FD759770-E662-4822-A627-85FAA2B3177C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreMemoryCache.Tests", "CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.Tests\NHibernate.Caches.CoreMemoryCache.Tests.csproj", "{4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj", "{EFB25D54-38E6-441C-9287-4D69E40B1595}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Tests", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Tests\NHibernate.Caches.CoreDistributedCache.Tests.csproj", "{AD359D7F-6E65-48CB-A59A-B78ED58C1309}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Redis", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Redis\NHibernate.Caches.CoreDistributedCache.Redis.csproj", "{0A6D9315-094E-4C6D-8A87-8C642A2D25D7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Memory", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Memory\NHibernate.Caches.CoreDistributedCache.Memory.csproj", "{63AD02AD-1F35-4341-A4C7-7EAEA238F08C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.SqlServer", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.SqlServer\NHibernate.Caches.CoreDistributedCache.SqlServer.csproj", "{03A80D4B-6C72-4F31-8680-DD6119E34CDF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Memcached", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Memcached\NHibernate.Caches.CoreDistributedCache.Memcached.csproj", "{CB7BEB99-1964-4D83-86AD-100439E04186}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Common", "NHibernate.Caches.Common\NHibernate.Caches.Common.csproj", "{15C72C05-4976-4401-91CB-31EF769AADD7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.JsonSerializer", "Util\NHibernate.Caches.Util.JsonSerializer\NHibernate.Caches.Util.JsonSerializer.csproj", "{2327C330-0CE6-4F58-96A3-013BEFDFCCEB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.JsonSerializer.Tests", "Util\NHibernate.Caches.Util.JsonSerializer.Tests\NHibernate.Caches.Util.JsonSerializer.Tests.csproj", "{26EAA903-63F7-41B1-9197-5944CEBF00C2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Release|Any CPU.Build.0 = Release|Any CPU - {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Release|Any CPU.Build.0 = Release|Any CPU - {6008B514-E6E4-4485-A188-2D3FE802EC95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6008B514-E6E4-4485-A188-2D3FE802EC95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6008B514-E6E4-4485-A188-2D3FE802EC95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6008B514-E6E4-4485-A188-2D3FE802EC95}.Release|Any CPU.Build.0 = Release|Any CPU - {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Release|Any CPU.Build.0 = Release|Any CPU - {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Release|Any CPU.Build.0 = Release|Any CPU - {1EA6533B-EDC0-4585-927B-65A8A607826A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1EA6533B-EDC0-4585-927B-65A8A607826A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1EA6533B-EDC0-4585-927B-65A8A607826A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1EA6533B-EDC0-4585-927B-65A8A607826A}.Release|Any CPU.Build.0 = Release|Any CPU - {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Release|Any CPU.Build.0 = Release|Any CPU - {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Release|Any CPU.Build.0 = Release|Any CPU - {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Release|Any CPU.Build.0 = Release|Any CPU - {07372137-0714-485A-AB07-0A0A872D3444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07372137-0714-485A-AB07-0A0A872D3444}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07372137-0714-485A-AB07-0A0A872D3444}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07372137-0714-485A-AB07-0A0A872D3444}.Release|Any CPU.Build.0 = Release|Any CPU - {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Release|Any CPU.Build.0 = Release|Any CPU - {8B477613-1694-4467-A351-E43349D14527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B477613-1694-4467-A351-E43349D14527}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B477613-1694-4467-A351-E43349D14527}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B477613-1694-4467-A351-E43349D14527}.Release|Any CPU.Build.0 = Release|Any CPU - {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Release|Any CPU.ActiveCfg = Release|Any CPU - {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Release|Any CPU.Build.0 = Release|Any CPU - {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Release|Any CPU.Build.0 = Release|Any CPU - {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Release|Any CPU.Build.0 = Release|Any CPU - {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Release|Any CPU.Build.0 = Release|Any CPU - {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Release|Any CPU.Build.0 = Release|Any CPU - {FD759770-E662-4822-A627-85FAA2B3177C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD759770-E662-4822-A627-85FAA2B3177C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD759770-E662-4822-A627-85FAA2B3177C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD759770-E662-4822-A627-85FAA2B3177C}.Release|Any CPU.Build.0 = Release|Any CPU - {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Release|Any CPU.Build.0 = Release|Any CPU - {EFB25D54-38E6-441C-9287-4D69E40B1595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFB25D54-38E6-441C-9287-4D69E40B1595}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFB25D54-38E6-441C-9287-4D69E40B1595}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFB25D54-38E6-441C-9287-4D69E40B1595}.Release|Any CPU.Build.0 = Release|Any CPU - {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Release|Any CPU.Build.0 = Release|Any CPU - {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Release|Any CPU.Build.0 = Release|Any CPU - {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Release|Any CPU.Build.0 = Release|Any CPU - {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7BEB99-1964-4D83-86AD-100439E04186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7BEB99-1964-4D83-86AD-100439E04186}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7BEB99-1964-4D83-86AD-100439E04186}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7BEB99-1964-4D83-86AD-100439E04186}.Release|Any CPU.Build.0 = Release|Any CPU - {15C72C05-4976-4401-91CB-31EF769AADD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15C72C05-4976-4401-91CB-31EF769AADD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15C72C05-4976-4401-91CB-31EF769AADD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15C72C05-4976-4401-91CB-31EF769AADD7}.Release|Any CPU.Build.0 = Release|Any CPU - {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Release|Any CPU.Build.0 = Release|Any CPU - {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {F5852ECD-C05B-4C98-B300-702BA8D8244C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {6008B514-E6E4-4485-A188-2D3FE802EC95} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {1EA6533B-EDC0-4585-927B-65A8A607826A} = {55271617-8CB8-4225-B338-069033160497} - {650E352A-EAE4-4D63-9A89-D7DD7FA4630F} = {55271617-8CB8-4225-B338-069033160497} - {F67ECF65-16D8-400D-A3D7-93A359C26A76} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {D54BB1AC-274D-4BD7-B89D-05A61078C83F} = {55271617-8CB8-4225-B338-069033160497} - {07372137-0714-485A-AB07-0A0A872D3444} = {55271617-8CB8-4225-B338-069033160497} - {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E} = {55271617-8CB8-4225-B338-069033160497} - {8B477613-1694-4467-A351-E43349D14527} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319} = {55271617-8CB8-4225-B338-069033160497} - {F56E6488-DE96-4D1A-9913-D36CBC16E945} = {55271617-8CB8-4225-B338-069033160497} - {C0295AB5-00FB-4B45-82F6-2060080D01F9} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {0B67ADE8-929E-46D2-A4E6-C0D621378EAE} = {55271617-8CB8-4225-B338-069033160497} - {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC} = {55271617-8CB8-4225-B338-069033160497} - {FD759770-E662-4822-A627-85FAA2B3177C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119} = {55271617-8CB8-4225-B338-069033160497} - {EFB25D54-38E6-441C-9287-4D69E40B1595} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {AD359D7F-6E65-48CB-A59A-B78ED58C1309} = {55271617-8CB8-4225-B338-069033160497} - {0A6D9315-094E-4C6D-8A87-8C642A2D25D7} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {63AD02AD-1F35-4341-A4C7-7EAEA238F08C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {03A80D4B-6C72-4F31-8680-DD6119E34CDF} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {CB7BEB99-1964-4D83-86AD-100439E04186} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {15C72C05-4976-4401-91CB-31EF769AADD7} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {2327C330-0CE6-4F58-96A3-013BEFDFCCEB} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} - {26EAA903-63F7-41B1-9197-5944CEBF00C2} = {55271617-8CB8-4225-B338-069033160497} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2002 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{9BC335BB-4F31-44B4-9C2D-5A97B4742675}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{55271617-8CB8-4225-B338-069033160497}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{132CB859-841B-48C6-AA58-41BE77727E0D}" + ProjectSection(SolutionItems) = preProject + buildcommon.xml = buildcommon.xml + NHibernate.Caches.snk = NHibernate.Caches.snk + readme.md = readme.md + NHibernate.Caches.props = NHibernate.Caches.props + default.build = default.build + AsyncGenerator.yml = AsyncGenerator.yml + appveyor.yml = appveyor.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache", "SysCache\NHibernate.Caches.SysCache\NHibernate.Caches.SysCache.csproj", "{2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache2", "SysCache2\NHibernate.Caches.SysCache2\NHibernate.Caches.SysCache2.csproj", "{F5852ECD-C05B-4C98-B300-702BA8D8244C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Prevalence", "Prevalence\NHibernate.Caches.Prevalence\NHibernate.Caches.Prevalence.csproj", "{6008B514-E6E4-4485-A188-2D3FE802EC95}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.MemCache", "MemCache\NHibernate.Caches.MemCache\NHibernate.Caches.MemCache.csproj", "{A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Velocity", "Velocity\NHibernate.Caches.Velocity\NHibernate.Caches.Velocity.csproj", "{09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache.Tests", "SysCache\NHibernate.Caches.SysCache.Tests\NHibernate.Caches.SysCache.Tests.csproj", "{1EA6533B-EDC0-4585-927B-65A8A607826A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.MemCache.Tests", "MemCache\NHibernate.Caches.MemCache.Tests\NHibernate.Caches.MemCache.Tests.csproj", "{650E352A-EAE4-4D63-9A89-D7DD7FA4630F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SharedCache", "SharedCache\NHibernate.Caches.SharedCache\NHibernate.Caches.SharedCache.csproj", "{F67ECF65-16D8-400D-A3D7-93A359C26A76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SharedCache.Tests", "SharedCache\NHibernate.Caches.SharedCache.Tests\NHibernate.Caches.SharedCache.Tests.csproj", "{D54BB1AC-274D-4BD7-B89D-05A61078C83F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Prevalence.Tests", "Prevalence\NHibernate.Caches.Prevalence.Tests\NHibernate.Caches.Prevalence.Tests.csproj", "{07372137-0714-485A-AB07-0A0A872D3444}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Velocity.Tests", "Velocity\NHibernate.Caches.Velocity.Tests\NHibernate.Caches.Velocity.Tests.csproj", "{CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.EnyimMemcached", "EnyimMemcached\NHibernate.Caches.EnyimMemcached\NHibernate.Caches.EnyimMemcached.csproj", "{8B477613-1694-4467-A351-E43349D14527}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.EnyimMemcached.Tests", "EnyimMemcached\NHibernate.Caches.EnyimMemcached.Tests\NHibernate.Caches.EnyimMemcached.Tests.csproj", "{49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.SysCache2.Tests", "SysCache2\NHibernate.Caches.SysCache2.Tests\NHibernate.Caches.SysCache2.Tests.csproj", "{F56E6488-DE96-4D1A-9913-D36CBC16E945}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.RtMemoryCache", "RtMemoryCache\NHibernate.Caches.RtMemoryCache\NHibernate.Caches.RtMemoryCache.csproj", "{C0295AB5-00FB-4B45-82F6-2060080D01F9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.RtMemoryCache.Tests", "RtMemoryCache\NHibernate.Caches.RtMemoryCache.Tests\NHibernate.Caches.RtMemoryCache.Tests.csproj", "{0B67ADE8-929E-46D2-A4E6-C0D621378EAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Common.Tests", "NHibernate.Caches.Common.Tests\NHibernate.Caches.Common.Tests.csproj", "{D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreMemoryCache", "CoreMemoryCache\NHibernate.Caches.CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.csproj", "{FD759770-E662-4822-A627-85FAA2B3177C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreMemoryCache.Tests", "CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.Tests\NHibernate.Caches.CoreMemoryCache.Tests.csproj", "{4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj", "{EFB25D54-38E6-441C-9287-4D69E40B1595}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Tests", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Tests\NHibernate.Caches.CoreDistributedCache.Tests.csproj", "{AD359D7F-6E65-48CB-A59A-B78ED58C1309}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Redis", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Redis\NHibernate.Caches.CoreDistributedCache.Redis.csproj", "{0A6D9315-094E-4C6D-8A87-8C642A2D25D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Memory", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Memory\NHibernate.Caches.CoreDistributedCache.Memory.csproj", "{63AD02AD-1F35-4341-A4C7-7EAEA238F08C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.SqlServer", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.SqlServer\NHibernate.Caches.CoreDistributedCache.SqlServer.csproj", "{03A80D4B-6C72-4F31-8680-DD6119E34CDF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistributedCache.Memcached", "CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Memcached\NHibernate.Caches.CoreDistributedCache.Memcached.csproj", "{CB7BEB99-1964-4D83-86AD-100439E04186}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Common", "NHibernate.Caches.Common\NHibernate.Caches.Common.csproj", "{15C72C05-4976-4401-91CB-31EF769AADD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.JsonSerializer", "Util\NHibernate.Caches.Util.JsonSerializer\NHibernate.Caches.Util.JsonSerializer.csproj", "{2327C330-0CE6-4F58-96A3-013BEFDFCCEB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.JsonSerializer.Tests", "Util\NHibernate.Caches.Util.JsonSerializer.Tests\NHibernate.Caches.Util.JsonSerializer.Tests.csproj", "{26EAA903-63F7-41B1-9197-5944CEBF00C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExRedis", "StackExRedis\NHibernate.Caches.StackExRedis\NHibernate.Caches.StackExRedis.csproj", "{726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExRedis.Tests", "StackExRedis\NHibernate.Caches.StackExRedis.Tests\NHibernate.Caches.StackExRedis.Tests.csproj", "{CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F}.Release|Any CPU.Build.0 = Release|Any CPU + {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5852ECD-C05B-4C98-B300-702BA8D8244C}.Release|Any CPU.Build.0 = Release|Any CPU + {6008B514-E6E4-4485-A188-2D3FE802EC95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6008B514-E6E4-4485-A188-2D3FE802EC95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6008B514-E6E4-4485-A188-2D3FE802EC95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6008B514-E6E4-4485-A188-2D3FE802EC95}.Release|Any CPU.Build.0 = Release|Any CPU + {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1}.Release|Any CPU.Build.0 = Release|Any CPU + {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8}.Release|Any CPU.Build.0 = Release|Any CPU + {1EA6533B-EDC0-4585-927B-65A8A607826A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EA6533B-EDC0-4585-927B-65A8A607826A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EA6533B-EDC0-4585-927B-65A8A607826A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EA6533B-EDC0-4585-927B-65A8A607826A}.Release|Any CPU.Build.0 = Release|Any CPU + {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {650E352A-EAE4-4D63-9A89-D7DD7FA4630F}.Release|Any CPU.Build.0 = Release|Any CPU + {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F67ECF65-16D8-400D-A3D7-93A359C26A76}.Release|Any CPU.Build.0 = Release|Any CPU + {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D54BB1AC-274D-4BD7-B89D-05A61078C83F}.Release|Any CPU.Build.0 = Release|Any CPU + {07372137-0714-485A-AB07-0A0A872D3444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07372137-0714-485A-AB07-0A0A872D3444}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07372137-0714-485A-AB07-0A0A872D3444}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07372137-0714-485A-AB07-0A0A872D3444}.Release|Any CPU.Build.0 = Release|Any CPU + {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E}.Release|Any CPU.Build.0 = Release|Any CPU + {8B477613-1694-4467-A351-E43349D14527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B477613-1694-4467-A351-E43349D14527}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B477613-1694-4467-A351-E43349D14527}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B477613-1694-4467-A351-E43349D14527}.Release|Any CPU.Build.0 = Release|Any CPU + {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319}.Release|Any CPU.Build.0 = Release|Any CPU + {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F56E6488-DE96-4D1A-9913-D36CBC16E945}.Release|Any CPU.Build.0 = Release|Any CPU + {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0295AB5-00FB-4B45-82F6-2060080D01F9}.Release|Any CPU.Build.0 = Release|Any CPU + {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B67ADE8-929E-46D2-A4E6-C0D621378EAE}.Release|Any CPU.Build.0 = Release|Any CPU + {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC}.Release|Any CPU.Build.0 = Release|Any CPU + {FD759770-E662-4822-A627-85FAA2B3177C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD759770-E662-4822-A627-85FAA2B3177C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD759770-E662-4822-A627-85FAA2B3177C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD759770-E662-4822-A627-85FAA2B3177C}.Release|Any CPU.Build.0 = Release|Any CPU + {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119}.Release|Any CPU.Build.0 = Release|Any CPU + {EFB25D54-38E6-441C-9287-4D69E40B1595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFB25D54-38E6-441C-9287-4D69E40B1595}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFB25D54-38E6-441C-9287-4D69E40B1595}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFB25D54-38E6-441C-9287-4D69E40B1595}.Release|Any CPU.Build.0 = Release|Any CPU + {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD359D7F-6E65-48CB-A59A-B78ED58C1309}.Release|Any CPU.Build.0 = Release|Any CPU + {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A6D9315-094E-4C6D-8A87-8C642A2D25D7}.Release|Any CPU.Build.0 = Release|Any CPU + {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63AD02AD-1F35-4341-A4C7-7EAEA238F08C}.Release|Any CPU.Build.0 = Release|Any CPU + {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03A80D4B-6C72-4F31-8680-DD6119E34CDF}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7BEB99-1964-4D83-86AD-100439E04186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7BEB99-1964-4D83-86AD-100439E04186}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7BEB99-1964-4D83-86AD-100439E04186}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7BEB99-1964-4D83-86AD-100439E04186}.Release|Any CPU.Build.0 = Release|Any CPU + {15C72C05-4976-4401-91CB-31EF769AADD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15C72C05-4976-4401-91CB-31EF769AADD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15C72C05-4976-4401-91CB-31EF769AADD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15C72C05-4976-4401-91CB-31EF769AADD7}.Release|Any CPU.Build.0 = Release|Any CPU + {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2327C330-0CE6-4F58-96A3-013BEFDFCCEB}.Release|Any CPU.Build.0 = Release|Any CPU + {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26EAA903-63F7-41B1-9197-5944CEBF00C2}.Release|Any CPU.Build.0 = Release|Any CPU + {726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}.Release|Any CPU.Build.0 = Release|Any CPU + {CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2D48853D-7F90-4E1F-BE98-035CAB2D4F1F} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {F5852ECD-C05B-4C98-B300-702BA8D8244C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {6008B514-E6E4-4485-A188-2D3FE802EC95} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {A824DAC6-D317-4DBD-B08A-1CA8C3CFB7C1} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {09EA5F04-1F0C-4D5E-A393-2B7B2FD279E8} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {1EA6533B-EDC0-4585-927B-65A8A607826A} = {55271617-8CB8-4225-B338-069033160497} + {650E352A-EAE4-4D63-9A89-D7DD7FA4630F} = {55271617-8CB8-4225-B338-069033160497} + {F67ECF65-16D8-400D-A3D7-93A359C26A76} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {D54BB1AC-274D-4BD7-B89D-05A61078C83F} = {55271617-8CB8-4225-B338-069033160497} + {07372137-0714-485A-AB07-0A0A872D3444} = {55271617-8CB8-4225-B338-069033160497} + {CA4F0A59-844B-4717-8F3B-6F53DC0F0B2E} = {55271617-8CB8-4225-B338-069033160497} + {8B477613-1694-4467-A351-E43349D14527} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {49F3BCBF-924F-4CFD-A5F7-4C8C4F8FC319} = {55271617-8CB8-4225-B338-069033160497} + {F56E6488-DE96-4D1A-9913-D36CBC16E945} = {55271617-8CB8-4225-B338-069033160497} + {C0295AB5-00FB-4B45-82F6-2060080D01F9} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {0B67ADE8-929E-46D2-A4E6-C0D621378EAE} = {55271617-8CB8-4225-B338-069033160497} + {D3B6FF9C-4254-48F3-A6A6-64DB03C8A2AC} = {55271617-8CB8-4225-B338-069033160497} + {FD759770-E662-4822-A627-85FAA2B3177C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {4EE5D89B-A1B5-4CF8-95B8-44C2FAFD0119} = {55271617-8CB8-4225-B338-069033160497} + {EFB25D54-38E6-441C-9287-4D69E40B1595} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {AD359D7F-6E65-48CB-A59A-B78ED58C1309} = {55271617-8CB8-4225-B338-069033160497} + {0A6D9315-094E-4C6D-8A87-8C642A2D25D7} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {63AD02AD-1F35-4341-A4C7-7EAEA238F08C} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {03A80D4B-6C72-4F31-8680-DD6119E34CDF} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {CB7BEB99-1964-4D83-86AD-100439E04186} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {15C72C05-4976-4401-91CB-31EF769AADD7} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {2327C330-0CE6-4F58-96A3-013BEFDFCCEB} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {26EAA903-63F7-41B1-9197-5944CEBF00C2} = {55271617-8CB8-4225-B338-069033160497} + {726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82} = {9BC335BB-4F31-44B4-9C2D-5A97B4742675} + {CC2A7851-B1CD-49F7-ACD4-08604C2ACC63} = {55271617-8CB8-4225-B338-069033160497} + EndGlobalSection +EndGlobal diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config new file mode 100644 index 00000000..db99c96a --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config @@ -0,0 +1,44 @@ + + + +
+
+
+ + + + + + + + + + NHibernate.Connection.DriverConnectionProvider, NHibernate + NHibernate.Dialect.MsSql2000Dialect + NHibernate.Driver.SqlClientDriver + + Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI + + ReadCommitted + NHibernate.Caches.StackExRedis.RedisCacheProvider, NHibernate.Caches.StackExRedis + + + + + + + + + + + + + + + + + + + + + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs new file mode 100644 index 00000000..343a5d87 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -0,0 +1,100 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; + +namespace NHibernate.Caches.StackExRedis.Tests.Caches +{ + public partial class DistributedRedisCache : ICache + { + + /// + public Task GetAsync(object key, CancellationToken cancellationToken) + { + try + { + // Use a random strategy to get the value. + // A real distributed cache should use a proper load balancing. + var strategy = _regionStrategies[_random.Next(0, _regionStrategies.Length - 1)]; + return strategy.GetAsync(key, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + public async Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.PutAsync(key, value, cancellationToken)); + } + } + + /// + public async Task RemoveAsync(object key, CancellationToken cancellationToken) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.RemoveAsync(key, cancellationToken)); + } + } + + /// + public async Task ClearAsync(CancellationToken cancellationToken) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.ClearAsync()); + } + } + + /// + public async Task LockAsync(object key, CancellationToken cancellationToken) + { + // A simple locking that requires all instances to obtain the lock + // A real distributed cache should use something like the Redlock algorithm. + try + { + foreach (var strategy in _regionStrategies) + { + await (strategy.LockAsync(key, cancellationToken)); + } + + } + catch (CacheException) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.UnlockAsync(key, cancellationToken)); + } + throw; + } + } + + /// + public async Task UnlockAsync(object key, CancellationToken cancellationToken) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.UnlockAsync(key, cancellationToken)); + } + } + + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs new file mode 100644 index 00000000..de87c01c --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs @@ -0,0 +1,122 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + public partial class RedisCacheDefaultStrategyFixture : RedisCacheFixture + { + + [Test] + public async Task TestMaxAllowedVersionAsync() + { + var cache = (RedisCache) GetDefaultCache(); + var strategy = (DefaultRegionStrategy)cache.RegionStrategy; + var version = strategy.CurrentVersion; + + var props = GetDefaultProperties(); + props.Add("cache.region_strategy.default.max_allowed_version", version.ToString()); + cache = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + strategy = (DefaultRegionStrategy) cache.RegionStrategy; + + await (cache.ClearAsync(CancellationToken.None)); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "The version was not reset to 1"); + } + + [Test] + public async Task TestClearWithMultipleClientsAndPubSubAsync() + { + const string key = "keyTestClear"; + const string value = "valueClear"; + + var cache = (RedisCache)GetDefaultCache(); + var strategy = (DefaultRegionStrategy)cache.RegionStrategy; + var cache2 = (RedisCache) GetDefaultCache(); + var strategy2 = (DefaultRegionStrategy) cache2.RegionStrategy; + + // add the item + await (cache.PutAsync(key, value, CancellationToken.None)); + + // make sure it's there + var item = await (cache.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Not.Null, "couldn't find item in cache"); + + item = await (cache2.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Not.Null, "couldn't find item in second cache"); + + var version = strategy.CurrentVersion; + + // clear the cache + await (cache.ClearAsync(CancellationToken.None)); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(version + 1), "the version has not been updated"); + await (Task.Delay(TimeSpan.FromSeconds(2))); + Assert.That(strategy2.CurrentVersion, Is.EqualTo(version + 1), "the version should be updated with the pub/sub api"); + + // make sure we don't get an item + item = await (cache.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Null, "item still exists in cache after clear"); + + item = await (cache2.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Null, "item still exists in the second cache after clear"); + } + + [Test] + public async Task TestClearWithMultipleClientsAndNoPubSubAsync() + { + const string key = "keyTestClear"; + const string value = "valueClear"; + + var props = GetDefaultProperties(); + props.Add("cache.region_strategy.default.use_pubsub", "false"); + + var cache = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + var strategy = (DefaultRegionStrategy) cache.RegionStrategy; + var cache2 = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + var strategy2 = (DefaultRegionStrategy) cache2.RegionStrategy; + + // add the item + await (cache.PutAsync(key, value, CancellationToken.None)); + + // make sure it's there + var item = await (cache.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Not.Null, "couldn't find item in cache"); + + item = await (cache2.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Not.Null, "couldn't find item in second cache"); + + var version = strategy.CurrentVersion; + + // clear the cache + await (cache.ClearAsync(CancellationToken.None)); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(version + 1), "the version has not been updated"); + await (Task.Delay(TimeSpan.FromSeconds(2))); + Assert.That(strategy2.CurrentVersion, Is.EqualTo(version), "the version should not be updated"); + + // make sure we don't get an item + item = await (cache.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Null, "item still exists in cache after clear"); + + item = await (cache2.GetAsync(key, CancellationToken.None)); + Assert.That(item, Is.Null, "item still exists in the second cache after clear"); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs new file mode 100644 index 00000000..3f59e572 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -0,0 +1,196 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + public abstract partial class RedisCacheFixture : CacheFixture + { + + [Test] + public async Task TestEnvironmentNameAsync() + { + var props = GetDefaultProperties(); + + var developProvider = ProviderBuilder(); + props[RedisEnvironment.EnvironmentName] = "develop"; + developProvider.Start(props); + var developCache = developProvider.BuildCache(DefaultRegion, props); + + var releaseProvider = ProviderBuilder(); + props[RedisEnvironment.EnvironmentName] = "release"; + releaseProvider.Start(props); + var releaseCache = releaseProvider.BuildCache(DefaultRegion, props); + + const string key = "testKey"; + const string value = "testValue"; + + await (developCache.PutAsync(key, value, CancellationToken.None)); + + Assert.That(await (releaseCache.GetAsync(key, CancellationToken.None)), Is.Null, "release environment should be separate from develop"); + + await (developCache.RemoveAsync(key, CancellationToken.None)); + await (releaseCache.PutAsync(key, value, CancellationToken.None)); + + Assert.That(await (developCache.GetAsync(key, CancellationToken.None)), Is.Null, "develop environment should be separate from release"); + + await (releaseCache.RemoveAsync(key, CancellationToken.None)); + + developProvider.Stop(); + releaseProvider.Stop(); + } + + [Test] + public async Task TestPutManyAsync() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestPut{i}"; + values[i] = $"valuePut{i}"; + } + + var cache = (RedisCache) GetDefaultCache(); + // Due to async version, it may already be there. + await (cache.RemoveManyAsync(keys, CancellationToken.None)); + + Assert.That(await (cache.GetManyAsync(keys, CancellationToken.None)), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); + + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + var items = await (cache.GetManyAsync(keys, CancellationToken.None)); + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); + Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); + } + } + + [Test] + public async Task TestRemoveManyAsync() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestRemove{i}"; + values[i] = $"valueRemove{i}"; + } + + var cache = (RedisCache) GetDefaultCache(); + + // add the item + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + + // make sure it's there + var items = await (cache.GetManyAsync(keys, CancellationToken.None)); + Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); + + // remove it + await (cache.RemoveManyAsync(keys, CancellationToken.None)); + + // make sure it's not there + items = await (cache.GetManyAsync(keys, CancellationToken.None)); + Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); + } + + [Test] + public async Task TestLockUnlockManyAsync() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestLock{i}"; + values[i] = $"valueLock{i}"; + } + + var cache = (RedisCache)GetDefaultCache(); + + // add the item + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + await (cache.LockManyAsync(keys, CancellationToken.None)); + Assert.ThrowsAsync(() => cache.LockManyAsync(keys, CancellationToken.None), "all items should be locked"); + + await (Task.Delay(cache.Timeout / Timestamper.OneMs)); + + for (var i = 0; i < 2; i++) + { + Assert.DoesNotThrowAsync(async () => + { + await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys, CancellationToken.None)), CancellationToken.None)); + }, "the items should be unlocked"); + } + + // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. + var lockValue = await (cache.LockManyAsync(keys.Take(5).ToArray(), CancellationToken.None)); + + Assert.ThrowsAsync(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "The fifth key should be locked"); + + Assert.DoesNotThrowAsync(async () => + { + await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys.Skip(5).ToArray(), CancellationToken.None)), CancellationToken.None)); + }, "the last 5 keys should not be locked."); + + // Unlock the first 5 keys + await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); + + Assert.DoesNotThrowAsync(async () => + { + lockValue = await (cache.LockManyAsync(keys, CancellationToken.None)); + await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); + }, "the first 5 keys should not be locked."); + } + + [Test] + public void TestNullKeyPutManyAsync() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.ThrowsAsync(() => cache.PutManyAsync(null, null, CancellationToken.None)); + } + + [Test] + public void TestNullValuePutManyAsync() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.ThrowsAsync(() => cache.PutManyAsync(new object[] { "keyTestNullValuePut" }, null, CancellationToken.None)); + } + + [Test] + public async Task TestNullKeyGetManyAsync() + { + var cache = (RedisCache) GetDefaultCache(); + await (cache.PutAsync("keyTestNullKeyGet", "value", CancellationToken.None)); + var items = await (cache.GetManyAsync(null, CancellationToken.None)); + Assert.IsNull(items); + } + + [Test] + public void TestNullKeyRemoveManyAsync() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.ThrowsAsync(() => cache.RemoveManyAsync(null, CancellationToken.None)); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs new file mode 100644 index 00000000..c58ab101 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; + +namespace NHibernate.Caches.StackExRedis.Tests.Caches +{ + /// + /// Operates with multiple independent Redis instances. + /// + public partial class DistributedRedisCache : ICache + { + private readonly AbstractRegionStrategy[] _regionStrategies; + private readonly Random _random = new Random(); + + public DistributedRedisCache(RedisCacheRegionConfiguration configuration, IEnumerable regionStrategies) + { + _regionStrategies = regionStrategies.ToArray(); + RegionName = configuration.RegionName; + Timeout = Timestamper.OneMs * (int) configuration.LockConfiguration.KeyTimeout.TotalMilliseconds; + } + + /// + /// The region strategies used by the cache. + /// + public IEnumerable RegionStrategies => _regionStrategies; + + /// + public int Timeout { get; } + + /// + public string RegionName { get; } + + /// + public long NextTimestamp() => Timestamper.Next(); + + /// + public object Get(object key) + { + // Use a random strategy to get the value. + // A real distributed cache should use a proper load balancing. + var strategy = _regionStrategies[_random.Next(0, _regionStrategies.Length - 1)]; + return strategy.Get(key); + } + + /// + public void Put(object key, object value) + { + foreach (var strategy in _regionStrategies) + { + strategy.Put(key, value); + } + } + + /// + public void Remove(object key) + { + foreach (var strategy in _regionStrategies) + { + strategy.Remove(key); + } + } + + /// + public void Clear() + { + foreach (var strategy in _regionStrategies) + { + strategy.Clear(); + } + } + + /// + public void Destroy() + { + } + + /// + public void Lock(object key) + { + // A simple locking that requires all instances to obtain the lock + // A real distributed cache should use something like the Redlock algorithm. + try + { + foreach (var strategy in _regionStrategies) + { + strategy.Lock(key); + } + + } + catch (CacheException) + { + foreach (var strategy in _regionStrategies) + { + strategy.Unlock(key); + } + throw; + } + } + + /// + public void Unlock(object key) + { + foreach (var strategy in _regionStrategies) + { + strategy.Unlock(key); + } + } + + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs new file mode 100644 index 00000000..94e39cb3 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NHibernate.Caches.StackExRedis.Tests.Providers; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture] + public class DistributedRedisCacheFixture : CacheFixture + { + protected override bool SupportsSlidingExpiration => true; + protected override bool SupportsLocking => true; + protected override bool SupportsDistinguishingKeysWithSameStringRepresentationAndHashcode => false; + + protected override Func ProviderBuilder => + () => new DistributedRedisCacheProvider(); + + protected override void Configure(Dictionary defaultProperties) + { + // Simulate Redis instances by using databases as instances + defaultProperties.Add(RedisEnvironment.Configuration, "127.0.0.1,defaultDatabase=0;127.0.0.1,defaultDatabase=1"); + base.Configure(defaultProperties); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj new file mode 100644 index 00000000..5e89828e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj @@ -0,0 +1,29 @@ + + + + NHibernate.Caches.StackExRedis.Tests + Unit tests of cache provider NHibernate using StackExchange.Redis. + net461;netcoreapp2.0 + true + + + NETFX;$(DefineConstants) + + + Exe + false + + + + + + + + + + + + + + + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs new file mode 100644 index 00000000..b262abad --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs @@ -0,0 +1,12 @@ +#if !NETFX +namespace NHibernate.Caches.StackExRedis.Tests +{ + public class Program + { + public static int Main(string[] args) + { + return new NUnitLite.AutoRun(typeof(Program).Assembly).Execute(args); + } + } +} +#endif diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..83cc064e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: CLSCompliant(false)] \ No newline at end of file diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs new file mode 100644 index 00000000..43d2d865 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.StackExRedis.Tests.Caches; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis.Tests.Providers +{ + /// + /// Provider for building a cache capable of operating with multiple independent Redis instances. + /// + public class DistributedRedisCacheProvider : RedisCacheProvider + { + private readonly List _connectionMultiplexers = new List(); + + /// + protected override void Start(string configurationString, IDictionary properties, TextWriter textWriter) + { + foreach (var instanceConfiguration in configurationString.Split(';')) + { + var configuration = ConfigurationOptions.Parse(instanceConfiguration); + var connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); + connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting + _connectionMultiplexers.Add(connectionMultiplexer); + } + } + + /// + protected override ICache BuildCache(RedisCacheConfiguration defaultConfiguration, RedisCacheRegionConfiguration regionConfiguration, + IDictionary properties) + { + var strategies = new List(); + foreach (var connectionMultiplexer in _connectionMultiplexers) + { + var regionStrategy = + defaultConfiguration.RegionStrategyFactory.Create(connectionMultiplexer, regionConfiguration, properties); + regionStrategy.Validate(); + strategies.Add(regionStrategy); + } + return new DistributedRedisCache(regionConfiguration, strategies); + } + + /// + public override void Stop() + { + foreach (var connectionMultiplexer in _connectionMultiplexers) + { + connectionMultiplexer.Dispose(); + } + _connectionMultiplexers.Clear(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs new file mode 100644 index 00000000..11e1a186 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture] + public partial class RedisCacheDefaultStrategyFixture : RedisCacheFixture + { + [Test] + public void TestNoExpiration() + { + var props = GetDefaultProperties(); + props["expiration"] = "0"; + Assert.Throws(() => DefaultProvider.BuildCache(DefaultRegion, props), + "Default region strategy should not allow to have no expiration"); + } + + [Test] + public void TestMaxAllowedVersion() + { + var cache = (RedisCache) GetDefaultCache(); + var strategy = (DefaultRegionStrategy)cache.RegionStrategy; + var version = strategy.CurrentVersion; + + var props = GetDefaultProperties(); + props.Add("cache.region_strategy.default.max_allowed_version", version.ToString()); + cache = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + strategy = (DefaultRegionStrategy) cache.RegionStrategy; + + cache.Clear(); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "The version was not reset to 1"); + } + + [Test] + public void TestClearWithMultipleClientsAndPubSub() + { + const string key = "keyTestClear"; + const string value = "valueClear"; + + var cache = (RedisCache)GetDefaultCache(); + var strategy = (DefaultRegionStrategy)cache.RegionStrategy; + var cache2 = (RedisCache) GetDefaultCache(); + var strategy2 = (DefaultRegionStrategy) cache2.RegionStrategy; + + // add the item + cache.Put(key, value); + + // make sure it's there + var item = cache.Get(key); + Assert.That(item, Is.Not.Null, "couldn't find item in cache"); + + item = cache2.Get(key); + Assert.That(item, Is.Not.Null, "couldn't find item in second cache"); + + var version = strategy.CurrentVersion; + + // clear the cache + cache.Clear(); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(version + 1), "the version has not been updated"); + Thread.Sleep(TimeSpan.FromSeconds(2)); + Assert.That(strategy2.CurrentVersion, Is.EqualTo(version + 1), "the version should be updated with the pub/sub api"); + + // make sure we don't get an item + item = cache.Get(key); + Assert.That(item, Is.Null, "item still exists in cache after clear"); + + item = cache2.Get(key); + Assert.That(item, Is.Null, "item still exists in the second cache after clear"); + } + + [Test] + public void TestClearWithMultipleClientsAndNoPubSub() + { + const string key = "keyTestClear"; + const string value = "valueClear"; + + var props = GetDefaultProperties(); + props.Add("cache.region_strategy.default.use_pubsub", "false"); + + var cache = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + var strategy = (DefaultRegionStrategy) cache.RegionStrategy; + var cache2 = (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + var strategy2 = (DefaultRegionStrategy) cache2.RegionStrategy; + + // add the item + cache.Put(key, value); + + // make sure it's there + var item = cache.Get(key); + Assert.That(item, Is.Not.Null, "couldn't find item in cache"); + + item = cache2.Get(key); + Assert.That(item, Is.Not.Null, "couldn't find item in second cache"); + + var version = strategy.CurrentVersion; + + // clear the cache + cache.Clear(); + + Assert.That(strategy.CurrentVersion, Is.EqualTo(version + 1), "the version has not been updated"); + Thread.Sleep(TimeSpan.FromSeconds(2)); + Assert.That(strategy2.CurrentVersion, Is.EqualTo(version), "the version should not be updated"); + + // make sure we don't get an item + item = cache.Get(key); + Assert.That(item, Is.Null, "item still exists in cache after clear"); + + item = cache2.Get(key); + Assert.That(item, Is.Null, "item still exists in the second cache after clear"); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs new file mode 100644 index 00000000..978ab4e6 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture] + public class RedisCacheFastStrategyFixture : RedisCacheFixture + { + protected override bool SupportsClear => false; + + protected override void Configure(Dictionary defaultProperties) + { + base.Configure(defaultProperties); + defaultProperties.Add(RedisEnvironment.RegionStrategy, typeof(FastRegionStrategy).AssemblyQualifiedName); + } + + [Test] + public void TestRegionStrategyType() + { + var cache = (RedisCache)GetDefaultCache(); + Assert.That(cache, Is.Not.Null, "cache is not a redis cache."); + + Assert.That(cache.RegionStrategy, Is.TypeOf(), "cache strategy is not type of FastRegionStrategy"); + } + + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs new file mode 100644 index 00000000..13929b49 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture] + public abstract partial class RedisCacheFixture : CacheFixture + { + protected override bool SupportsSlidingExpiration => true; + protected override bool SupportsLocking => true; + protected override bool SupportsDistinguishingKeysWithSameStringRepresentationAndHashcode => false; + + protected override Func ProviderBuilder => + () => new RedisCacheProvider(); + + [Test] + public void TestEnvironmentName() + { + var props = GetDefaultProperties(); + + var developProvider = ProviderBuilder(); + props[RedisEnvironment.EnvironmentName] = "develop"; + developProvider.Start(props); + var developCache = developProvider.BuildCache(DefaultRegion, props); + + var releaseProvider = ProviderBuilder(); + props[RedisEnvironment.EnvironmentName] = "release"; + releaseProvider.Start(props); + var releaseCache = releaseProvider.BuildCache(DefaultRegion, props); + + const string key = "testKey"; + const string value = "testValue"; + + developCache.Put(key, value); + + Assert.That(releaseCache.Get(key), Is.Null, "release environment should be separate from develop"); + + developCache.Remove(key); + releaseCache.Put(key, value); + + Assert.That(developCache.Get(key), Is.Null, "develop environment should be separate from release"); + + releaseCache.Remove(key); + + developProvider.Stop(); + releaseProvider.Stop(); + } + + [Test] + public void TestPutMany() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestPut{i}"; + values[i] = $"valuePut{i}"; + } + + var cache = (RedisCache) GetDefaultCache(); + // Due to async version, it may already be there. + cache.RemoveMany(keys); + + Assert.That(cache.GetMany(keys), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); + + cache.PutMany(keys, values); + var items = cache.GetMany(keys); + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); + Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); + } + } + + [Test] + public void TestRemoveMany() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestRemove{i}"; + values[i] = $"valueRemove{i}"; + } + + var cache = (RedisCache) GetDefaultCache(); + + // add the item + cache.PutMany(keys, values); + + // make sure it's there + var items = cache.GetMany(keys); + Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); + + // remove it + cache.RemoveMany(keys); + + // make sure it's not there + items = cache.GetMany(keys); + Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); + } + + [Test] + public void TestLockUnlockMany() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestLock{i}"; + values[i] = $"valueLock{i}"; + } + + var cache = (RedisCache)GetDefaultCache(); + + // add the item + cache.PutMany(keys, values); + cache.LockMany(keys); + Assert.Throws(() => cache.LockMany(keys), "all items should be locked"); + + Thread.Sleep(cache.Timeout / Timestamper.OneMs); + + for (var i = 0; i < 2; i++) + { + Assert.DoesNotThrow(() => + { + cache.UnlockMany(keys, cache.LockMany(keys)); + }, "the items should be unlocked"); + } + + // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. + var lockValue = cache.LockMany(keys.Take(5).ToArray()); + + Assert.Throws(() => cache.LockMany(keys.Skip(4).ToArray()), "The fifth key should be locked"); + + Assert.DoesNotThrow(() => + { + cache.UnlockMany(keys, cache.LockMany(keys.Skip(5).ToArray())); + }, "the last 5 keys should not be locked."); + + // Unlock the first 5 keys + cache.UnlockMany(keys, lockValue); + + Assert.DoesNotThrow(() => + { + lockValue = cache.LockMany(keys); + cache.UnlockMany(keys, lockValue); + }, "the first 5 keys should not be locked."); + } + + [Test] + public void TestNullKeyPutMany() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.Throws(() => cache.PutMany(null, null)); + } + + [Test] + public void TestNullValuePutMany() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.Throws(() => cache.PutMany(new object[] { "keyTestNullValuePut" }, null)); + } + + [Test] + public void TestNullKeyGetMany() + { + var cache = (RedisCache) GetDefaultCache(); + cache.Put("keyTestNullKeyGet", "value"); + var items = cache.GetMany(null); + Assert.IsNull(items); + } + + [Test] + public void TestNullKeyRemoveMany() + { + var cache = (RedisCache) GetDefaultCache(); + Assert.Throws(() => cache.RemoveMany(null)); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs new file mode 100644 index 00000000..4654ceff --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture] + public class RedisCacheProviderFixture : CacheProviderFixture + { + protected override Func ProviderBuilder => + () => new RedisCacheProvider(); + + [Test] + public void TestBuildCacheFromConfig() + { + var cache = DefaultProvider.BuildCache("foo", null); + Assert.That(cache, Is.Not.Null, "pre-configured cache not found"); + } + + [Test] + public void TestExpiration() + { + var cache = DefaultProvider.BuildCache("foo", null) as RedisCache; + Assert.That(cache, Is.Not.Null, "pre-configured foo cache not found"); + + var strategy = cache.RegionStrategy; + Assert.That(strategy, Is.Not.Null, "strategy was not set for the pre-configured foo cache"); + Assert.That(strategy, Is.TypeOf(), "Unexpected strategy type for foo region"); + Assert.That(strategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "Unexpected expiration value for foo region"); + + cache = (RedisCache) DefaultProvider.BuildCache("noExplicitExpiration", null); + Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(300)), + "Unexpected expiration value for noExplicitExpiration region"); + Assert.That(cache.RegionStrategy.UseSlidingExpiration, Is.True, "Unexpected sliding value for noExplicitExpiration region"); + + cache = (RedisCache) DefaultProvider + .BuildCache("noExplicitExpiration", new Dictionary { { "expiration", "100" } }); + Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(100)), + "Unexpected expiration value for noExplicitExpiration region with default expiration"); + + cache = (RedisCache) DefaultProvider + .BuildCache("noExplicitExpiration", new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } }); + Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(50)), + "Unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); + } + + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs new file mode 100644 index 00000000..77e95173 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs @@ -0,0 +1,42 @@ +using log4net.Repository.Hierarchy; +#if !NETFX +using NHibernate.Caches.Common.Tests; +#endif +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [SetUpFixture] + public class TestsContext + { + [OneTimeSetUp] + public void RunBeforeAnyTests() + { +#if !NETFX + TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "redis"); +#endif + ConfigureLog4Net(); + } + +#if !NETFX + [OneTimeTearDown] + public void RunAfterAnyTests() + { + TestsContextHelper.RunAfterAnyTests(); + } +#endif + + private static void ConfigureLog4Net() + { + var hierarchy = (Hierarchy)log4net.LogManager.GetRepository(typeof(TestsContext).Assembly); + + var consoleAppender = new log4net.Appender.ConsoleAppender + { + Layout = new log4net.Layout.PatternLayout("%d{ABSOLUTE} %-5p %c{1}:%L - %m%n"), + }; + hierarchy.Root.Level = log4net.Core.Level.Info; + hierarchy.Root.AddAppender(consoleAppender); + hierarchy.Configured = true; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs new file mode 100644 index 00000000..d7d24be8 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -0,0 +1,541 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// An abstract region strategy that provides common functionalities to create a region strategy. + /// + public abstract partial class AbstractRegionStrategy + { + /// + /// The NHibernate logger. + /// + protected readonly INHibernateLogger Log; + + /// + /// The Redis connection. + /// + protected readonly ConnectionMultiplexer ConnectionMultiplexer; + + /// + /// The Redis database where the keys are stored. + /// + protected readonly IDatabase Database; + + /// + /// The instance. + /// + protected readonly IRedisSerializer Serializer; + + private readonly RedisKeyLocker _keyLocker; + + /// + /// With the current NHibernate version (5.1) the method does not have a + /// return value that would indicate which lock value was used to lock the key, that would then be + /// passed to the method in order to prevent unlocking keys that were locked + /// by others. Luckily, the and methods are currently + /// always called inside a lock statement which we abuse by storing the locked key when the + /// is called and use it when is called. This + /// means that our and are not thread safe and have to be called + /// sequentially in order prevent overriding the lock key for a given key. + /// Currently, the is always called after the method + /// even if an exception occurs which we are also abusing in order to avoid having a special mechanism to + /// clear the dictionary in order to prevent memory leaks. + /// + private readonly ConcurrentDictionary _aquiredKeyLocks = new ConcurrentDictionary(); + + + /// + /// The constructor for creating the region strategy. + /// + /// The Redis connection. + /// The region configuration. + /// The NHibernate configuration properties. + protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + RedisCacheRegionConfiguration configuration, IDictionary properties) + { + Log = NHibernateLogger.For(GetType()); + RegionName = configuration.RegionName; + Expiration = configuration.Expiration; + UseSlidingExpiration = configuration.UseSlidingExpiration; + RegionKey = configuration.RegionKey; + ConnectionMultiplexer = connectionMultiplexer; + Database = connectionMultiplexer.GetDatabase(configuration.Database); + Serializer = configuration.Serializer; + LockTimeout = configuration.LockConfiguration.KeyTimeout; + _keyLocker = new RedisKeyLocker(RegionName, Database, configuration.LockConfiguration); + } + + /// + /// The lua script for getting a key from the cache. + /// + protected virtual string GetScript => null; + + /// + /// The lua script for getting many keys from the cache at once. + /// + protected virtual string GetManyScript => null; + + /// + /// The lua script for putting a key into the cache. + /// + protected virtual string PutScript => null; + + /// + /// The lua script for putting many keys into the cache at once. + /// + protected abstract string PutManyScript { get; } + + /// + /// The lua script for removing a key from the cache. + /// + protected virtual string RemoveScript => null; + + /// + /// The lua script for removing many keys from the cache at once. + /// + protected virtual string RemoveManyScript => null; + + /// + /// The lua script for locking a key. + /// + protected virtual string LockScript => null; + + /// + /// The lua script for locking many keys at once. + /// + protected abstract string LockManyScript { get; } + + /// + /// The lua script for unlocking a key. + /// + protected virtual string UnlockScript => null; + + /// + /// The lua script for unlocking many keys at once. + /// + protected abstract string UnlockManyScript { get; } + + /// + /// The expiration delay applied to cached items. + /// + public TimeSpan Expiration { get; } + + /// + /// The name of the region. + /// + public string RegionName { get; } + + /// + /// The key representing the region that is composed of , + /// , + /// and . + /// + public string RegionKey { get; } + + /// + /// Should the expiration delay be sliding? + /// + /// for resetting a cached item expiration each time it is accessed. + public bool UseSlidingExpiration { get; } + + /// + /// Is the expiration enabled? + /// + public bool ExpirationEnabled => Expiration != TimeSpan.Zero; + + /// + /// The timeout of an aquired lock. + /// + public TimeSpan LockTimeout { get; } + + /// + /// Gets the object that is stored in Redis by its key. + /// + /// The key of the object to retrieve. + /// The object behind the key or if the key was not found. + public virtual object Get(object key) + { + if (key == null) + { + return null; + } + var cacheKey = GetCacheKey(key); + RedisValue result; + if (string.IsNullOrEmpty(GetScript)) + { + result = Database.StringGet(cacheKey); + } + else + { + var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var values = AppendAdditionalValues(new RedisValue[] + { + UseSlidingExpiration && ExpirationEnabled, + (long) Expiration.TotalMilliseconds + }); + var results = (RedisValue[]) Database.ScriptEvaluate(GetScript, keys, values); + result = results[0]; + } + return result.IsNullOrEmpty ? null : Serializer.Deserialize(result); + } + + /// + /// Gets the objects that are stored in Redis by their key. + /// + /// The keys of the objects to retrieve. + /// An array of objects behind the keys or if the key was not found. + public virtual object[] GetMany(object[] keys) + { + if (keys == null) + { + return null; + } + var cacheKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + RedisValue[] results; + if (string.IsNullOrEmpty(GetManyScript)) + { + results = Database.StringGet(cacheKeys); + } + else + { + cacheKeys = AppendAdditionalKeys(cacheKeys); + var values = AppendAdditionalValues(new RedisValue[] + { + UseSlidingExpiration && ExpirationEnabled, + (long) Expiration.TotalMilliseconds + }); + results = (RedisValue[]) Database.ScriptEvaluate(GetManyScript, cacheKeys, values); + } + + var objects = new object[keys.Length]; + for (var i = 0; i < results.Length; i++) + { + var result = results[i]; + if (!result.IsNullOrEmpty) + { + objects[i] = Serializer.Deserialize(result); + } + } + return objects; + } + + /// + /// Stores the object into Redis by the given key. + /// + /// The key to store the object. + /// The object to store. + public virtual void Put(object key, object value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + var cacheKey = GetCacheKey(key); + var serializedValue = Serializer.Serialize(value); + + if (string.IsNullOrEmpty(PutScript)) + { + Database.StringSet(cacheKey, serializedValue, ExpirationEnabled ? Expiration : (TimeSpan?) null); + return; + } + + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); + var values = AppendAdditionalValues(new[] + { + serializedValue, + ExpirationEnabled, + (long)Expiration.TotalMilliseconds + }); + Database.ScriptEvaluate(PutScript, keys, values); + } + + /// + /// Stores the objects into Redis by the given keys. + /// + /// The keys to store the objects. + /// The objects to store. + public virtual void PutMany(object[] keys, object[] values) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + if (keys.Length != values.Length) + { + throw new ArgumentException($"Length of {nameof(keys)} array does not match with {nameof(values)} array."); + } + if (string.IsNullOrEmpty(PutManyScript)) + { + if (ExpirationEnabled) + { + throw new NotSupportedException($"{nameof(PutMany)} operation is not supported."); + } + var pairs = new KeyValuePair[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + pairs[i] = new KeyValuePair(GetCacheKey(keys[i]), Serializer.Serialize(values[i])); + } + Database.StringSet(pairs); + return; + } + + + var cacheKeys = new RedisKey[keys.Length]; + var cacheValues = new RedisValue[keys.Length + 2]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + cacheValues[i] = Serializer.Serialize(values[i]); + } + cacheKeys = AppendAdditionalKeys(cacheKeys); + cacheValues[keys.Length] = ExpirationEnabled; + cacheValues[keys.Length + 1] = (long) Expiration.TotalMilliseconds; + cacheValues = AppendAdditionalValues(cacheValues); + Database.ScriptEvaluate(PutManyScript, cacheKeys, cacheValues); + } + + /// + /// Removes the key from Redis. + /// + /// The key to remove. + public virtual bool Remove(object key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var cacheKey = GetCacheKey(key); + if (string.IsNullOrEmpty(RemoveScript)) + { + return Database.KeyDelete(cacheKey); + } + var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var values = GetAdditionalValues(); + var results = (RedisValue[]) Database.ScriptEvaluate(RemoveScript, keys, values); + return (bool) results[0]; + } + + /// + /// Removes many keys from Redis at once. + /// + /// The keys to remove. + public virtual long RemoveMany(object[] keys) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + var cacheKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + if (string.IsNullOrEmpty(RemoveManyScript)) + { + return Database.KeyDelete(cacheKeys); + } + cacheKeys = AppendAdditionalKeys(cacheKeys); + var results = (RedisValue[]) Database.ScriptEvaluate(RemoveManyScript, cacheKeys, GetAdditionalValues()); + return (long) results[0]; + } + + /// + /// Locks the key. + /// + /// The key to lock. + /// The value used to lock the key. + public virtual string Lock(object key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + var cacheKey = GetCacheKey(key); + var lockValue = _keyLocker.Lock(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues()); + + _aquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => + { + Log.Warn( + $"Calling {nameof(Lock)} method for key:'{cacheKey}' that was already locked. " + + $"{nameof(Unlock)} method must be called after each {nameof(Lock)} call for " + + $"the same key prior calling {nameof(Lock)} again with the same key."); + return lockValue; + }); + return lockValue; + } + + /// + /// Locks many keys at once. + /// + /// The keys to lock. + /// The value used to lock the keys. + public virtual string LockMany(object[] keys) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (string.IsNullOrEmpty(LockManyScript)) + { + throw new NotSupportedException($"{nameof(LockMany)} operation is not supported."); + } + var cacheKeys = new string[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + return _keyLocker.LockMany(cacheKeys, LockManyScript, GetAdditionalKeys(), GetAdditionalValues()); + } + + /// + /// Unlocks the key. + /// + /// The key to unlock. + /// Whether the key was unlocked + public virtual bool Unlock(object key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + var cacheKey = GetCacheKey(key); + + if (!_aquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) + { + Log.Warn( + $"Calling {nameof(Unlock)} method for key:'{cacheKey}' that was not locked with {nameof(Lock)} method before."); + return false; + } + return _keyLocker.Unlock(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues()); + } + + /// + /// Unlocks many keys at once. + /// + /// The keys to unlock. + /// The value used to lock the keys + /// The number of unlocked keys. + public virtual int UnlockMany(object[] keys, string lockValue) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (string.IsNullOrEmpty(UnlockManyScript)) + { + throw new NotSupportedException($"{nameof(UnlockMany)} operation is not supported."); + } + var cacheKeys = new string[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + var cacheKey = GetCacheKey(keys[i]); + cacheKeys[i] = cacheKey; + } + return _keyLocker.UnlockMany(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues()); + } + + /// + /// Clears all the keys from the region. + /// + public abstract void Clear(); + + /// + /// Validates if the region strategy was correctly configured. + /// + /// Thrown when the region strategy is not configured correctly. + public abstract void Validate(); + + /// + /// Gets additional values required by the concrete strategy that can be used in the lua scripts. + /// + /// The values to be used in the lua scripts. + protected virtual RedisValue[] GetAdditionalValues() + { + return null; + } + + /// + /// Gets additional keys required by the concrete strategy that can be used in the lua scripts. + /// + /// The keys to be used in the lua scripts. + protected virtual RedisKey[] GetAdditionalKeys() + { + return null; + } + + /// + /// Calculates the cache key for the given object. + /// + /// The object for which the key will be calculated. + /// The key for the given object. + protected virtual string GetCacheKey(object value) + { + // Hash tag (wrap with curly brackets) the region key in order to ensure that all region keys + // will be located on the same server, when a Redis cluster is used. + return string.Concat("{", RegionKey, "}:", value.ToString(), "@", value.GetHashCode()); + } + + /// + /// Combine the given values with the values returned from . + /// + /// The values to combine with the additional values. + /// An array of combined values. + protected RedisValue[] AppendAdditionalValues(RedisValue[] values) + { + if (values == null) + { + return GetAdditionalValues(); + } + var additionalValues = GetAdditionalValues(); + if (additionalValues == null) + { + return values; + } + var combinedValues = new RedisValue[values.Length + additionalValues.Length]; + values.CopyTo(combinedValues, 0); + additionalValues.CopyTo(combinedValues, values.Length); + return combinedValues; + } + + /// + /// Combine the given keys with the keys returned from . + /// + /// The keys to combine with the additional keys. + /// An array of combined keys. + protected RedisKey[] AppendAdditionalKeys(RedisKey[] keys) + { + if (keys == null) + { + return GetAdditionalKeys(); + } + var additionalKeys = GetAdditionalKeys(); + if (additionalKeys == null) + { + return keys; + } + var combinedKeys = new RedisKey[keys.Length + additionalKeys.Length]; + keys.CopyTo(combinedKeys, 0); + additionalKeys.CopyTo(combinedKeys, keys.Length); + return combinedKeys; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs new file mode 100644 index 00000000..60157121 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -0,0 +1,426 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + using System.Threading.Tasks; + using System.Threading; + public abstract partial class AbstractRegionStrategy + { + + /// + /// Gets the object that is stored in Redis by its key. + /// + /// The key of the object to retrieve. + /// A cancellation token that can be used to cancel the work + /// The object behind the key or if the key was not found. + public virtual async Task GetAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (key == null) + { + return null; + } + var cacheKey = GetCacheKey(key); + RedisValue result; + if (string.IsNullOrEmpty(GetScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + result = await (Database.StringGetAsync(cacheKey)).ConfigureAwait(false); + } + else + { + var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var values = AppendAdditionalValues(new RedisValue[] + { + UseSlidingExpiration && ExpirationEnabled, + (long) Expiration.TotalMilliseconds + }); + cancellationToken.ThrowIfCancellationRequested(); + var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(GetScript, keys, values)).ConfigureAwait(false); + result = results[0]; + } + return result.IsNullOrEmpty ? null : Serializer.Deserialize(result); + } + + /// + /// Gets the objects that are stored in Redis by their key. + /// + /// The keys of the objects to retrieve. + /// A cancellation token that can be used to cancel the work + /// An array of objects behind the keys or if the key was not found. + public virtual async Task GetManyAsync(object[] keys, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (keys == null) + { + return null; + } + var cacheKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + RedisValue[] results; + if (string.IsNullOrEmpty(GetManyScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + results = await (Database.StringGetAsync(cacheKeys)).ConfigureAwait(false); + } + else + { + cacheKeys = AppendAdditionalKeys(cacheKeys); + var values = AppendAdditionalValues(new RedisValue[] + { + UseSlidingExpiration && ExpirationEnabled, + (long) Expiration.TotalMilliseconds + }); + cancellationToken.ThrowIfCancellationRequested(); + results = (RedisValue[]) await (Database.ScriptEvaluateAsync(GetManyScript, cacheKeys, values)).ConfigureAwait(false); + } + + var objects = new object[keys.Length]; + for (var i = 0; i < results.Length; i++) + { + var result = results[i]; + if (!result.IsNullOrEmpty) + { + objects[i] = Serializer.Deserialize(result); + } + } + return objects; + } + + /// + /// Stores the object into Redis by the given key. + /// + /// The key to store the object. + /// The object to store. + /// A cancellation token that can be used to cancel the work + public virtual Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalPutAsync(); + async Task InternalPutAsync() + { + var cacheKey = GetCacheKey(key); + var serializedValue = Serializer.Serialize(value); + + if (string.IsNullOrEmpty(PutScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + await (Database.StringSetAsync(cacheKey, serializedValue, ExpirationEnabled ? Expiration : (TimeSpan?) null)).ConfigureAwait(false); + return; + } + + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); + var values = AppendAdditionalValues(new[] + { + serializedValue, + ExpirationEnabled, + (long)Expiration.TotalMilliseconds + }); + cancellationToken.ThrowIfCancellationRequested(); + await (Database.ScriptEvaluateAsync(PutScript, keys, values)).ConfigureAwait(false); + } + } + + /// + /// Stores the objects into Redis by the given keys. + /// + /// The keys to store the objects. + /// The objects to store. + /// A cancellation token that can be used to cancel the work + public virtual Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + if (keys.Length != values.Length) + { + throw new ArgumentException($"Length of {nameof(keys)} array does not match with {nameof(values)} array."); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalPutManyAsync(); + async Task InternalPutManyAsync() + { + if (string.IsNullOrEmpty(PutManyScript)) + { + if (ExpirationEnabled) + { + throw new NotSupportedException($"{nameof(PutManyAsync)} operation is not supported."); + } + var pairs = new KeyValuePair[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + pairs[i] = new KeyValuePair(GetCacheKey(keys[i]), Serializer.Serialize(values[i])); + } + cancellationToken.ThrowIfCancellationRequested(); + await (Database.StringSetAsync(pairs)).ConfigureAwait(false); + return; + } + + + var cacheKeys = new RedisKey[keys.Length]; + var cacheValues = new RedisValue[keys.Length + 2]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + cacheValues[i] = Serializer.Serialize(values[i]); + } + cacheKeys = AppendAdditionalKeys(cacheKeys); + cacheValues[keys.Length] = ExpirationEnabled; + cacheValues[keys.Length + 1] = (long) Expiration.TotalMilliseconds; + cacheValues = AppendAdditionalValues(cacheValues); + cancellationToken.ThrowIfCancellationRequested(); + await (Database.ScriptEvaluateAsync(PutManyScript, cacheKeys, cacheValues)).ConfigureAwait(false); + } + } + + /// + /// Removes the key from Redis. + /// + /// The key to remove. + /// A cancellation token that can be used to cancel the work + public virtual Task RemoveAsync(object key, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalRemoveAsync(); + async Task InternalRemoveAsync() + { + + var cacheKey = GetCacheKey(key); + if (string.IsNullOrEmpty(RemoveScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + return await (Database.KeyDeleteAsync(cacheKey)).ConfigureAwait(false); + } + var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var values = GetAdditionalValues(); + cancellationToken.ThrowIfCancellationRequested(); + var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(RemoveScript, keys, values)).ConfigureAwait(false); + return (bool) results[0]; + } + } + + /// + /// Removes many keys from Redis at once. + /// + /// The keys to remove. + /// A cancellation token that can be used to cancel the work + public virtual Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalRemoveManyAsync(); + async Task InternalRemoveManyAsync() + { + var cacheKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + if (string.IsNullOrEmpty(RemoveManyScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + return await (Database.KeyDeleteAsync(cacheKeys)).ConfigureAwait(false); + } + cacheKeys = AppendAdditionalKeys(cacheKeys); + cancellationToken.ThrowIfCancellationRequested(); + var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(RemoveManyScript, cacheKeys, GetAdditionalValues())).ConfigureAwait(false); + return (long) results[0]; + } + } + + /// + /// Locks the key. + /// + /// The key to lock. + /// A cancellation token that can be used to cancel the work + /// The value used to lock the key. + public virtual Task LockAsync(object key, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalLockAsync(); + async Task InternalLockAsync() + { + var cacheKey = GetCacheKey(key); + var lockValue = await (_keyLocker.LockAsync(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); + + _aquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => + { + Log.Warn( + $"Calling {nameof(LockAsync)} method for key:'{cacheKey}' that was already locked. " + + $"{nameof(UnlockAsync)} method must be called after each {nameof(LockAsync)} call for " + + $"the same key prior calling {nameof(LockAsync)} again with the same key."); + return lockValue; + }); + return lockValue; + } + } + + /// + /// Locks many keys at once. + /// + /// The keys to lock. + /// A cancellation token that can be used to cancel the work + /// The value used to lock the keys. + public virtual Task LockManyAsync(object[] keys, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (string.IsNullOrEmpty(LockManyScript)) + { + throw new NotSupportedException($"{nameof(LockManyAsync)} operation is not supported."); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + var cacheKeys = new string[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + } + return _keyLocker.LockManyAsync(cacheKeys, LockManyScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// Unlocks the key. + /// + /// The key to unlock. + /// A cancellation token that can be used to cancel the work + /// Whether the key was unlocked + public virtual Task UnlockAsync(object key, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + var cacheKey = GetCacheKey(key); + + if (!_aquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) + { + Log.Warn( + $"Calling {nameof(UnlockAsync)} method for key:'{cacheKey}' that was not locked with {nameof(LockAsync)} method before."); + return Task.FromResult(false); + } + return _keyLocker.UnlockAsync(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// Unlocks many keys at once. + /// + /// The keys to unlock. + /// The value used to lock the keys + /// A cancellation token that can be used to cancel the work + /// The number of unlocked keys. + public virtual Task UnlockManyAsync(object[] keys, string lockValue, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (string.IsNullOrEmpty(UnlockManyScript)) + { + throw new NotSupportedException($"{nameof(UnlockManyAsync)} operation is not supported."); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + var cacheKeys = new string[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + var cacheKey = GetCacheKey(keys[i]); + cacheKeys[i] = cacheKey; + } + return _keyLocker.UnlockManyAsync(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// Clears all the keys from the region. + /// + public abstract Task ClearAsync(); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs new file mode 100644 index 00000000..021b0952 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -0,0 +1,209 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.Collections.Generic; +using NHibernate.Cache; +using StackExchange.Redis; +using static NHibernate.Caches.StackExRedis.ConfigurationHelper; + +namespace NHibernate.Caches.StackExRedis +{ + using System.Threading.Tasks; + using System.Threading; + public partial class DefaultRegionStrategy : AbstractRegionStrategy + { + + /// + public override async Task GetAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.GetAsync(key, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + return await (base.GetAsync(key, cancellationToken)).ConfigureAwait(false); + } + } + + /// + public override async Task GetManyAsync(object[] keys, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + return await (base.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + } + + /// + public override async Task LockAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.LockAsync(key, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + return await (base.LockAsync(key, cancellationToken)).ConfigureAwait(false); + } + } + + /// + public override async Task LockManyAsync(object[] keys, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + return await (base.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + } + + /// + public override async Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + await (base.PutAsync(key, value, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // Here we don't know if the operation was executed after as successful lock, so + // the easiest solution is to skip the operation + } + } + + /// + public override async Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + await (base.PutManyAsync(keys, values, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // Here we don't know if the operation was executed after as successful lock, so + // the easiest solution is to skip the operation + } + } + + /// + public override async Task RemoveAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.RemoveAsync(key, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // There is no point removing the key in the new version. + return false; + } + } + + /// + public override async Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.RemoveManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // There is no point removing the keys in the new version. + return 0L; + } + } + + /// + public override async Task UnlockAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.UnlockAsync(key, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // If the lock was aquired in the old version we are unable to unlock the key. + return false; + } + } + + /// + public override async Task UnlockManyAsync(object[] keys, string lockValue, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + return await (base.UnlockManyAsync(keys, lockValue, cancellationToken)).ConfigureAwait(false); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + cancellationToken.ThrowIfCancellationRequested(); + await (InitializeVersionAsync()).ConfigureAwait(false); + // If the lock was aquired in the old version we are unable to unlock the keys. + return 0; + } + } + + /// + public override async Task ClearAsync() + { + var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(UpdateVersionLuaScript, + _regionKeyArray, _maxVersionNumber)).ConfigureAwait(false); + var version = results[0]; + UpdateVersion(version); + if (_usePubSub) + { + await (ConnectionMultiplexer.GetSubscriber().PublishAsync(RegionKey, version)).ConfigureAwait(false); + } + } + + private async Task InitializeVersionAsync() + { + var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(InitializeVersionLuaScript, _regionKeyArray)).ConfigureAwait(false); + var version = results[0]; + UpdateVersion(version); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs new file mode 100644 index 00000000..497d4565 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + public partial class FastRegionStrategy : AbstractRegionStrategy + { + + /// + public override Task ClearAsync() + { + throw new NotSupportedException( + $"{nameof(ClearAsync)} operation is not supported, if it cannot be avoided use {nameof(DefaultRegionStrategy)}."); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs new file mode 100644 index 00000000..7190f8cf --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs @@ -0,0 +1,59 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System.IO; +using System.Text; + +namespace NHibernate.Caches.StackExRedis +{ + using System.Threading.Tasks; + internal partial class NHibernateTextWriter : TextWriter + { + + public override Task WriteAsync(string value) + { + try + { + Write(value); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task WriteLineAsync(string value) + { + try + { + WriteLine(value); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public override Task WriteLineAsync() + { + try + { + WriteLine(); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs new file mode 100644 index 00000000..086f80c3 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + public partial class RedisCache : ICache + { + + /// + public Task GetAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.GetAsync(key, cancellationToken); + } + + /// + public Task GetManyAsync(object[] keys, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.GetManyAsync(keys, cancellationToken); + } + + /// + public Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.PutAsync(key, value, cancellationToken); + } + + /// + public Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.PutManyAsync(keys, values, cancellationToken); + } + + /// + public Task RemoveAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.RemoveAsync(key, cancellationToken); + } + + /// + public Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.RemoveManyAsync(keys, cancellationToken); + } + + /// + public Task ClearAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + cancellationToken.ThrowIfCancellationRequested(); + return RegionStrategy.ClearAsync(); + } + + /// + public Task LockAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.LockAsync(key, cancellationToken); + } + + /// + public async Task LockManyAsync(object[] keys, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return await (RegionStrategy.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); + } + + /// + public Task UnlockAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return RegionStrategy.UnlockAsync(key, cancellationToken); + } + + /// + public Task UnlockManyAsync(object[] keys, object lockValue, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return RegionStrategy.UnlockManyAsync(keys, (string) lockValue, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs new file mode 100644 index 00000000..440b9710 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs @@ -0,0 +1,260 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + using System.Threading.Tasks; + internal partial class RedisKeyLocker + { + + /// + /// Tries to lock the given key. + /// + /// The key to lock. + /// The lua script to lock the key. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// A cancellation token that can be used to cancel the work + /// The lock value used to lock the key. + /// Thrown if the lock was not aquired. + public Task LockAsync(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalLockAsync(); + async Task InternalLockAsync() + { + var lockKey = $"{key}{_lockKeyPostfix}"; + var totalAttempts = 0; + var lockTimer = new Stopwatch(); + lockTimer.Restart(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); + await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false); + } + var lockValue = _lockValueProvider.GetValue(); + if (!string.IsNullOrEmpty(luaScript)) + { + var keys = new RedisKey[] {lockKey}; + if (extraKeys != null) + { + keys = keys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + cancellationToken.ThrowIfCancellationRequested(); + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false); + if ((bool) result[0]) + { + return lockValue; + } + } + else if (await (_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false)) + { + return lockValue; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + + throw new CacheException("Unable to acquire cache lock: " + + $"region='{_regionName}', " + + $"key='{key}', " + + $"total attempts='{totalAttempts}', " + + $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + } + } + + /// + /// Tries to lock the given keys. + /// + /// The keys to lock. + /// The lua script to lock the keys. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// A cancellation token that can be used to cancel the work + /// The lock value used to lock the keys. + /// Thrown if the lock was not aquired. + public Task LockManyAsync(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (luaScript == null) + { + throw new ArgumentNullException(nameof(luaScript)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalLockManyAsync(); + async Task InternalLockManyAsync() + { + + var lockKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + } + var totalAttempts = 0; + var lockTimer = new Stopwatch(); + lockTimer.Restart(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); + await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false); + } + var lockValue = _lockValueProvider.GetValue(); + if (extraKeys != null) + { + lockKeys = lockKeys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + cancellationToken.ThrowIfCancellationRequested(); + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false); + if ((bool) result[0]) + { + return lockValue; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + + throw new CacheException("Unable to acquire cache lock: " + + $"region='{_regionName}', " + + $"keys='{string.Join(",", lockKeys)}', " + + $"total attempts='{totalAttempts}', " + + $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + } + } + + /// + /// Tries to unlock the given key. + /// + /// The key to unlock. + /// The value that was used to lock the key. + /// The lua script to unlock the key. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// A cancellation token that can be used to cancel the work + /// Whether the key was unlocked. + public Task UnlockAsync(string key, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalUnlockAsync(); + async Task InternalUnlockAsync() + { + var lockKey = $"{key}{_lockKeyPostfix}"; + if (string.IsNullOrEmpty(luaScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + return await (_database.LockReleaseAsync(lockKey, lockValue)).ConfigureAwait(false); + } + var keys = new RedisKey[] {lockKey}; + if (extraKeys != null) + { + keys = keys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + cancellationToken.ThrowIfCancellationRequested(); + + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false); + return (bool) result[0]; + } + } + + /// + /// Tries to unlock the given keys. + /// + /// The keys to unlock. + /// The value that was used to lock the keys. + /// The lua script to unlock the keys. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// A cancellation token that can be used to cancel the work + /// How many keys were unlocked. + public Task UnlockManyAsync(string[] keys, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (luaScript == null) + { + throw new ArgumentNullException(nameof(luaScript)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return InternalUnlockManyAsync(); + async Task InternalUnlockManyAsync() + { + + var lockKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + } + if (extraKeys != null) + { + lockKeys = lockKeys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + cancellationToken.ThrowIfCancellationRequested(); + + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false); + return (int) result[0]; + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs new file mode 100644 index 00000000..2ca962f1 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// A Redis serializer that uses to serialize and deserialize objects. + /// + public class BinaryRedisSerializer : IRedisSerializer + { + /// + public RedisValue Serialize(object value) + { + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream()) + { + serializer.Serialize(stream, value); + return stream.ToArray(); + } + } + + /// + public object Deserialize(RedisValue value) + { + if (value.IsNull) + { + return null; + } + + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream(value)) + { + return serializer.Deserialize(stream); + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs new file mode 100644 index 00000000..ebca790b --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs @@ -0,0 +1,31 @@ +using System; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Cache configuration properties. + /// + public class CacheConfig + { + /// + /// Build a cache configuration. + /// + /// The redis configuration + /// The configured cache regions. + public CacheConfig(string configuration, RegionConfig[] regions) + { + Regions = regions; + Configuration = configuration; + } + + /// + /// The configured cache regions. + /// + public RegionConfig[] Regions { get; } + + /// + /// The StackExchange.Redis configuration string. + /// + public string Configuration { get; } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs new file mode 100644 index 00000000..fff9a65d --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Util; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Various methods to easier retrieve the configuration values. + /// + internal static class ConfigurationHelper + { + public static string GetString(string key, IDictionary properties, string defaultValue) + { + if (properties == null) + { + return defaultValue; + } + return properties.TryGetValue(key, out var value) ? value : defaultValue; + } + + public static bool GetBoolean(string key, IDictionary properties, bool defaultValue) + { + if (properties == null) + { + return defaultValue; + } + return properties.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue; + } + + public static int GetInteger(string key, IDictionary properties, int defaultValue) + { + if (properties == null) + { + return defaultValue; + } + return properties.TryGetValue(key, out var value) ? Convert.ToInt32(value) : defaultValue; + } + + public static TimeSpan GetTimeSpanFromSeconds(string key, IDictionary properties, TimeSpan defaultValue) + { + if (properties == null) + { + return defaultValue; + } + var seconds = properties.TryGetValue(key, out var value) + ? Convert.ToInt64(value) + : (long) defaultValue.TotalSeconds; + return TimeSpan.FromSeconds(seconds); + } + + public static TimeSpan GetTimeSpanFromMilliseconds(string key, IDictionary properties, TimeSpan defaultValue) + { + if (properties == null) + { + return defaultValue; + } + var milliseconds = properties.TryGetValue(key, out var value) + ? Convert.ToInt64(value) + : (long) defaultValue.TotalMilliseconds; + return TimeSpan.FromMilliseconds(milliseconds); + } + + public static System.Type GetSystemType(string key, IDictionary properties, System.Type defaultValue) + { + var typeName = GetString(key, properties, null); + if (typeName == null) + { + return defaultValue; + } + try + { + return System.Type.GetType(typeName, true); + } + catch (Exception e) + { + throw new CacheException($"Unable to aquire type '{typeName}' from the configuration property '{key}'", e); + } + } + + public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue, params object[] arguments) + { + var type = GetSystemType(key, properties, null); + if (type == null) + { + return defaultValue; + } + if (!typeof(TType).IsAssignableFrom(type)) + { + throw new CacheException($"Type '{type}' from the configuration property '{key}' is not assignable to '{typeof(TType)}'"); + } + + try + { + var argsByType = new object[] { properties }.Concat(arguments).ToDictionary(o => o.GetType()); + + // Try to find a constructor that we can instantiate + foreach (var ctor in type.GetConstructors().OrderByDescending(o => o.GetParameters().Length)) + { + var parameters = ctor.GetParameters(); + if (parameters.Any(o => !argsByType.ContainsKey(o.ParameterType))) + { + continue; + } + + var args = new object[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + args[i] = argsByType[parameters[i].ParameterType]; + } + + return (TType) ctor.Invoke(args); + } + throw new CacheException($"Unable to find a constructor for type '{type}' from configuration property '{key}'"); + } + catch (InvalidOperationException) + { + throw; + } + catch (Exception e) + { + throw new CacheException($"Unable to create an instance of type '{type}' from configuration property '{key}'.", e); + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs new file mode 100644 index 00000000..fa09517b --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs @@ -0,0 +1,18 @@ +using System; + +namespace NHibernate.Caches.StackExRedis +{ + /// + public class DefaultCacheLockRetryDelayProvider : ICacheLockRetryDelayProvider + { + private readonly Random _random = new Random(); + + /// + public TimeSpan GetValue(TimeSpan minDelay, TimeSpan maxDelay) + { + var delay = _random.NextDouble() * (maxDelay.TotalMilliseconds - minDelay.TotalMilliseconds) + + minDelay.TotalMilliseconds; + return TimeSpan.FromMilliseconds(delay); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs new file mode 100644 index 00000000..2733ff75 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs @@ -0,0 +1,14 @@ +using System; + +namespace NHibernate.Caches.StackExRedis +{ + /// + public class DefaultCacheLockValueProvider : ICacheLockValueProvider + { + /// + public string GetValue() + { + return Guid.NewGuid().ToString(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs new file mode 100644 index 00000000..5bdb51ed --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + public class DefaultCacheRegionStrategyFactory : ICacheRegionStrategyFactory + { + /// + public AbstractRegionStrategy Create(ConnectionMultiplexer connectionMultiplexer, + RedisCacheRegionConfiguration configuration, IDictionary properties) + { + return (AbstractRegionStrategy) Activator.CreateInstance(configuration.RegionStrategy, connectionMultiplexer, + configuration, properties); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs new file mode 100644 index 00000000..ad568805 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -0,0 +1,403 @@ +using System.Collections.Generic; +using NHibernate.Cache; +using StackExchange.Redis; +using static NHibernate.Caches.StackExRedis.ConfigurationHelper; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// The default region strategy. This strategy uses a special key that contains the region current version number which is appended + /// after the region prefix. Each time a clear operation is performed the version number is increased and an event is send to all + /// clients so that they can update thier local versions. Event if the event was not sent to all clients, each operation has a + /// version check in order to prevent working with stale data. + /// + public partial class DefaultRegionStrategy : AbstractRegionStrategy + { + private const string InvalidVersionMessage = "Invalid version"; + + private static readonly string CheckVersionCode = $@" + local version = redis.call('get', KEYS[#KEYS]) + if version ~= ARGV[#ARGV] then + return redis.error_reply('{InvalidVersionMessage}') + end"; + + private const string UpdateVersionLuaScript = @" + local version = redis.call('incr', KEYS[1]) + if version > tonumber(ARGV[1]) then + version = 1 + redis.call('set', KEYS[1], version) + end + return version"; + + private const string InitializeVersionLuaScript = @" + if redis.call('exists', KEYS[1]) == 1 then + return redis.call('get', KEYS[1]) + else + redis.call('set', KEYS[1], 1) + return 1 + end"; + + private static readonly string GetLuaScript = $@" + {CheckVersionCode} + local value = redis.call('get', KEYS[1]) + if value ~= nil and ARGV[1] == '1' then + redis.call('pexpire', KEYS[1], ARGV[2]) + end + return value"; + + private static readonly string GetManyLuaScript = $@" + {CheckVersionCode} + local values = {{}} + local sliding = ARGV[#ARGV-2] + local expirationMs = ARGV[#ARGV-1] + for i=1,#KEYS-1 do + local value = redis.call('get', KEYS[i]) + if value ~= nil and sliding == '1' then + redis.call('pexpire', KEYS[i], expirationMs) + end + values[i] = value + end + return values"; + + private static readonly string PutLuaScript = $@" + {CheckVersionCode} + return redis.call('set', KEYS[1], ARGV[1], 'px', ARGV[3])"; + + private static readonly string PutManyLuaScript = $@" + {CheckVersionCode} + local expirationMs = ARGV[#ARGV-1] + for i=1,#KEYS-1 do + redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) + end"; + + private static readonly string RemoveLuaScript = $@" + {CheckVersionCode} + return redis.call('del', KEYS[1])"; + + private static readonly string RemoveManyLuaScript = $@" + {CheckVersionCode} + local removedKeys = 0 + for i=1,#KEYS-1 do + removedKeys = removedKeys + redis.call('del', KEYS[i]) + end + return removedKeys"; + + private static readonly string LockLuaScript = $@" + {CheckVersionCode} + if redis.call('set', KEYS[1], ARGV[1], 'nx', 'px', ARGV[2]) == false then + return 0 + else + return 1 + end"; + + private static readonly string LockManyLuaScript = $@" + {CheckVersionCode} + local lockValue = ARGV[#ARGV-2] + local expirationMs = ARGV[#ARGV-1] + local lockedKeys = {{}} + local lockedKeyIndex = 1 + local locked = true + for i=1,#KEYS-1 do + if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then + locked = 0 + break + else + lockedKeys[lockedKeyIndex] = KEYS[i] + lockedKeyIndex = lockedKeyIndex + 1 + end + end + if locked == true then + return 1 + else + for i=1,#lockedKeys do + redis.call('del', lockedKeys[i]) + end + return 0 + end"; + + private static readonly string UnlockLuaScript = $@" + {CheckVersionCode} + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + else + return 0 + end"; + + private static readonly string UnlockManyLuaScript = $@" + {CheckVersionCode} + local lockValue = ARGV[1] + local removedKeys = 0 + for i=1,#KEYS-1 do + if redis.call('get', KEYS[i]) == lockValue then + removedKeys = removedKeys + redis.call('del', KEYS[i]) + end + end + return removedKeys"; + + private readonly RedisKey[] _regionKeyArray; + private readonly RedisValue[] _maxVersionNumber; + private RedisValue _currentVersion; + private RedisValue[] _currentVersionArray; + private readonly bool _usePubSub; + + /// + /// Default constructor. + /// + public DefaultRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + RedisCacheRegionConfiguration configuration, IDictionary properties) + : base(connectionMultiplexer, configuration, properties) + { + var maxVersion = GetInteger("cache.region_strategy.default.max_allowed_version", properties, 1000); + Log.Debug("Max allowed version for region {0}: {1}", RegionName, maxVersion); + + _usePubSub = GetBoolean("cache.region_strategy.default.use_pubsub", properties, true); + Log.Debug("Use pubsub for region {0}: {1}", RegionName, _usePubSub); + + _regionKeyArray = new RedisKey[] { RegionKey }; + _maxVersionNumber = new RedisValue[] { maxVersion }; + InitializeVersion(); + + if (_usePubSub) + { + ConnectionMultiplexer.GetSubscriber().SubscribeAsync(RegionKey, (channel, value) => + { + UpdateVersion(value); + }); + } + } + + /// + /// The version number that is currently used to retrieve/store keys. + /// + public long CurrentVersion => (long) _currentVersion; + + /// + protected override string GetScript => GetLuaScript; + + /// + protected override string GetManyScript => GetManyLuaScript; + + /// + protected override string PutScript => PutLuaScript; + + /// + protected override string PutManyScript => PutManyLuaScript; + + /// + protected override string RemoveScript => RemoveLuaScript; + + /// + protected override string RemoveManyScript => RemoveManyLuaScript; + + /// + protected override string LockScript => LockLuaScript; + + /// + protected override string LockManyScript => LockManyLuaScript; + + /// + protected override string UnlockScript => UnlockLuaScript; + + /// + protected override string UnlockManyScript => UnlockManyLuaScript; + + /// + public override object Get(object key) + { + try + { + return base.Get(key); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + return base.Get(key); + } + } + + /// + public override object[] GetMany(object[] keys) + { + try + { + return base.GetMany(keys); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + return base.GetMany(keys); + } + } + + /// + public override string Lock(object key) + { + try + { + return base.Lock(key); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + return base.Lock(key); + } + } + + /// + public override string LockMany(object[] keys) + { + try + { + return base.LockMany(keys); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + return base.LockMany(keys); + } + } + + /// + public override void Put(object key, object value) + { + try + { + base.Put(key, value); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // Here we don't know if the operation was executed after as successful lock, so + // the easiest solution is to skip the operation + } + } + + /// + public override void PutMany(object[] keys, object[] values) + { + try + { + base.PutMany(keys, values); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // Here we don't know if the operation was executed after as successful lock, so + // the easiest solution is to skip the operation + } + } + + /// + public override bool Remove(object key) + { + try + { + return base.Remove(key); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // There is no point removing the key in the new version. + return false; + } + } + + /// + public override long RemoveMany(object[] keys) + { + try + { + return base.RemoveMany(keys); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // There is no point removing the keys in the new version. + return 0L; + } + } + + /// + public override bool Unlock(object key) + { + try + { + return base.Unlock(key); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // If the lock was aquired in the old version we are unable to unlock the key. + return false; + } + } + + /// + public override int UnlockMany(object[] keys, string lockValue) + { + try + { + return base.UnlockMany(keys, lockValue); + } + catch (RedisServerException e) when (e.Message == InvalidVersionMessage) + { + InitializeVersion(); + // If the lock was aquired in the old version we are unable to unlock the keys. + return 0; + } + } + + /// + public override void Clear() + { + var results = (RedisValue[]) Database.ScriptEvaluate(UpdateVersionLuaScript, + _regionKeyArray, _maxVersionNumber); + var version = results[0]; + UpdateVersion(version); + if (_usePubSub) + { + ConnectionMultiplexer.GetSubscriber().Publish(RegionKey, version); + } + } + + /// + public override void Validate() + { + if (!ExpirationEnabled) + { + throw new CacheException($"Expiration must be greater than zero for cache region: '{RegionName}'"); + } + } + + /// + protected override RedisKey[] GetAdditionalKeys() + { + return _regionKeyArray; + } + + /// + protected override RedisValue[] GetAdditionalValues() + { + return _currentVersionArray; + } + + /// + protected override string GetCacheKey(object value) + { + return string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString(), "@", value.GetHashCode()); + } + + private void InitializeVersion() + { + var results = (RedisValue[]) Database.ScriptEvaluate(InitializeVersionLuaScript, _regionKeyArray); + var version = results[0]; + UpdateVersion(version); + } + + private void UpdateVersion(RedisValue version) + { + _currentVersion = version; + _currentVersionArray = new[] {version}; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs new file mode 100644 index 00000000..004671ed --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// A region strategy that have very simple read/write operations but does not support + /// operation. + /// + public partial class FastRegionStrategy : AbstractRegionStrategy + { + private const string SlidingGetLuaScript = @" + local value = redis.call('get', KEYS[1]) + if value ~= nil then + redis.call('pexpire', KEYS[1], ARGV[2]) + end + return value"; + + private const string SlidingGetManyLuaScript = @" + local expirationMs = ARGV[2] + local values = redis.call('MGET', unpack(KEYS)); + for i=1,#KEYS do + if values[i] ~= nil then + redis.call('pexpire', KEYS[i], expirationMs) + end + end + return values"; + + private const string ExpirationPutManyLuaScript = @" + local expirationMs = ARGV[#ARGV] + for i=1,#KEYS do + redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) + end"; + + private const string LockManyLuaScript = @" + local lockValue = ARGV[#ARGV-1] + local expirationMs = ARGV[#ARGV] + local lockedKeys = {} + local lockedKeyIndex = 1 + local locked = true + for i=1,#KEYS do + if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then + locked = false + break + else + lockedKeys[lockedKeyIndex] = KEYS[i] + lockedKeyIndex = lockedKeyIndex + 1 + end + end + if locked == true then + return 1 + else + for i=1,#lockedKeys do + redis.call('del', lockedKeys[i]) + end + return 0 + end"; + + private const string UnlockLuaScript = @" + if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) + else + return 0 + end"; + + private const string UnlockManyLuaScript = @" + local lockValue = ARGV[1] + local removedKeys = 0 + for i=1,#KEYS do + if redis.call('get', KEYS[i]) == lockValue then + removedKeys = removedKeys + redis.call('del', KEYS[i]) + end + end + return removedKeys"; + + + /// + /// Default constructor. + /// + public FastRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + RedisCacheRegionConfiguration configuration, IDictionary properties) + : base(connectionMultiplexer, configuration, properties) + { + if (ExpirationEnabled) + { + PutManyScript = ExpirationPutManyLuaScript; + if (UseSlidingExpiration) + { + GetScript = SlidingGetLuaScript; + GetManyScript = SlidingGetManyLuaScript; + } + } + } + + /// + protected override string GetScript { get; } + + /// + protected override string GetManyScript { get; } + + /// + protected override string PutManyScript { get; } + + /// + protected override string LockManyScript => LockManyLuaScript; + + /// + protected override string UnlockScript => UnlockLuaScript; + + /// + protected override string UnlockManyScript => UnlockManyLuaScript; + + /// + public override void Clear() + { + throw new NotSupportedException( + $"{nameof(Clear)} operation is not supported, if it cannot be avoided use {nameof(DefaultRegionStrategy)}."); + } + + /// + public override void Validate() + { + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs new file mode 100644 index 00000000..c4219f0f --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs @@ -0,0 +1,18 @@ +using System; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines a method to return a to be waited before the next lock attempt. + /// + public interface ICacheLockRetryDelayProvider + { + /// + /// Get a delay value between two values. + /// + /// The minimum delay value. + /// The maximum delay value. + /// + TimeSpan GetValue(TimeSpan minDelay, TimeSpan maxDelay); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs new file mode 100644 index 00000000..6fb5c080 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs @@ -0,0 +1,15 @@ +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines a method to get a unique value that will be used as a value when locking keys in + /// order to identify which instance locked the key. + /// + public interface ICacheLockValueProvider + { + /// + /// Gets a unique value that will be used for locking keys. + /// + /// A unique value. + string GetValue(); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs new file mode 100644 index 00000000..c320236b --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines a factory to create concrete instances. + /// + public interface ICacheRegionStrategyFactory + { + /// + /// Creates a concrete instance. + /// + /// The connection to be used. + /// The region configuration. + /// The properties from NHibernate configuration. + /// + AbstractRegionStrategy Create(ConnectionMultiplexer connectionMultiplexer, + RedisCacheRegionConfiguration configuration, IDictionary properties); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs new file mode 100644 index 00000000..29e957a4 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs @@ -0,0 +1,24 @@ +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines methods for serializing and deserializing objects that will be stored/retrieved for Redis. + /// + public interface IRedisSerializer + { + /// + /// Serialize the object to a to be stored into Redis. + /// + /// The object to serialize. + /// A serialized that can be stored into Redis. + RedisValue Serialize(object value); + + /// + /// Deserialize the that was retrieved from Redis. + /// + /// The value to deserialize. + /// The object that was serialized. + object Deserialize(RedisValue value); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj new file mode 100644 index 00000000..8a3c75a5 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj @@ -0,0 +1,33 @@ + + + + NHibernate.Caches.StackExRedis + NHibernate.Caches.StackExRedis + Redis cache provider for NHibernate using StackExchange.Redis. + + net461;netstandard2.0 + True + ..\..\NHibernate.Caches.snk + true + + + NETFX;$(DefineConstants) + + + + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs new file mode 100644 index 00000000..f6c12114 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Text; + +namespace NHibernate.Caches.StackExRedis +{ + internal partial class NHibernateTextWriter : TextWriter + { + private readonly INHibernateLogger _logger; + + public NHibernateTextWriter(INHibernateLogger logger) + { + _logger = logger; + } + + public override Encoding Encoding => Encoding.UTF8; + + public override void Write(string value) + { + if (value == null) + { + return; + } + + _logger.Debug(value); + } + + public override void WriteLine(string value) + { + if (value == null) + { + return; + } + _logger.Debug(value); + } + + public override void WriteLine() + { + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs new file mode 100644 index 00000000..68821f0b --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// A cache used to store objects into a Redis cache. + /// + public partial class RedisCache : ICache + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(RedisCache)); + + /// + /// Default constructor. + /// + public RedisCache(string regionName, AbstractRegionStrategy regionStrategy) + { + RegionName = regionName; + RegionStrategy = regionStrategy; + Timeout = Timestamper.OneMs * (int) RegionStrategy.LockTimeout.TotalMilliseconds; + } + + /// + public int Timeout { get; } + + /// + public string RegionName { get; } + + /// + /// The region strategy used by the cache. + /// + public AbstractRegionStrategy RegionStrategy { get; } + + /// + public object Get(object key) + { + return RegionStrategy.Get(key); + } + + /// + public object[] GetMany(object[] keys) + { + return RegionStrategy.GetMany(keys); + } + + /// + public void Put(object key, object value) + { + RegionStrategy.Put(key, value); + } + + /// + public void PutMany(object[] keys, object[] values) + { + RegionStrategy.PutMany(keys, values); + } + + /// + public void Remove(object key) + { + RegionStrategy.Remove(key); + } + + /// + public void RemoveMany(object[] keys) + { + RegionStrategy.RemoveMany(keys); + } + + /// + public void Clear() + { + RegionStrategy.Clear(); + } + + /// + public void Destroy() + { + // We cannot destroy the region cache as there may be other clients using it. + } + + /// + public void Lock(object key) + { + RegionStrategy.Lock(key); + } + + /// + public object LockMany(object[] keys) + { + return RegionStrategy.LockMany(keys); + } + + /// + public void Unlock(object key) + { + RegionStrategy.Unlock(key); + } + + /// + public void UnlockMany(object[] keys, object lockValue) + { + RegionStrategy.UnlockMany(keys, (string) lockValue); + } + + /// + public long NextTimestamp() + { + return Timestamper.Next(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs new file mode 100644 index 00000000..0523af28 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Global cache configuration. + /// + public class RedisCacheConfiguration + { + /// + /// The instance. + /// + public IRedisSerializer Serializer { get; set; } = new BinaryRedisSerializer(); + + /// + /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients + /// uses the same Redis database. + /// + public string CacheKeyPrefix { get; set; } = "NHibernate-Cache:"; + + /// + /// The name of the environment that will be prepended before each cache key in order to allow having + /// multiple environments on the same Redis database. + /// + public string EnvironmentName { get; set; } + + /// + /// The prefix that will be prepended before the region name when building a cache key. + /// + public string RegionPrefix { get; set; } + + /// + /// Should the expiration delay be sliding? + /// + /// for resetting a cached item expiration each time it is accessed. + public bool DefaultUseSlidingExpiration { get; set; } + + /// + /// The default expiration time for the keys to expire. + /// + public TimeSpan DefaultExpiration { get; set; } = TimeSpan.FromSeconds(300); + + /// + /// The default Redis database index. + /// + public int DefaultDatabase { get; set; } = -1; + + /// + /// The instance. + /// + public ICacheRegionStrategyFactory RegionStrategyFactory { get; set; } = new DefaultCacheRegionStrategyFactory(); + + /// + /// The default type. + /// + public System.Type DefaultRegionStrategy { get; set; } = typeof(DefaultRegionStrategy); + + /// + /// The configuration for locking keys. + /// + public RedisCacheLockConfiguration LockConfiguration { get; } = new RedisCacheLockConfiguration(); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs new file mode 100644 index 00000000..0b872973 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs @@ -0,0 +1,66 @@ +using System; +using System.Text; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Cache configuration for locking keys. + /// + public class RedisCacheLockConfiguration + { + /// + /// The timeout for a lock key to expire. + /// + public TimeSpan KeyTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// The postfix for the lock key. + /// + public string KeyPostfix { get; set; } = ":lock"; + + /// + /// The time limit to aquire the lock. + /// + public TimeSpan AquireTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// The number of retries for acquiring the lock. + /// + public int RetryTimes { get; set; } = 3; + + /// + /// The maximum delay before retrying to aquire the lock. + /// + public TimeSpan MaxRetryDelay { get; set; } = TimeSpan.FromMilliseconds(400); + + /// + /// The minumum delay before retrying to aquire the lock. + /// + public TimeSpan MinRetryDelay { get; set; } = TimeSpan.FromMilliseconds(10); + + /// + /// The instance. + /// + public ICacheLockValueProvider ValueProvider { get; set; } = new DefaultCacheLockValueProvider(); + + /// + /// The instance. + /// + public ICacheLockRetryDelayProvider RetryDelayProvider { get; set; } = new DefaultCacheLockRetryDelayProvider(); + + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("KeyTimeout={0}s", KeyTimeout.TotalSeconds); + sb.AppendFormat("KeyPostfix=({0})", KeyPostfix); + sb.AppendFormat("AquireTimeout={0}s", AquireTimeout.TotalSeconds); + sb.AppendFormat("RetryTimes={0}", RetryTimes); + sb.AppendFormat("MaxRetryDelay={0}ms", MaxRetryDelay.TotalMilliseconds); + sb.AppendFormat("MinRetryDelay={0}ms", MinRetryDelay.TotalMilliseconds); + sb.AppendFormat("ValueProvider={0}", ValueProvider); + sb.AppendFormat("RetryDelayProvider={0}", RetryDelayProvider); + return sb.ToString(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs new file mode 100644 index 00000000..3a5a1f95 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using NHibernate.Cache; +using NHibernate.Util; +using StackExchange.Redis; +using static NHibernate.Caches.StackExRedis.ConfigurationHelper; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Cache provider using the classes. + /// + public class RedisCacheProvider : ICacheProvider + { + private static readonly INHibernateLogger Log; + private static readonly Dictionary ConfiguredCacheRegions; + private static readonly CacheConfig ConfiguredCache; + + private ConnectionMultiplexer _connectionMultiplexer; + private RedisCacheConfiguration _defaultCacheConfiguration; + + static RedisCacheProvider() + { + Log = NHibernateLogger.For(typeof(RedisCacheProvider)); + ConfiguredCacheRegions = new Dictionary(); + + if (!(ConfigurationManager.GetSection("redis") is CacheConfig config)) + return; + + ConfiguredCache = config; + foreach (var cache in config.Regions) + { + ConfiguredCacheRegions.Add(cache.Region, cache); + } + } + + /// + public ICache BuildCache(string regionName, IDictionary properties) + { + if (regionName == null) + { + regionName = string.Empty; + } + + var regionConfiguration = ConfiguredCacheRegions.TryGetValue(regionName, out var regionConfig) + ? BuildRegionConfiguration(regionConfig, properties) + : BuildRegionConfiguration(regionName, properties); + + Log.Debug("Building cache: {0}", regionConfiguration.ToString()); + + return BuildCache(_defaultCacheConfiguration, regionConfiguration, properties); + } + + /// + public long NextTimestamp() + { + return Timestamper.Next(); + } + + /// + public void Start(IDictionary properties) + { + var configurationString = GetString(RedisEnvironment.Configuration, properties, ConfiguredCache?.Configuration); + if (string.IsNullOrEmpty(configurationString)) + { + throw new CacheException("The StackExchange.Redis configuration string was not provided."); + } + + Log.Debug("Starting with configuration string: {0}", configurationString); + + _defaultCacheConfiguration = BuildDefaultConfiguration(properties); + + Log.Debug("Default configuration: {0}", _defaultCacheConfiguration); + + TextWriter textWriter = Log.IsDebugEnabled() ? new NHibernateTextWriter(Log) : null; + Start(configurationString, properties, textWriter); + } + + /// + public virtual void Stop() + { + try + { + if (Log.IsDebugEnabled()) + { + Log.Debug("Releasing connection."); + } + _connectionMultiplexer.Dispose(); + _connectionMultiplexer = null; + } + catch (Exception e) + { + Log.Error(e, "An error occurred while releasing the connection."); + } + } + + /// + /// Callback to perform any necessary initialization of the underlying cache implementation + /// during ISessionFactory construction. + /// + /// The Redis configuration string. + /// NHibernate configuration settings. + /// The text writer. + protected virtual void Start(string configurationString, IDictionary properties, TextWriter textWriter) + { + var configuration = ConfigurationOptions.Parse(configurationString); + _connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); + _connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting + } + + /// + /// Builds the cache. + /// + /// The default cache configuration. + /// The region cache configuration. + /// NHibernate configuration settings. + /// The builded cache. + protected virtual ICache BuildCache(RedisCacheConfiguration defaultConfiguration, + RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) + { + var regionStrategy = + defaultConfiguration.RegionStrategyFactory.Create(_connectionMultiplexer, regionConfiguration, properties); + + regionStrategy.Validate(); + + return new RedisCache(regionConfiguration.RegionName, regionStrategy); + } + private RedisCacheRegionConfiguration BuildRegionConfiguration(string regionName, IDictionary properties) + { + return BuildRegionConfiguration(new RegionConfig(regionName), properties); + } + + private RedisCacheRegionConfiguration BuildRegionConfiguration(RegionConfig regionConfig, IDictionary properties) + { + var config = new RedisCacheRegionConfiguration(regionConfig.Region) + { + LockConfiguration = _defaultCacheConfiguration.LockConfiguration, + RegionPrefix = _defaultCacheConfiguration.RegionPrefix, + Serializer = _defaultCacheConfiguration.Serializer, + EnvironmentName = _defaultCacheConfiguration.EnvironmentName, + CacheKeyPrefix = _defaultCacheConfiguration.CacheKeyPrefix + }; + + config.Database = GetInteger("database", properties, + regionConfig.Database ?? GetInteger(RedisEnvironment.Database, properties, + _defaultCacheConfiguration.DefaultDatabase)); + Log.Debug("Database for region {0}: {1}", regionConfig.Region, config.Database); + + config.Expiration = GetTimeSpanFromSeconds("expiration", properties, + regionConfig.Expiration ?? GetTimeSpanFromSeconds(Cfg.Environment.CacheDefaultExpiration, properties, + _defaultCacheConfiguration.DefaultExpiration)); + Log.Debug("Expiration for region {0}: {1} seconds", regionConfig.Region, config.Expiration.TotalSeconds); + + config.RegionStrategy = GetSystemType("strategy", properties, + regionConfig.RegionStrategy ?? GetSystemType(RedisEnvironment.RegionStrategy, properties, + _defaultCacheConfiguration.DefaultRegionStrategy)); + Log.Debug("Region strategy for region {0}: {1}", regionConfig.Region, config.RegionStrategy); + + config.UseSlidingExpiration = GetBoolean("sliding", properties, + regionConfig.UseSlidingExpiration ?? GetBoolean(RedisEnvironment.UseSlidingExpiration, properties, + _defaultCacheConfiguration.DefaultUseSlidingExpiration)); + Log.Debug("Use sliding expiration for region {0}: {1}", regionConfig.Region, config.UseSlidingExpiration); + + return config; + } + + private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary properties) + { + var config = new RedisCacheConfiguration(); + + config.CacheKeyPrefix = GetString(RedisEnvironment.KeyPrefix, properties, config.CacheKeyPrefix); + Log.Debug("Cache key prefix: {0}", config.CacheKeyPrefix); + + config.EnvironmentName = GetString(RedisEnvironment.EnvironmentName, properties, config.EnvironmentName); + Log.Debug("Cache environment name: {0}", config.EnvironmentName); + + config.RegionPrefix = GetString(Cfg.Environment.CacheRegionPrefix, properties, config.RegionPrefix); + Log.Debug("Region prefix: {0}", config.RegionPrefix); + + config.Serializer = GetInstanceOfType(RedisEnvironment.Serializer, properties, config.Serializer); + Log.Debug("Serializer: {0}", config.Serializer); + + config.RegionStrategyFactory = GetInstanceOfType(RedisEnvironment.RegionStrategyFactory, properties, config.RegionStrategyFactory); + Log.Debug("Region strategy factory: {0}", config.RegionStrategyFactory); + + config.DefaultExpiration = GetTimeSpanFromSeconds(Cfg.Environment.CacheDefaultExpiration, properties, config.DefaultExpiration); + Log.Debug("Default expiration: {0} seconds", config.DefaultExpiration.TotalSeconds); + + config.DefaultDatabase = GetInteger(RedisEnvironment.Database, properties, config.DefaultDatabase); + Log.Debug("Default database: {0}", config.DefaultDatabase); + + config.DefaultRegionStrategy = GetSystemType(RedisEnvironment.RegionStrategy, properties, config.DefaultRegionStrategy); + Log.Debug("Default region strategy: {0}", config.DefaultRegionStrategy); + + config.DefaultUseSlidingExpiration = GetBoolean(RedisEnvironment.UseSlidingExpiration, properties, + config.DefaultUseSlidingExpiration); + Log.Debug("Default use sliding expiration: {0}", config.DefaultUseSlidingExpiration); + + var lockConfig = config.LockConfiguration; + lockConfig.KeyTimeout = GetTimeSpanFromSeconds(RedisEnvironment.LockKeyTimeout, properties, lockConfig.KeyTimeout); + Log.Debug("Lock key timeout: {0} seconds", lockConfig.KeyTimeout.TotalSeconds); + + lockConfig.AquireTimeout = GetTimeSpanFromSeconds(RedisEnvironment.LockAquireTimeout, properties, lockConfig.AquireTimeout); + Log.Debug("Lock aquire timeout: {0} seconds", lockConfig.AquireTimeout.TotalSeconds); + + lockConfig.RetryTimes = GetInteger(RedisEnvironment.LockRetryTimes, properties, lockConfig.RetryTimes); + Log.Debug("Lock retry times: {0}", lockConfig.RetryTimes); + + lockConfig.MaxRetryDelay = GetTimeSpanFromMilliseconds(RedisEnvironment.LockMaxRetryDelay, properties, lockConfig.MaxRetryDelay); + Log.Debug("Lock max retry delay: {0} milliseconds", lockConfig.MaxRetryDelay.TotalMilliseconds); + + lockConfig.MinRetryDelay = GetTimeSpanFromMilliseconds(RedisEnvironment.LockMinRetryDelay, properties, lockConfig.MinRetryDelay); + Log.Debug("Lock min retry delay: {0} milliseconds", lockConfig.MinRetryDelay.TotalMilliseconds); + + lockConfig.ValueProvider = GetInstanceOfType(RedisEnvironment.LockValueProvider, properties, lockConfig.ValueProvider); + Log.Debug("Lock value provider: {0}", lockConfig.ValueProvider); + + lockConfig.RetryDelayProvider = GetInstanceOfType(RedisEnvironment.LockRetryDelayProvider, properties, lockConfig.RetryDelayProvider); + Log.Debug("Lock retry delay provider: {0}", lockConfig.RetryDelayProvider); + + lockConfig.KeyPostfix = GetString(RedisEnvironment.LockKeyPostfix, properties, lockConfig.KeyPostfix); + Log.Debug("Lock key postfix: {0}", lockConfig.KeyPostfix); + + return config; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs new file mode 100644 index 00000000..d4720a62 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Cache configuration for a region. + /// + public class RedisCacheRegionConfiguration + { + /// + /// Creates a cache region configuration. + /// + /// The name of the region. + public RedisCacheRegionConfiguration(string regionName) + { + RegionName = regionName; + } + + /// + /// The key representing the region that is composed of , + /// , and . + /// + public string RegionKey => $"{CacheKeyPrefix}{EnvironmentName}{RegionPrefix}{RegionName}"; + + /// + /// The region name. + /// + public string RegionName { get; } + + /// + /// The name of the environment that will be prepended before each cache key in order to allow having + /// multiple environments on the same Redis database. + /// + public string EnvironmentName { get; set; } + + /// + /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients + /// uses the same Redis database. + /// + public string CacheKeyPrefix { get; set; } + + /// + /// The expiration time for the keys to expire. + /// + public TimeSpan Expiration { get; set; } + + /// + /// The prefix that will be prepended before the region name when building a cache key. + /// + public string RegionPrefix { get; set; } + + /// + /// The Redis database index. + /// + public int Database { get; set; } + + /// + /// Whether the expiration is sliding or not. + /// + public bool UseSlidingExpiration { get; set; } + + /// + /// The type. + /// + public System.Type RegionStrategy { get; set; } + + /// + /// The to be used. + /// + public IRedisSerializer Serializer { get; set; } + + /// + /// The configuration for locking keys. + /// + public RedisCacheLockConfiguration LockConfiguration { get; set; } + + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("RegionName={0}", RegionName); + sb.AppendFormat("EnvironmentName={0}", EnvironmentName); + sb.AppendFormat("CacheKeyPrefix={0}", CacheKeyPrefix); + sb.AppendFormat("Expiration={0}s", Expiration.TotalSeconds); + sb.AppendFormat("Database={0}", Database); + sb.AppendFormat("UseSlidingExpiration={0}", UseSlidingExpiration); + sb.AppendFormat("RegionStrategy={0}", RegionStrategy); + sb.AppendFormat("Serializer={0}", Serializer); + sb.AppendFormat("LockConfiguration=({0})", LockConfiguration); + return sb.ToString(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs new file mode 100644 index 00000000..e3dd746d --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs @@ -0,0 +1,92 @@ +using NHibernate.Cfg; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// An extension of NHibernate that provides configuration for the Redis cache. + /// + public static class RedisEnvironment + { + /// + /// The StackExchange.Redis configuration string. + /// + public const string Configuration = "cache.configuration"; + + /// + /// The Redis database index. + /// + public const string Database = "cache.database"; + + /// + /// The name of the environment that will be prepended before each cache key in order to allow having + /// multiple environments on the same Redis database. + /// + public const string EnvironmentName = "cache.environment_name"; + + /// + /// The assembly qualified name of the serializer. + /// + public const string Serializer = "cache.serializer"; + + /// + /// The assembly qualified name of the region strategy. + /// + public const string RegionStrategy = "cache.region_strategy"; + + /// + /// The assembly qualified name of the region strategy factory. + /// + public const string RegionStrategyFactory = "cache.region_strategy_factory"; + + /// + /// Whether the expiration delay should be sliding. + /// + public const string UseSlidingExpiration = "cache.use_sliding_expiration"; + + /// + /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients + /// uses the same Redis database. + /// + public const string KeyPrefix = "cache.key_prefix"; + + /// + /// The timeout for a lock key to expire in seconds. + /// + public const string LockKeyTimeout = "cache.lock.key_timeout"; + + /// + /// The time limit to aquire the lock in seconds. + /// + public const string LockAquireTimeout = "cache.lock.aquire_timeout"; + + /// + /// The number of retries for acquiring the lock. + /// + public const string LockRetryTimes = "cache.lock.retry_times"; + + /// + /// The maximum delay before retrying to aquire the lock in milliseconds. + /// + public const string LockMaxRetryDelay = "cache.lock.max_retry_delay"; + + /// + /// The minumum delay before retrying to aquire the lock in milliseconds. + /// + public const string LockMinRetryDelay = "cache.lock.min_retry_delay"; + + /// + /// The assembly qualified name of the lock value provider. + /// + public const string LockValueProvider = "cache.lock.value_provider"; + + /// + /// The assembly qualified name of the lock retry delay provider. + /// + public const string LockRetryDelayProvider = "cache.lock.retry_delay_provider"; + + /// + /// The postfix for the lock key. + /// + public const string LockKeyPostfix = "cache.lock.key_postfix"; + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs new file mode 100644 index 00000000..f9e2ab58 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs @@ -0,0 +1,244 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using NHibernate.Cache; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Provides a mechanism for locking and unlocking one or many keys. + /// + internal partial class RedisKeyLocker + { + private readonly string _regionName; + private readonly string _lockKeyPostfix; + private readonly TimeSpan _lockTimeout; + private readonly double _aquireLockTimeout; + private readonly int _retryTimes; + private readonly TimeSpan _maxRetryDelay; + private readonly TimeSpan _minRetryDelay; + private readonly ICacheLockRetryDelayProvider _lockRetryDelayProvider; + private readonly ICacheLockValueProvider _lockValueProvider; + private readonly IDatabase _database; + + /// + /// Default constructor. + /// + /// The region name. + /// The Redis database. + /// The lock configuration. + public RedisKeyLocker( + string regionName, + IDatabase database, + RedisCacheLockConfiguration configuration) + { + _regionName = regionName; + _database = database; + _lockKeyPostfix = configuration.KeyPostfix; + _lockTimeout = configuration.KeyTimeout; + _aquireLockTimeout = configuration.AquireTimeout.TotalMilliseconds; + _retryTimes = configuration.RetryTimes; + _maxRetryDelay = configuration.MaxRetryDelay; + _minRetryDelay = configuration.MinRetryDelay; + _lockRetryDelayProvider = configuration.RetryDelayProvider; + _lockValueProvider = configuration.ValueProvider; + } + + /// + /// Tries to lock the given key. + /// + /// The key to lock. + /// The lua script to lock the key. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// The lock value used to lock the key. + /// Thrown if the lock was not aquired. + public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + var lockKey = $"{key}{_lockKeyPostfix}"; + var totalAttempts = 0; + var lockTimer = new Stopwatch(); + lockTimer.Restart(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); + Thread.Sleep(retryDelay); + } + var lockValue = _lockValueProvider.GetValue(); + if (!string.IsNullOrEmpty(luaScript)) + { + var keys = new RedisKey[] {lockKey}; + if (extraKeys != null) + { + keys = keys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + var result = (RedisValue[]) _database.ScriptEvaluate(luaScript, keys, values); + if ((bool) result[0]) + { + return lockValue; + } + } + else if (_database.LockTake(lockKey, lockValue, _lockTimeout)) + { + return lockValue; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + + throw new CacheException("Unable to acquire cache lock: " + + $"region='{_regionName}', " + + $"key='{key}', " + + $"total attempts='{totalAttempts}', " + + $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + } + + /// + /// Tries to lock the given keys. + /// + /// The keys to lock. + /// The lua script to lock the keys. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// The lock value used to lock the keys. + /// Thrown if the lock was not aquired. + public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (luaScript == null) + { + throw new ArgumentNullException(nameof(luaScript)); + } + + var lockKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + } + var totalAttempts = 0; + var lockTimer = new Stopwatch(); + lockTimer.Restart(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); + Thread.Sleep(retryDelay); + } + var lockValue = _lockValueProvider.GetValue(); + if (extraKeys != null) + { + lockKeys = lockKeys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + var result = (RedisValue[]) _database.ScriptEvaluate(luaScript, lockKeys, values); + if ((bool) result[0]) + { + return lockValue; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + + throw new CacheException("Unable to acquire cache lock: " + + $"region='{_regionName}', " + + $"keys='{string.Join(",", lockKeys)}', " + + $"total attempts='{totalAttempts}', " + + $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + } + + /// + /// Tries to unlock the given key. + /// + /// The key to unlock. + /// The value that was used to lock the key. + /// The lua script to unlock the key. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// Whether the key was unlocked. + public bool Unlock(string key, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + var lockKey = $"{key}{_lockKeyPostfix}"; + if (string.IsNullOrEmpty(luaScript)) + { + return _database.LockRelease(lockKey, lockValue); + } + var keys = new RedisKey[] {lockKey}; + if (extraKeys != null) + { + keys = keys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + + var result = (RedisValue[]) _database.ScriptEvaluate(luaScript, keys, values); + return (bool) result[0]; + } + + /// + /// Tries to unlock the given keys. + /// + /// The keys to unlock. + /// The value that was used to lock the keys. + /// The lua script to unlock the keys. + /// The extra keys that will be provided to the + /// The extra values that will be provided to the + /// How many keys were unlocked. + public int UnlockMany(string[] keys, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) + { + if (keys == null) + { + throw new ArgumentNullException(nameof(keys)); + } + if (luaScript == null) + { + throw new ArgumentNullException(nameof(luaScript)); + } + + var lockKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + } + if (extraKeys != null) + { + lockKeys = lockKeys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + + var result = (RedisValue[]) _database.ScriptEvaluate(luaScript, lockKeys, values); + return (int) result[0]; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs new file mode 100644 index 00000000..805081dd --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Xml; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Configuration file provider. + /// + public class RedisSectionHandler : IConfigurationSectionHandler + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(RedisSectionHandler)); + + #region IConfigurationSectionHandler Members + + /// + /// A object. + public object Create(object parent, object configContext, XmlNode section) + { + var configuration = section.Attributes?["configuration"]?.Value; + var regions = new List(); + + var nodes = section.SelectNodes("cache"); + foreach (XmlNode node in nodes) + { + var region = node.Attributes?["region"]?.Value; + if (region != null) + { + regions.Add(new RegionConfig( + region, + GetTimespanFromSeconds(node, "expiration"), + GetBoolean(node, "sliding"), + GetInteger(node, "database"), + GetType(node, "strategy") + )); + } + else + { + Log.Warn("Found a cache region node lacking a region name: ignored. Node: {0}", + node.OuterXml); + } + } + return new CacheConfig(configuration, regions.ToArray()); + } + + private static TimeSpan? GetTimespanFromSeconds(XmlNode node, string attributeName) + { + var seconds = GetInteger(node, attributeName); + if (!seconds.HasValue) + { + return null; + } + return TimeSpan.FromSeconds(seconds.Value); + } + + private static bool? GetBoolean(XmlNode node, string attributeName) + { + var value = node.Attributes?[attributeName]?.Value; + if (string.IsNullOrEmpty(value)) + { + return null; + } + if (bool.TryParse(value, out var boolean)) + { + return boolean; + } + + Log.Warn("Invalid value for boolean attribute {0}: ignored. Node: {1}", attributeName, node.OuterXml); + return null; + } + + private static System.Type GetType(XmlNode node, string attributeName) + { + var value = node.Attributes?[attributeName]?.Value; + if (string.IsNullOrEmpty(value)) + { + return null; + } + try + { + return System.Type.GetType(value, true); + } + catch (Exception e) + { + Log.Warn("Unable to aquire type for attribute {0}: ignored. Node: {1}, Exception: {2}", attributeName, node.OuterXml, e); + return null; + } + } + + private static int? GetInteger(XmlNode node, string attributeName) + { + var value = node.Attributes?[attributeName]?.Value; + if (string.IsNullOrEmpty(value)) + { + return null; + } + if (int.TryParse(value, out var number)) + { + return number; + } + + Log.Warn("Invalid value for integer attribute {0}: ignored. Node: {1}", attributeName, node.OuterXml); + return null; + } + + #endregion + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs new file mode 100644 index 00000000..78571b34 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Region configuration properties. + /// + public class RegionConfig + { + /// + /// Build a cache region configuration. + /// + /// The configured cache region. + public RegionConfig(string region) : this(region, null, null, null, null) + { + } + + /// + /// Build a cache region configuration. + /// + /// The configured cache region. + /// The expiration for the region. + /// Whether the expiration should be sliding or not. + /// The database for the region. + /// The strategy for the region. + public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpiration, int? database, System.Type regionStrategy) + { + Region = region; + Expiration = expiration; + UseSlidingExpiration = useSlidingExpiration; + Database = database; + RegionStrategy = regionStrategy; + } + + /// + /// The region name. + /// + public string Region { get; } + + /// + /// The expiration time for the keys to expire. + /// + public TimeSpan? Expiration { get; } + + /// + /// Whether the expiration is sliding or not. + /// + public bool? UseSlidingExpiration { get; } + + /// + /// The Redis database index. + /// + public int? Database { get; } + + /// + /// The type. + /// + public System.Type RegionStrategy { get; } + } +} From fe9b9e0d8be25334e09aa9b572ab3773d6c53671 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 19 Jun 2018 21:22:33 +0200 Subject: [PATCH 02/24] Added cancellation tokens to the missing AbstractRegionStrategy members --- AsyncGenerator.yml | 6 ++++++ .../Async/Caches/DistributedRedisCache.cs | 2 +- .../Async/AbstractRegionStrategy.cs | 3 ++- .../Async/DefaultRegionStrategy.cs | 5 ++++- .../Async/FastRegionStrategy.cs | 3 ++- .../NHibernate.Caches.StackExRedis/Async/RedisCache.cs | 3 +-- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index 43b2d396..9663a994 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -62,6 +62,12 @@ name: Remove - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy name: RemoveMany + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: Unlock + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: UnlockMany + - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + name: Clear - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker name: Unlock - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs index 343a5d87..6f77fa7b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -60,7 +60,7 @@ public async Task ClearAsync(CancellationToken cancellationToken) { foreach (var strategy in _regionStrategies) { - await (strategy.ClearAsync()); + await (strategy.ClearAsync(cancellationToken)); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index 60157121..38134260 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -421,6 +421,7 @@ public virtual Task UnlockManyAsync(object[] keys, string lockValue, Cancel /// /// Clears all the keys from the region. /// - public abstract Task ClearAsync(); + /// A cancellation token that can be used to cancel the work + public abstract Task ClearAsync(CancellationToken cancellationToken); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs index 021b0952..6ab77144 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -187,14 +187,17 @@ public override async Task UnlockManyAsync(object[] keys, string lockValue, } /// - public override async Task ClearAsync() + public override async Task ClearAsync(CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(UpdateVersionLuaScript, _regionKeyArray, _maxVersionNumber)).ConfigureAwait(false); var version = results[0]; UpdateVersion(version); if (_usePubSub) { + cancellationToken.ThrowIfCancellationRequested(); await (ConnectionMultiplexer.GetSubscriber().PublishAsync(RegionKey, version)).ConfigureAwait(false); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs index 497d4565..6ee99103 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs @@ -17,11 +17,12 @@ namespace NHibernate.Caches.StackExRedis { + using System.Threading; public partial class FastRegionStrategy : AbstractRegionStrategy { /// - public override Task ClearAsync() + public override Task ClearAsync(CancellationToken cancellationToken) { throw new NotSupportedException( $"{nameof(ClearAsync)} operation is not supported, if it cannot be avoided use {nameof(DefaultRegionStrategy)}."); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs index 086f80c3..176b4856 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs @@ -88,8 +88,7 @@ public Task ClearAsync(CancellationToken cancellationToken) { return Task.FromCanceled(cancellationToken); } - cancellationToken.ThrowIfCancellationRequested(); - return RegionStrategy.ClearAsync(); + return RegionStrategy.ClearAsync(cancellationToken); } /// From a0f070b985475009394bb2dd1fbab4d02d56e254 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 19 Jun 2018 22:01:22 +0200 Subject: [PATCH 03/24] Used IObjectsFactory for creating type instances. --- .../ConfigurationHelper.cs | 38 +------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs index fff9a65d..ea4af04f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Cache; -using NHibernate.Util; namespace NHibernate.Caches.StackExRedis { @@ -81,7 +77,7 @@ public static System.Type GetSystemType(string key, IDictionary } } - public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue, params object[] arguments) + public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue) { var type = GetSystemType(key, properties, null); if (type == null) @@ -93,37 +89,7 @@ public static TType GetInstanceOfType(string key, IDictionary o.GetType()); - - // Try to find a constructor that we can instantiate - foreach (var ctor in type.GetConstructors().OrderByDescending(o => o.GetParameters().Length)) - { - var parameters = ctor.GetParameters(); - if (parameters.Any(o => !argsByType.ContainsKey(o.ParameterType))) - { - continue; - } - - var args = new object[parameters.Length]; - for (var i = 0; i < parameters.Length; i++) - { - args[i] = argsByType[parameters[i].ParameterType]; - } - - return (TType) ctor.Invoke(args); - } - throw new CacheException($"Unable to find a constructor for type '{type}' from configuration property '{key}'"); - } - catch (InvalidOperationException) - { - throw; - } - catch (Exception e) - { - throw new CacheException($"Unable to create an instance of type '{type}' from configuration property '{key}'.", e); - } + return (TType) Cfg.Environment.BytecodeProvider.ObjectsFactory.CreateInstance(type); } } } From 97238c4912b5899569c774e4c848248c7152bda9 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 19 Jun 2018 22:15:12 +0200 Subject: [PATCH 04/24] Fixed typos --- .../Async/RedisCacheDefaultStrategyFixture.cs | 2 +- .../Async/RedisCacheFixture.cs | 2 +- .../RedisCacheDefaultStrategyFixture.cs | 4 ++-- .../RedisCacheFixture.cs | 2 +- .../RedisCacheProviderFixture.cs | 12 +++++----- .../AbstractRegionStrategy.cs | 8 +++---- .../Async/AbstractRegionStrategy.cs | 4 ++-- .../Async/DefaultRegionStrategy.cs | 4 ++-- .../Async/RedisKeyLocker.cs | 16 ++++++------- .../ConfigurationHelper.cs | 2 +- .../DefaultRegionStrategy.cs | 6 ++--- .../RedisCacheLockConfiguration.cs | 16 ++++++------- .../RedisCacheProvider.cs | 8 +++---- .../RedisEnvironment.cs | 12 +++++----- .../RedisKeyLocker.cs | 24 +++++++++---------- .../RedisSectionHandler.cs | 2 +- 16 files changed, 62 insertions(+), 62 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs index de87c01c..105c3bca 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs @@ -37,7 +37,7 @@ public async Task TestMaxAllowedVersionAsync() await (cache.ClearAsync(CancellationToken.None)); - Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "The version was not reset to 1"); + Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "the version was not reset to 1"); } [Test] diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 3f59e572..711a05df 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -146,7 +146,7 @@ public async Task TestLockUnlockManyAsync() // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. var lockValue = await (cache.LockManyAsync(keys.Take(5).ToArray(), CancellationToken.None)); - Assert.ThrowsAsync(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "The fifth key should be locked"); + Assert.ThrowsAsync(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "the fifth key should be locked"); Assert.DoesNotThrowAsync(async () => { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs index 11e1a186..7e061984 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs @@ -19,7 +19,7 @@ public void TestNoExpiration() var props = GetDefaultProperties(); props["expiration"] = "0"; Assert.Throws(() => DefaultProvider.BuildCache(DefaultRegion, props), - "Default region strategy should not allow to have no expiration"); + "default region strategy should not allow to have no expiration"); } [Test] @@ -36,7 +36,7 @@ public void TestMaxAllowedVersion() cache.Clear(); - Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "The version was not reset to 1"); + Assert.That(strategy.CurrentVersion, Is.EqualTo(1L), "the version was not reset to 1"); } [Test] diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index 13929b49..a16ba405 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -143,7 +143,7 @@ public void TestLockUnlockMany() // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. var lockValue = cache.LockMany(keys.Take(5).ToArray()); - Assert.Throws(() => cache.LockMany(keys.Skip(4).ToArray()), "The fifth key should be locked"); + Assert.Throws(() => cache.LockMany(keys.Skip(4).ToArray()), "the fifth key should be locked"); Assert.DoesNotThrow(() => { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs index 4654ceff..870b6a71 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -30,23 +30,23 @@ public void TestExpiration() var strategy = cache.RegionStrategy; Assert.That(strategy, Is.Not.Null, "strategy was not set for the pre-configured foo cache"); - Assert.That(strategy, Is.TypeOf(), "Unexpected strategy type for foo region"); - Assert.That(strategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "Unexpected expiration value for foo region"); + Assert.That(strategy, Is.TypeOf(), "unexpected strategy type for foo region"); + Assert.That(strategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "unexpected expiration value for foo region"); cache = (RedisCache) DefaultProvider.BuildCache("noExplicitExpiration", null); Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(300)), - "Unexpected expiration value for noExplicitExpiration region"); - Assert.That(cache.RegionStrategy.UseSlidingExpiration, Is.True, "Unexpected sliding value for noExplicitExpiration region"); + "unexpected expiration value for noExplicitExpiration region"); + Assert.That(cache.RegionStrategy.UseSlidingExpiration, Is.True, "unexpected sliding value for noExplicitExpiration region"); cache = (RedisCache) DefaultProvider .BuildCache("noExplicitExpiration", new Dictionary { { "expiration", "100" } }); Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(100)), - "Unexpected expiration value for noExplicitExpiration region with default expiration"); + "unexpected expiration value for noExplicitExpiration region with default expiration"); cache = (RedisCache) DefaultProvider .BuildCache("noExplicitExpiration", new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } }); Assert.That(cache.RegionStrategy.Expiration, Is.EqualTo(TimeSpan.FromSeconds(50)), - "Unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); + "unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index d7d24be8..0f1966f9 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -47,7 +47,7 @@ public abstract partial class AbstractRegionStrategy /// even if an exception occurs which we are also abusing in order to avoid having a special mechanism to /// clear the dictionary in order to prevent memory leaks. /// - private readonly ConcurrentDictionary _aquiredKeyLocks = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _acquiredKeyLocks = new ConcurrentDictionary(); /// @@ -150,7 +150,7 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, public bool ExpirationEnabled => Expiration != TimeSpan.Zero; /// - /// The timeout of an aquired lock. + /// The timeout of an acquired lock. /// public TimeSpan LockTimeout { get; } @@ -372,7 +372,7 @@ public virtual string Lock(object key) var cacheKey = GetCacheKey(key); var lockValue = _keyLocker.Lock(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues()); - _aquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => + _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => { Log.Warn( $"Calling {nameof(Lock)} method for key:'{cacheKey}' that was already locked. " + @@ -419,7 +419,7 @@ public virtual bool Unlock(object key) } var cacheKey = GetCacheKey(key); - if (!_aquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) + if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) { Log.Warn( $"Calling {nameof(Unlock)} method for key:'{cacheKey}' that was not locked with {nameof(Lock)} method before."); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index 38134260..fec5f481 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -300,7 +300,7 @@ async Task InternalLockAsync() var cacheKey = GetCacheKey(key); var lockValue = await (_keyLocker.LockAsync(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); - _aquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => + _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => { Log.Warn( $"Calling {nameof(LockAsync)} method for key:'{cacheKey}' that was already locked. " + @@ -367,7 +367,7 @@ public virtual Task UnlockAsync(object key, CancellationToken cancellation { var cacheKey = GetCacheKey(key); - if (!_aquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) + if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) { Log.Warn( $"Calling {nameof(UnlockAsync)} method for key:'{cacheKey}' that was not locked with {nameof(LockAsync)} method before."); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs index 6ab77144..a1aac360 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -164,7 +164,7 @@ public override async Task UnlockAsync(object key, CancellationToken cance { cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); - // If the lock was aquired in the old version we are unable to unlock the key. + // If the lock was acquired in the old version we are unable to unlock the key. return false; } } @@ -181,7 +181,7 @@ public override async Task UnlockManyAsync(object[] keys, string lockValue, { cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); - // If the lock was aquired in the old version we are unable to unlock the keys. + // If the lock was acquired in the old version we are unable to unlock the keys. return 0; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs index 440b9710..d8816cce 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs @@ -30,7 +30,7 @@ internal partial class RedisKeyLocker /// The extra values that will be provided to the /// A cancellation token that can be used to cancel the work /// The lock value used to lock the key. - /// Thrown if the lock was not aquired. + /// Thrown if the lock was not acquired. public Task LockAsync(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) { if (key == null) @@ -44,7 +44,7 @@ public Task LockAsync(string key, string luaScript, RedisKey[] extraKeys return InternalLockAsync(); async Task InternalLockAsync() { - var lockKey = $"{key}{_lockKeyPostfix}"; + var lockKey = $"{key}{_lockKeySuffix}"; var totalAttempts = 0; var lockTimer = new Stopwatch(); lockTimer.Restart(); @@ -81,7 +81,7 @@ async Task InternalLockAsync() } totalAttempts++; - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); throw new CacheException("Unable to acquire cache lock: " + $"region='{_regionName}', " + @@ -100,7 +100,7 @@ async Task InternalLockAsync() /// The extra values that will be provided to the /// A cancellation token that can be used to cancel the work /// The lock value used to lock the keys. - /// Thrown if the lock was not aquired. + /// Thrown if the lock was not acquired. public Task LockManyAsync(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken) { if (keys == null) @@ -122,7 +122,7 @@ async Task InternalLockManyAsync() var lockKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { - lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } var totalAttempts = 0; var lockTimer = new Stopwatch(); @@ -152,7 +152,7 @@ async Task InternalLockManyAsync() } totalAttempts++; - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); throw new CacheException("Unable to acquire cache lock: " + $"region='{_regionName}', " + @@ -185,7 +185,7 @@ public Task UnlockAsync(string key, string lockValue, string luaScript, Re return InternalUnlockAsync(); async Task InternalUnlockAsync() { - var lockKey = $"{key}{_lockKeyPostfix}"; + var lockKey = $"{key}{_lockKeySuffix}"; if (string.IsNullOrEmpty(luaScript)) { cancellationToken.ThrowIfCancellationRequested(); @@ -239,7 +239,7 @@ async Task InternalUnlockManyAsync() var lockKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { - lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } if (extraKeys != null) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs index ea4af04f..622fa60a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -73,7 +73,7 @@ public static System.Type GetSystemType(string key, IDictionary } catch (Exception e) { - throw new CacheException($"Unable to aquire type '{typeName}' from the configuration property '{key}'", e); + throw new CacheException($"Unable to acquire type '{typeName}' from the configuration property '{key}'", e); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index ad568805..0d4e5038 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -8,7 +8,7 @@ namespace NHibernate.Caches.StackExRedis /// /// The default region strategy. This strategy uses a special key that contains the region current version number which is appended /// after the region prefix. Each time a clear operation is performed the version number is increased and an event is send to all - /// clients so that they can update thier local versions. Event if the event was not sent to all clients, each operation has a + /// clients so that they can update thier local versions. Even if the event was not sent to all clients, each operation has a /// version check in order to prevent working with stale data. /// public partial class DefaultRegionStrategy : AbstractRegionStrategy @@ -327,7 +327,7 @@ public override bool Unlock(object key) catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { InitializeVersion(); - // If the lock was aquired in the old version we are unable to unlock the key. + // If the lock was acquired in the old version we are unable to unlock the key. return false; } } @@ -342,7 +342,7 @@ public override int UnlockMany(object[] keys, string lockValue) catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { InitializeVersion(); - // If the lock was aquired in the old version we are unable to unlock the keys. + // If the lock was acquired in the old version we are unable to unlock the keys. return 0; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs index 0b872973..f1f3743e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs @@ -14,14 +14,14 @@ public class RedisCacheLockConfiguration public TimeSpan KeyTimeout { get; set; } = TimeSpan.FromSeconds(5); /// - /// The postfix for the lock key. + /// The suffix for the lock key. /// - public string KeyPostfix { get; set; } = ":lock"; + public string KeySuffix { get; set; } = ":lock"; /// - /// The time limit to aquire the lock. + /// The time limit to acquire the lock. /// - public TimeSpan AquireTimeout { get; set; } = TimeSpan.FromSeconds(5); + public TimeSpan AcquireTimeout { get; set; } = TimeSpan.FromSeconds(5); /// /// The number of retries for acquiring the lock. @@ -29,12 +29,12 @@ public class RedisCacheLockConfiguration public int RetryTimes { get; set; } = 3; /// - /// The maximum delay before retrying to aquire the lock. + /// The maximum delay before retrying to acquire the lock. /// public TimeSpan MaxRetryDelay { get; set; } = TimeSpan.FromMilliseconds(400); /// - /// The minumum delay before retrying to aquire the lock. + /// The minumum delay before retrying to acquire the lock. /// public TimeSpan MinRetryDelay { get; set; } = TimeSpan.FromMilliseconds(10); @@ -53,8 +53,8 @@ public override string ToString() { var sb = new StringBuilder(); sb.AppendFormat("KeyTimeout={0}s", KeyTimeout.TotalSeconds); - sb.AppendFormat("KeyPostfix=({0})", KeyPostfix); - sb.AppendFormat("AquireTimeout={0}s", AquireTimeout.TotalSeconds); + sb.AppendFormat("KeySuffix=({0})", KeySuffix); + sb.AppendFormat("AcquireTimeout={0}s", AcquireTimeout.TotalSeconds); sb.AppendFormat("RetryTimes={0}", RetryTimes); sb.AppendFormat("MaxRetryDelay={0}ms", MaxRetryDelay.TotalMilliseconds); sb.AppendFormat("MinRetryDelay={0}ms", MinRetryDelay.TotalMilliseconds); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index 3a5a1f95..ba7a9d00 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -205,8 +205,8 @@ private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary - /// The time limit to aquire the lock in seconds. + /// The time limit to acquire the lock in seconds. /// - public const string LockAquireTimeout = "cache.lock.aquire_timeout"; + public const string LockAcquireTimeout = "cache.lock.acquire_timeout"; /// /// The number of retries for acquiring the lock. @@ -65,12 +65,12 @@ public static class RedisEnvironment public const string LockRetryTimes = "cache.lock.retry_times"; /// - /// The maximum delay before retrying to aquire the lock in milliseconds. + /// The maximum delay before retrying to acquire the lock in milliseconds. /// public const string LockMaxRetryDelay = "cache.lock.max_retry_delay"; /// - /// The minumum delay before retrying to aquire the lock in milliseconds. + /// The minumum delay before retrying to acquire the lock in milliseconds. /// public const string LockMinRetryDelay = "cache.lock.min_retry_delay"; @@ -85,8 +85,8 @@ public static class RedisEnvironment public const string LockRetryDelayProvider = "cache.lock.retry_delay_provider"; /// - /// The postfix for the lock key. + /// The suffix for the lock key. /// - public const string LockKeyPostfix = "cache.lock.key_postfix"; + public const string LockKeySuffix = "cache.lock.key_suffix"; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs index f9e2ab58..6a5ae0df 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs @@ -13,9 +13,9 @@ namespace NHibernate.Caches.StackExRedis internal partial class RedisKeyLocker { private readonly string _regionName; - private readonly string _lockKeyPostfix; + private readonly string _lockKeySuffix; private readonly TimeSpan _lockTimeout; - private readonly double _aquireLockTimeout; + private readonly double _acquireLockTimeout; private readonly int _retryTimes; private readonly TimeSpan _maxRetryDelay; private readonly TimeSpan _minRetryDelay; @@ -36,9 +36,9 @@ public RedisKeyLocker( { _regionName = regionName; _database = database; - _lockKeyPostfix = configuration.KeyPostfix; + _lockKeySuffix = configuration.KeySuffix; _lockTimeout = configuration.KeyTimeout; - _aquireLockTimeout = configuration.AquireTimeout.TotalMilliseconds; + _acquireLockTimeout = configuration.AcquireTimeout.TotalMilliseconds; _retryTimes = configuration.RetryTimes; _maxRetryDelay = configuration.MaxRetryDelay; _minRetryDelay = configuration.MinRetryDelay; @@ -54,14 +54,14 @@ public RedisKeyLocker( /// The extra keys that will be provided to the /// The extra values that will be provided to the /// The lock value used to lock the key. - /// Thrown if the lock was not aquired. + /// Thrown if the lock was not acquired. public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) { if (key == null) { throw new ArgumentNullException(nameof(key)); } - var lockKey = $"{key}{_lockKeyPostfix}"; + var lockKey = $"{key}{_lockKeySuffix}"; var totalAttempts = 0; var lockTimer = new Stopwatch(); lockTimer.Restart(); @@ -97,7 +97,7 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu } totalAttempts++; - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); throw new CacheException("Unable to acquire cache lock: " + $"region='{_regionName}', " + @@ -114,7 +114,7 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu /// The extra keys that will be provided to the /// The extra values that will be provided to the /// The lock value used to lock the keys. - /// Thrown if the lock was not aquired. + /// Thrown if the lock was not acquired. public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues) { if (keys == null) @@ -129,7 +129,7 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re var lockKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { - lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } var totalAttempts = 0; var lockTimer = new Stopwatch(); @@ -158,7 +158,7 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re } totalAttempts++; - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _aquireLockTimeout); + } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); throw new CacheException("Unable to acquire cache lock: " + $"region='{_regionName}', " + @@ -182,7 +182,7 @@ public bool Unlock(string key, string lockValue, string luaScript, RedisKey[] ex { throw new ArgumentNullException(nameof(key)); } - var lockKey = $"{key}{_lockKeyPostfix}"; + var lockKey = $"{key}{_lockKeySuffix}"; if (string.IsNullOrEmpty(luaScript)) { return _database.LockRelease(lockKey, lockValue); @@ -225,7 +225,7 @@ public int UnlockMany(string[] keys, string lockValue, string luaScript, RedisKe var lockKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { - lockKeys[i] = $"{keys[i]}{_lockKeyPostfix}"; + lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } if (extraKeys != null) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs index 805081dd..fde5ebb1 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs @@ -83,7 +83,7 @@ private static System.Type GetType(XmlNode node, string attributeName) } catch (Exception e) { - Log.Warn("Unable to aquire type for attribute {0}: ignored. Node: {1}, Exception: {2}", attributeName, node.OuterXml, e); + Log.Warn("Unable to acquire type for attribute {0}: ignored. Node: {1}, Exception: {2}", attributeName, node.OuterXml, e); return null; } } From e61468a1766d9766e9b56fdfad8a32e66748951b Mon Sep 17 00:00:00 2001 From: maca88 Date: Thu, 21 Jun 2018 21:32:34 +0200 Subject: [PATCH 05/24] Added a retry policy used for locking --- AsyncGenerator.yml | 2 + .../Async/DefaultRegionStrategy.cs | 1 - .../Async/RedisKeyLocker.cs | 115 ++++++------------ .../RedisKeyLocker.cs | 83 +++++-------- .../RetryPolicy.cs | 96 +++++++++++++++ 5 files changed, 171 insertions(+), 126 deletions(-) create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index 9663a994..8a8982fc 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -38,6 +38,8 @@ hasAttributeName: ObsoleteAttribute - conversion: Smart containingTypeName: AbstractRegionStrategy + - conversion: Smart + containingTypeName: RedisKeyLocker callForwarding: true cancellationTokens: guards: true diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs index a1aac360..22b5efcf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -189,7 +189,6 @@ public override async Task UnlockManyAsync(object[] keys, string lockValue, /// public override async Task ClearAsync(CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested(); var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(UpdateVersionLuaScript, _regionKeyArray, _maxVersionNumber)).ConfigureAwait(false); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs index d8816cce..e41e1798 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs @@ -41,54 +41,37 @@ public Task LockAsync(string key, string luaScript, RedisKey[] extraKeys { return Task.FromCanceled(cancellationToken); } - return InternalLockAsync(); - async Task InternalLockAsync() + var lockKey = $"{key}{_lockKeySuffix}"; + string Context() => lockKey; + + return _retryPolicy.ExecuteAsync(async () => { - var lockKey = $"{key}{_lockKeySuffix}"; - var totalAttempts = 0; - var lockTimer = new Stopwatch(); - lockTimer.Restart(); - do + var lockValue = _lockValueProvider.GetValue(); + if (!string.IsNullOrEmpty(luaScript)) { - if (totalAttempts > 0) + var keys = new RedisKey[] {lockKey}; + if (extraKeys != null) { - var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); - await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false); + keys = keys.Concat(extraKeys).ToArray(); } - var lockValue = _lockValueProvider.GetValue(); - if (!string.IsNullOrEmpty(luaScript)) + var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; + if (extraValues != null) { - var keys = new RedisKey[] {lockKey}; - if (extraKeys != null) - { - keys = keys.Concat(extraKeys).ToArray(); - } - var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds}; - if (extraValues != null) - { - values = values.Concat(extraValues).ToArray(); - } - cancellationToken.ThrowIfCancellationRequested(); - var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false); - if ((bool) result[0]) - { - return lockValue; - } + values = values.Concat(extraValues).ToArray(); } - else if (await (_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false)) + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false); + if ((bool) result[0]) { return lockValue; } - totalAttempts++; - - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); + } + else if (await (_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false)) + { + return lockValue; + } - throw new CacheException("Unable to acquire cache lock: " + - $"region='{_regionName}', " + - $"key='{key}', " + - $"total attempts='{totalAttempts}', " + - $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); - } + return null; // retry + }, Context, cancellationToken); } /// @@ -115,51 +98,33 @@ public Task LockManyAsync(string[] keys, string luaScript, RedisKey[] ex { return Task.FromCanceled(cancellationToken); } - return InternalLockManyAsync(); - async Task InternalLockManyAsync() - { + string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}")); + return _retryPolicy.ExecuteAsync(async () => + { var lockKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } - var totalAttempts = 0; - var lockTimer = new Stopwatch(); - lockTimer.Restart(); - do + var lockValue = _lockValueProvider.GetValue(); + if (extraKeys != null) { - if (totalAttempts > 0) - { - var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); - await (Task.Delay(retryDelay, cancellationToken)).ConfigureAwait(false); - } - var lockValue = _lockValueProvider.GetValue(); - if (extraKeys != null) - { - lockKeys = lockKeys.Concat(extraKeys).ToArray(); - } - var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; - if (extraValues != null) - { - values = values.Concat(extraValues).ToArray(); - } - cancellationToken.ThrowIfCancellationRequested(); - var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false); - if ((bool) result[0]) - { - return lockValue; - } - totalAttempts++; - - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); + lockKeys = lockKeys.Concat(extraKeys).ToArray(); + } + var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; + if (extraValues != null) + { + values = values.Concat(extraValues).ToArray(); + } + var result = (RedisValue[]) await (_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false); + if ((bool) result[0]) + { + return lockValue; + } - throw new CacheException("Unable to acquire cache lock: " + - $"region='{_regionName}', " + - $"keys='{string.Join(",", lockKeys)}', " + - $"total attempts='{totalAttempts}', " + - $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); - } + return null; // retry + }, Context, cancellationToken); } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs index 6a5ae0df..ecf8b779 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs @@ -12,16 +12,11 @@ namespace NHibernate.Caches.StackExRedis /// internal partial class RedisKeyLocker { - private readonly string _regionName; private readonly string _lockKeySuffix; private readonly TimeSpan _lockTimeout; - private readonly double _acquireLockTimeout; - private readonly int _retryTimes; - private readonly TimeSpan _maxRetryDelay; - private readonly TimeSpan _minRetryDelay; - private readonly ICacheLockRetryDelayProvider _lockRetryDelayProvider; private readonly ICacheLockValueProvider _lockValueProvider; private readonly IDatabase _database; + private readonly RetryPolicy> _retryPolicy; /// /// Default constructor. @@ -34,16 +29,29 @@ public RedisKeyLocker( IDatabase database, RedisCacheLockConfiguration configuration) { - _regionName = regionName; _database = database; _lockKeySuffix = configuration.KeySuffix; _lockTimeout = configuration.KeyTimeout; - _acquireLockTimeout = configuration.AcquireTimeout.TotalMilliseconds; - _retryTimes = configuration.RetryTimes; - _maxRetryDelay = configuration.MaxRetryDelay; - _minRetryDelay = configuration.MinRetryDelay; - _lockRetryDelayProvider = configuration.RetryDelayProvider; _lockValueProvider = configuration.ValueProvider; + + var acquireTimeout = configuration.AcquireTimeout; + var retryTimes = configuration.RetryTimes; + var maxRetryDelay = configuration.MaxRetryDelay; + var minRetryDelay = configuration.MinRetryDelay; + var lockRetryDelayProvider = configuration.RetryDelayProvider; + + _retryPolicy = new RetryPolicy>( + retryTimes, + acquireTimeout, + () => lockRetryDelayProvider.GetValue(minRetryDelay, maxRetryDelay) + ) + .ShouldRetry(s => s == null) + .OnFaliure((totalAttempts, elapsedMs, getKeysFn) => + throw new CacheException("Unable to acquire cache lock: " + + $"region='{regionName}', " + + $"keys='{getKeysFn()}', " + + $"total attempts='{totalAttempts}', " + + $"total acquiring time= '{elapsedMs}ms'")); } /// @@ -62,16 +70,10 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu throw new ArgumentNullException(nameof(key)); } var lockKey = $"{key}{_lockKeySuffix}"; - var totalAttempts = 0; - var lockTimer = new Stopwatch(); - lockTimer.Restart(); - do + string Context() => lockKey; + + return _retryPolicy.Execute(() => { - if (totalAttempts > 0) - { - var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); - Thread.Sleep(retryDelay); - } var lockValue = _lockValueProvider.GetValue(); if (!string.IsNullOrEmpty(luaScript)) { @@ -80,7 +82,7 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu { keys = keys.Concat(extraKeys).ToArray(); } - var values = new RedisValue[] {lockValue, (long)_lockTimeout.TotalMilliseconds}; + var values = new RedisValue[] {lockValue, (long) _lockTimeout.TotalMilliseconds}; if (extraValues != null) { values = values.Concat(extraValues).ToArray(); @@ -95,15 +97,9 @@ public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValu { return lockValue; } - totalAttempts++; - - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); - throw new CacheException("Unable to acquire cache lock: " + - $"region='{_regionName}', " + - $"key='{key}', " + - $"total attempts='{totalAttempts}', " + - $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + return null; // retry + }, Context); } /// @@ -125,21 +121,14 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re { throw new ArgumentNullException(nameof(luaScript)); } + string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}")); - var lockKeys = new RedisKey[keys.Length]; - for (var i = 0; i < keys.Length; i++) - { - lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; - } - var totalAttempts = 0; - var lockTimer = new Stopwatch(); - lockTimer.Restart(); - do + return _retryPolicy.Execute(() => { - if (totalAttempts > 0) + var lockKeys = new RedisKey[keys.Length]; + for (var i = 0; i < keys.Length; i++) { - var retryDelay = _lockRetryDelayProvider.GetValue(_minRetryDelay, _maxRetryDelay); - Thread.Sleep(retryDelay); + lockKeys[i] = $"{keys[i]}{_lockKeySuffix}"; } var lockValue = _lockValueProvider.GetValue(); if (extraKeys != null) @@ -156,15 +145,9 @@ public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, Re { return lockValue; } - totalAttempts++; - - } while (_retryTimes > totalAttempts - 1 && lockTimer.ElapsedMilliseconds < _acquireLockTimeout); - throw new CacheException("Unable to acquire cache lock: " + - $"region='{_regionName}', " + - $"keys='{string.Join(",", lockKeys)}', " + - $"total attempts='{totalAttempts}', " + - $"total acquiring time= '{lockTimer.ElapsedMilliseconds}ms'"); + return null; // retry + }, Context); } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs new file mode 100644 index 00000000..c758cff8 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// A retry policy that can be applied to delegates returning a value of type . + /// + /// The type of the execution result. + /// The context¸that can be used in callbacks. + internal class RetryPolicy + { + private readonly int _retryTimes; + private readonly Func _retryDelayFunc; + private readonly double _maxAllowedTime; + private Predicate _shouldRetryPredicate; + private Action _onFailureCallback; + + public RetryPolicy(int retryTimes, TimeSpan maxAllowedTime, Func retryDelayFunc) + { + _retryTimes = retryTimes; + _retryDelayFunc = retryDelayFunc; + _maxAllowedTime = maxAllowedTime.TotalMilliseconds; + } + + public RetryPolicy ShouldRetry(Predicate predicate) + { + _shouldRetryPredicate = predicate; + return this; + } + + public RetryPolicy OnFaliure(Action callback) + { + _onFailureCallback = callback; + return this; + } + + public TResult Execute(Func func, TContext context) + { + var totalAttempts = 0; + var timer = new Stopwatch(); + timer.Start(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _retryDelayFunc(); + Thread.Sleep(retryDelay); + } + + var result = func(); + if (_shouldRetryPredicate?.Invoke(result) != true) + { + return result; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && timer.ElapsedMilliseconds < _maxAllowedTime); + timer.Stop(); + _onFailureCallback?.Invoke(totalAttempts, timer.ElapsedMilliseconds, context); + return default(TResult); + } + + public async Task ExecuteAsync(Func> func, TContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var totalAttempts = 0; + var timer = new Stopwatch(); + timer.Start(); + do + { + if (totalAttempts > 0) + { + var retryDelay = _retryDelayFunc(); + await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false); + } + + var result = await func().ConfigureAwait(false); + if (_shouldRetryPredicate?.Invoke(result) != true) + { + return result; + } + totalAttempts++; + + } while (_retryTimes > totalAttempts - 1 && timer.ElapsedMilliseconds < _maxAllowedTime); + timer.Stop(); + _onFailureCallback?.Invoke(totalAttempts, timer.ElapsedMilliseconds, context); + return default(TResult); + } + } +} From d8f81022fc018965497b2619d834b99d226bf622 Mon Sep 17 00:00:00 2001 From: maca88 Date: Thu, 21 Jun 2018 22:57:43 +0200 Subject: [PATCH 06/24] Moved lua scripts to embedded resources --- .../DefaultRegionStrategy.cs | 148 ++++-------------- .../FastRegionStrategy.cs | 94 +++-------- .../DefaultRegionStrategy/CheckVersion.lua | 4 + .../Lua/DefaultRegionStrategy/Get.lua | 5 + .../Lua/DefaultRegionStrategy/GetMany.lua | 11 ++ .../InitializeVersion.lua | 6 + .../Lua/DefaultRegionStrategy/Lock.lua | 5 + .../Lua/DefaultRegionStrategy/LockMany.lua | 22 +++ .../Lua/DefaultRegionStrategy/Put.lua | 1 + .../Lua/DefaultRegionStrategy/PutMany.lua | 4 + .../Lua/DefaultRegionStrategy/Remove.lua | 1 + .../Lua/DefaultRegionStrategy/RemoveMany.lua | 5 + .../Lua/DefaultRegionStrategy/Unlock.lua | 5 + .../Lua/DefaultRegionStrategy/UnlockMany.lua | 8 + .../DefaultRegionStrategy/UpdateVersion.lua | 6 + .../FastRegionStrategy/ExpirationPutMany.lua | 4 + .../Lua/FastRegionStrategy/LockMany.lua | 22 +++ .../Lua/FastRegionStrategy/SlidingGet.lua | 5 + .../Lua/FastRegionStrategy/SlidingGetMany.lua | 8 + .../Lua/FastRegionStrategy/Unlock.lua | 5 + .../Lua/FastRegionStrategy/UnlockMany.lua | 8 + .../LuaScriptProvider.cs | 83 ++++++++++ .../NHibernate.Caches.StackExRedis.csproj | 3 + 23 files changed, 275 insertions(+), 188 deletions(-) create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index 0d4e5038..6f100839 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -14,125 +14,37 @@ namespace NHibernate.Caches.StackExRedis public partial class DefaultRegionStrategy : AbstractRegionStrategy { private const string InvalidVersionMessage = "Invalid version"; + private static readonly string UpdateVersionLuaScript; + private static readonly string InitializeVersionLuaScript; + private static readonly string GetLuaScript; + private static readonly string GetManyLuaScript; + private static readonly string PutLuaScript; + private static readonly string PutManyLuaScript; + private static readonly string RemoveLuaScript; + private static readonly string RemoveManyLuaScript; + private static readonly string LockLuaScript; + private static readonly string LockManyLuaScript; + private static readonly string UnlockLuaScript; + private static readonly string UnlockManyLuaScript; + + static DefaultRegionStrategy() + { + UpdateVersionLuaScript = LuaScriptProvider.GetScript("UpdateVersion"); + InitializeVersionLuaScript = LuaScriptProvider.GetScript("InitializeVersion"); + // For each operation we have to prepend the check version script + const string checkVersion = "CheckVersion"; + GetLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Get)); + GetManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(GetMany)); + PutLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Put)); + PutManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(PutMany)); + RemoveLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Remove)); + RemoveManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(RemoveMany)); + LockLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Lock)); + LockManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(LockMany)); + UnlockLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Unlock)); + UnlockManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(UnlockMany)); + } - private static readonly string CheckVersionCode = $@" - local version = redis.call('get', KEYS[#KEYS]) - if version ~= ARGV[#ARGV] then - return redis.error_reply('{InvalidVersionMessage}') - end"; - - private const string UpdateVersionLuaScript = @" - local version = redis.call('incr', KEYS[1]) - if version > tonumber(ARGV[1]) then - version = 1 - redis.call('set', KEYS[1], version) - end - return version"; - - private const string InitializeVersionLuaScript = @" - if redis.call('exists', KEYS[1]) == 1 then - return redis.call('get', KEYS[1]) - else - redis.call('set', KEYS[1], 1) - return 1 - end"; - - private static readonly string GetLuaScript = $@" - {CheckVersionCode} - local value = redis.call('get', KEYS[1]) - if value ~= nil and ARGV[1] == '1' then - redis.call('pexpire', KEYS[1], ARGV[2]) - end - return value"; - - private static readonly string GetManyLuaScript = $@" - {CheckVersionCode} - local values = {{}} - local sliding = ARGV[#ARGV-2] - local expirationMs = ARGV[#ARGV-1] - for i=1,#KEYS-1 do - local value = redis.call('get', KEYS[i]) - if value ~= nil and sliding == '1' then - redis.call('pexpire', KEYS[i], expirationMs) - end - values[i] = value - end - return values"; - - private static readonly string PutLuaScript = $@" - {CheckVersionCode} - return redis.call('set', KEYS[1], ARGV[1], 'px', ARGV[3])"; - - private static readonly string PutManyLuaScript = $@" - {CheckVersionCode} - local expirationMs = ARGV[#ARGV-1] - for i=1,#KEYS-1 do - redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) - end"; - - private static readonly string RemoveLuaScript = $@" - {CheckVersionCode} - return redis.call('del', KEYS[1])"; - - private static readonly string RemoveManyLuaScript = $@" - {CheckVersionCode} - local removedKeys = 0 - for i=1,#KEYS-1 do - removedKeys = removedKeys + redis.call('del', KEYS[i]) - end - return removedKeys"; - - private static readonly string LockLuaScript = $@" - {CheckVersionCode} - if redis.call('set', KEYS[1], ARGV[1], 'nx', 'px', ARGV[2]) == false then - return 0 - else - return 1 - end"; - - private static readonly string LockManyLuaScript = $@" - {CheckVersionCode} - local lockValue = ARGV[#ARGV-2] - local expirationMs = ARGV[#ARGV-1] - local lockedKeys = {{}} - local lockedKeyIndex = 1 - local locked = true - for i=1,#KEYS-1 do - if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then - locked = 0 - break - else - lockedKeys[lockedKeyIndex] = KEYS[i] - lockedKeyIndex = lockedKeyIndex + 1 - end - end - if locked == true then - return 1 - else - for i=1,#lockedKeys do - redis.call('del', lockedKeys[i]) - end - return 0 - end"; - - private static readonly string UnlockLuaScript = $@" - {CheckVersionCode} - if redis.call('get', KEYS[1]) == ARGV[1] then - return redis.call('del', KEYS[1]) - else - return 0 - end"; - - private static readonly string UnlockManyLuaScript = $@" - {CheckVersionCode} - local lockValue = ARGV[1] - local removedKeys = 0 - for i=1,#KEYS-1 do - if redis.call('get', KEYS[i]) == lockValue then - removedKeys = removedKeys + redis.call('del', KEYS[i]) - end - end - return removedKeys"; private readonly RedisKey[] _regionKeyArray; private readonly RedisValue[] _maxVersionNumber; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs index 004671ed..14abe8e5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs @@ -13,69 +13,22 @@ namespace NHibernate.Caches.StackExRedis /// public partial class FastRegionStrategy : AbstractRegionStrategy { - private const string SlidingGetLuaScript = @" - local value = redis.call('get', KEYS[1]) - if value ~= nil then - redis.call('pexpire', KEYS[1], ARGV[2]) - end - return value"; - - private const string SlidingGetManyLuaScript = @" - local expirationMs = ARGV[2] - local values = redis.call('MGET', unpack(KEYS)); - for i=1,#KEYS do - if values[i] ~= nil then - redis.call('pexpire', KEYS[i], expirationMs) - end - end - return values"; - - private const string ExpirationPutManyLuaScript = @" - local expirationMs = ARGV[#ARGV] - for i=1,#KEYS do - redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) - end"; - - private const string LockManyLuaScript = @" - local lockValue = ARGV[#ARGV-1] - local expirationMs = ARGV[#ARGV] - local lockedKeys = {} - local lockedKeyIndex = 1 - local locked = true - for i=1,#KEYS do - if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then - locked = false - break - else - lockedKeys[lockedKeyIndex] = KEYS[i] - lockedKeyIndex = lockedKeyIndex + 1 - end - end - if locked == true then - return 1 - else - for i=1,#lockedKeys do - redis.call('del', lockedKeys[i]) - end - return 0 - end"; - - private const string UnlockLuaScript = @" - if redis.call('get', KEYS[1]) == ARGV[1] then - return redis.call('del', KEYS[1]) - else - return 0 - end"; - - private const string UnlockManyLuaScript = @" - local lockValue = ARGV[1] - local removedKeys = 0 - for i=1,#KEYS do - if redis.call('get', KEYS[i]) == lockValue then - removedKeys = removedKeys + redis.call('del', KEYS[i]) - end - end - return removedKeys"; + private static readonly string SlidingGetLuaScript; + private static readonly string SlidingGetManyLuaScript; + private static readonly string ExpirationPutManyLuaScript; + private static readonly string LockManyLuaScript; + private static readonly string UnlockLuaScript; + private static readonly string UnlockManyLuaScript; + + static FastRegionStrategy() + { + SlidingGetLuaScript = LuaScriptProvider.GetScript("SlidingGet"); + SlidingGetManyLuaScript = LuaScriptProvider.GetScript("SlidingGetMany"); + ExpirationPutManyLuaScript = LuaScriptProvider.GetScript("ExpirationPutMany"); + LockManyLuaScript = LuaScriptProvider.GetScript(nameof(LockMany)); + UnlockLuaScript = LuaScriptProvider.GetScript(nameof(Unlock)); + UnlockManyLuaScript = LuaScriptProvider.GetScript(nameof(UnlockMany)); + } /// @@ -85,14 +38,15 @@ public FastRegionStrategy(ConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) : base(connectionMultiplexer, configuration, properties) { - if (ExpirationEnabled) + if (!ExpirationEnabled) + { + return; + } + PutManyScript = ExpirationPutManyLuaScript; + if (UseSlidingExpiration) { - PutManyScript = ExpirationPutManyLuaScript; - if (UseSlidingExpiration) - { - GetScript = SlidingGetLuaScript; - GetManyScript = SlidingGetManyLuaScript; - } + GetScript = SlidingGetLuaScript; + GetManyScript = SlidingGetManyLuaScript; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua new file mode 100644 index 00000000..338af90d --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua @@ -0,0 +1,4 @@ +local version = redis.call('get', KEYS[#KEYS]) +if version ~= ARGV[#ARGV] then + return redis.error_reply('Invalid version') +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua new file mode 100644 index 00000000..f7326f76 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua @@ -0,0 +1,5 @@ +local value = redis.call('get', KEYS[1]) +if value ~= nil and ARGV[1] == '1' then + redis.call('pexpire', KEYS[1], ARGV[2]) +end +return value diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua new file mode 100644 index 00000000..d6986c6e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua @@ -0,0 +1,11 @@ +local values = {{}} +local sliding = ARGV[#ARGV-2] +local expirationMs = ARGV[#ARGV-1] +for i=1,#KEYS-1 do + local value = redis.call('get', KEYS[i]) + if value ~= nil and sliding == '1' then + redis.call('pexpire', KEYS[i], expirationMs) + end + values[i] = value +end +return values diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua new file mode 100644 index 00000000..73bd5145 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua @@ -0,0 +1,6 @@ +if redis.call('exists', KEYS[1]) == 1 then + return redis.call('get', KEYS[1]) +else + redis.call('set', KEYS[1], 1) + return 1 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua new file mode 100644 index 00000000..ed59efed --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua @@ -0,0 +1,5 @@ +if redis.call('set', KEYS[1], ARGV[1], 'nx', 'px', ARGV[2]) == false then + return 0 +else + return 1 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua new file mode 100644 index 00000000..86e10255 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua @@ -0,0 +1,22 @@ +local lockValue = ARGV[#ARGV-2] +local expirationMs = ARGV[#ARGV-1] +local lockedKeys = {{}} +local lockedKeyIndex = 1 +local locked = true +for i=1,#KEYS-1 do + if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then + locked = 0 + break + else + lockedKeys[lockedKeyIndex] = KEYS[i] + lockedKeyIndex = lockedKeyIndex + 1 + end +end +if locked == true then + return 1 +else + for i=1,#lockedKeys do + redis.call('del', lockedKeys[i]) + end + return 0 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua new file mode 100644 index 00000000..40c9bcd7 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua @@ -0,0 +1 @@ +return redis.call('set', KEYS[1], ARGV[1], 'px', ARGV[3]) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua new file mode 100644 index 00000000..65657d4e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua @@ -0,0 +1,4 @@ +local expirationMs = ARGV[#ARGV-1] +for i=1,#KEYS-1 do + redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua new file mode 100644 index 00000000..661b3fcc --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua @@ -0,0 +1 @@ +return redis.call('del', KEYS[1]) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua new file mode 100644 index 00000000..733008aa --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua @@ -0,0 +1,5 @@ +local removedKeys = 0 +for i=1,#KEYS-1 do + removedKeys = removedKeys + redis.call('del', KEYS[i]) +end +return removedKeys diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua new file mode 100644 index 00000000..5d64dc4a --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua @@ -0,0 +1,5 @@ +if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) +else + return 0 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua new file mode 100644 index 00000000..55e4539e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua @@ -0,0 +1,8 @@ +local lockValue = ARGV[1] +local removedKeys = 0 +for i=1,#KEYS-1 do + if redis.call('get', KEYS[i]) == lockValue then + removedKeys = removedKeys + redis.call('del', KEYS[i]) + end +end +return removedKeys diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua new file mode 100644 index 00000000..29e3cdae --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua @@ -0,0 +1,6 @@ +local version = redis.call('incr', KEYS[1]) +if version > tonumber(ARGV[1]) then + version = 1 + redis.call('set', KEYS[1], version) +end +return version diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua new file mode 100644 index 00000000..c4e4c9b6 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua @@ -0,0 +1,4 @@ +local expirationMs = ARGV[#ARGV] +for i=1,#KEYS do + redis.call('set', KEYS[i], ARGV[i], 'px', expirationMs) +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua new file mode 100644 index 00000000..88f3a108 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua @@ -0,0 +1,22 @@ +local lockValue = ARGV[#ARGV-1] +local expirationMs = ARGV[#ARGV] +local lockedKeys = {} +local lockedKeyIndex = 1 +local locked = true +for i=1,#KEYS do + if redis.call('set', KEYS[i], lockValue, 'nx', 'px', expirationMs) == false then + locked = false + break + else + lockedKeys[lockedKeyIndex] = KEYS[i] + lockedKeyIndex = lockedKeyIndex + 1 + end +end +if locked == true then + return 1 +else + for i=1,#lockedKeys do + redis.call('del', lockedKeys[i]) + end + return 0 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua new file mode 100644 index 00000000..6762261a --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua @@ -0,0 +1,5 @@ +local value = redis.call('get', KEYS[1]) +if value ~= nil then + redis.call('pexpire', KEYS[1], ARGV[2]) +end +return value diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua new file mode 100644 index 00000000..6960b26e --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua @@ -0,0 +1,8 @@ +local expirationMs = ARGV[2] +local values = redis.call('MGET', unpack(KEYS)); +for i=1,#KEYS do + if values[i] ~= nil then + redis.call('pexpire', KEYS[i], expirationMs) + end +end +return values diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua new file mode 100644 index 00000000..5d64dc4a --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua @@ -0,0 +1,5 @@ +if redis.call('get', KEYS[1]) == ARGV[1] then + return redis.call('del', KEYS[1]) +else + return 0 +end diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua new file mode 100644 index 00000000..4f5f64e3 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua @@ -0,0 +1,8 @@ +local lockValue = ARGV[1] +local removedKeys = 0 +for i=1,#KEYS do + if redis.call('get', KEYS[i]) == lockValue then + removedKeys = removedKeys + redis.call('del', KEYS[i]) + end +end +return removedKeys diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs new file mode 100644 index 00000000..78be27ae --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Provides the lua scripts of the internal region strategies from the embedded resources. + /// + internal static class LuaScriptProvider + { + // Dictionary> + private static readonly Dictionary> StrategyLuaScripts = + new Dictionary>(); + + static LuaScriptProvider() + { + var assembly = Assembly.GetExecutingAssembly(); + var regex = new Regex(@"Lua\.([\w]+)\.([\w]+)\.lua"); + foreach (var resourceName in assembly.GetManifestResourceNames()) + { + var match = regex.Match(resourceName); + if (!match.Success) + { + continue; + } + if (!StrategyLuaScripts.TryGetValue(match.Groups[1].Value, out var luaScripts)) + { + luaScripts = new Dictionary(); + StrategyLuaScripts.Add(match.Groups[1].Value, luaScripts); + } + + using (var stream = assembly.GetManifestResourceStream(resourceName)) + using (var reader = new StreamReader(stream)) + { + luaScripts.Add(match.Groups[2].Value, reader.ReadToEnd()); + } + } + } + + /// + /// Get the concatenation of multiple lua scripts for the region strategy. + /// + /// The region strategy. + /// The script names to concatenate. + /// The concatenation of multiple lua scripts. + public static string GetConcatenatedScript(params string[] scriptNames) where TRegionStrategy : AbstractRegionStrategy + { + var scriptBuilder = new StringBuilder(); + foreach (var scriptName in scriptNames) + { + scriptBuilder.Append(GetScript(scriptName)); + } + return scriptBuilder.ToString(); + } + + /// + /// Get the lua script for the region strategy. + /// + /// The region strategy. + /// The script name. + /// The lua script. + public static string GetScript(string scriptName) where TRegionStrategy : AbstractRegionStrategy + { + if (!StrategyLuaScripts.TryGetValue(typeof(TRegionStrategy).Name, out var luaScripts)) + { + throw new KeyNotFoundException( + $"There are no embedded scripts for region strategy {typeof(TRegionStrategy).Name}."); + } + if (!luaScripts.TryGetValue(scriptName, out var script)) + { + throw new KeyNotFoundException( + $"There is no embedded script with name {scriptName} for region strategy {typeof(TRegionStrategy).Name}."); + } + return script; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj index 8a3c75a5..1f2346aa 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj @@ -13,6 +13,9 @@ NETFX;$(DefineConstants) + + + From c0cc96017a981c0ac28052402c5a55aa8f98b56c Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 23 Jun 2018 01:45:58 +0200 Subject: [PATCH 07/24] Added serialization tests for NHibernate types --- AsyncGenerator.yml | 2 + .../Async/RedisCacheFixture.cs | 236 +++++++++++++++++ .../RedisCacheFixture.cs | 242 ++++++++++++++++++ 3 files changed, 480 insertions(+) diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index 8a8982fc..f743802d 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -450,6 +450,8 @@ withoutCancellationToken: - hasAttributeName: TestAttribute - hasAttributeName: TheoryAttribute + ignoreSearchForAsyncCounterparts: + - name: Disassemble scanMethodBody: true searchAsyncCounterpartsInInheritedTypes: true scanForMissingAsyncMembers: diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 711a05df..26cf50ff 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -10,12 +10,22 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; using NHibernate.Cache; +using NHibernate.Cache.Entry; using NHibernate.Caches.Common.Tests; +using NHibernate.Collection; +using NHibernate.Engine; +using NHibernate.Persister.Entity; +using NHibernate.Type; +using NSubstitute; using NUnit.Framework; namespace NHibernate.Caches.StackExRedis.Tests @@ -23,6 +33,232 @@ namespace NHibernate.Caches.StackExRedis.Tests public abstract partial class RedisCacheFixture : CacheFixture { + [Test] + public async Task TestNHibernateAnyTypeSerializationAsync() + { + var objectTypeCacheEntryType = typeof(AnyType.ObjectTypeCacheEntry); + var entityNameField = objectTypeCacheEntryType.GetField("entityName", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(entityNameField, Is.Not.Null, "field entityName in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); + var idField = objectTypeCacheEntryType.GetField("id", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(idField, Is.Not.Null, "field id in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); + + var entityName = nameof(MyEntity); + var propertyValues = new Dictionary + { + {NHibernateUtil.Object, new MyEntity{Id = 2}} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + sessionImpl.BestGuessEntityName(Arg.Any()).Returns(o => o[0].GetType().Name); + sessionImpl.GetContextEntityIdentifier(Arg.Is(o => o is MyEntity)).Returns(o => ((MyEntity) o[0]).Id); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + + Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); + var anyObject = cacheEntry.DisassembledState[0]; + Assert.That(anyObject, Is.TypeOf(objectTypeCacheEntryType)); + Assert.That(entityNameField.GetValue(anyObject), Is.EqualTo(nameof(MyEntity))); + Assert.That(idField.GetValue(anyObject), Is.EqualTo(2)); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.DisassembledState, Has.Length.EqualTo(1)); + var retrievedAnyObject = retrievedCacheEntry.DisassembledState[0]; + Assert.That(retrievedAnyObject, Is.TypeOf(objectTypeCacheEntryType)); + Assert.That(entityNameField.GetValue(retrievedAnyObject), Is.EqualTo(nameof(MyEntity)), + "entityName is different from the original AnyType.ObjectTypeCacheEntry"); + Assert.That(idField.GetValue(retrievedAnyObject), Is.EqualTo(2), + "id is different from the original AnyType.ObjectTypeCacheEntry"); + } + + [Test] + public async Task TestNHibernateStandardTypesSerializationAsync() + { + var entityName = nameof(MyEntity); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml("XmlDoc"); + var propertyValues = new Dictionary + { + {NHibernateUtil.AnsiString, "test"}, + {NHibernateUtil.Binary, new byte[] {1, 2, 3, 4}}, + {NHibernateUtil.BinaryBlob, new byte[] {1, 2, 3, 4}}, + {NHibernateUtil.Boolean, true}, + {NHibernateUtil.Byte, (byte) 1}, + {NHibernateUtil.Character, 'a'}, + {NHibernateUtil.CultureInfo, CultureInfo.CurrentCulture}, + {NHibernateUtil.DateTime, DateTime.Now}, + {NHibernateUtil.DateTimeNoMs, DateTime.Now}, + {NHibernateUtil.LocalDateTime, DateTime.Now}, + {NHibernateUtil.UtcDateTime, DateTime.UtcNow}, + {NHibernateUtil.LocalDateTimeNoMs, DateTime.Now}, + {NHibernateUtil.UtcDateTimeNoMs, DateTime.UtcNow}, + {NHibernateUtil.DateTimeOffset, DateTimeOffset.Now}, + {NHibernateUtil.Date, DateTime.Today}, + {NHibernateUtil.Decimal, 2.5m}, + {NHibernateUtil.Double, 2.5d}, + {NHibernateUtil.Currency, 2.5m}, + {NHibernateUtil.Guid, Guid.NewGuid()}, + {NHibernateUtil.Int16, (short) 1}, + {NHibernateUtil.Int32, 3}, + {NHibernateUtil.Int64, 3L}, + {NHibernateUtil.SByte, (sbyte) 1}, + {NHibernateUtil.UInt16, (ushort) 1}, + {NHibernateUtil.UInt32, (uint) 1}, + {NHibernateUtil.UInt64, (ulong) 1}, + {NHibernateUtil.Single, 1.1f}, + {NHibernateUtil.String, "test"}, + {NHibernateUtil.StringClob, "test"}, + {NHibernateUtil.Time, DateTime.Now}, + {NHibernateUtil.Ticks, DateTime.Now}, + {NHibernateUtil.TimeAsTimeSpan, TimeSpan.FromMilliseconds(15)}, + {NHibernateUtil.TimeSpan, TimeSpan.FromMilliseconds(1234)}, + {NHibernateUtil.DbTimestamp, DateTime.Now}, + {NHibernateUtil.TrueFalse, false}, + {NHibernateUtil.YesNo, true}, + {NHibernateUtil.Class, typeof(IType)}, + {NHibernateUtil.ClassMetaType, entityName}, + {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, + {NHibernateUtil.AnsiChar, 'a'}, + {NHibernateUtil.XmlDoc, xmlDoc}, + {NHibernateUtil.XDoc, XDocument.Parse("XDoc")}, + {NHibernateUtil.Uri, new Uri("http://test.com")} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), + "DisassembledState is different from the original CacheEntry"); + } + + [Test] + public async Task TestNHibernateCacheEntrySerializationAsync() + { + var entityName = nameof(MyEntity); + var propertyValues = new Dictionary + { + {NHibernateUtil.String, "test"} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.AreLazyPropertiesUnfetched, Is.EqualTo(cacheEntry.AreLazyPropertiesUnfetched), + "AreLazyPropertiesUnfetched is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), + "DisassembledState is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.Subclass, Is.EqualTo(cacheEntry.Subclass), + "Subclass is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.Version, Is.EqualTo(cacheEntry.Version), + "Version is different from the original CacheEntry"); + } + + [Test] + public async Task TestNHibernateCollectionCacheEntrySerializationAsync() + { + var sfImpl = Substitute.For(); + var collection = Substitute.For(); + collection.Disassemble(null).Returns(o => new object[] {"test"}); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); + var cacheEntry = new CollectionCacheEntry(collection, null); + Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CollectionCacheEntry) value; + Assert.That(retrievedCacheEntry.State, Has.Length.EqualTo(1)); + Assert.That(retrievedCacheEntry.State[0], Is.EquivalentTo("test"), + "State is different from the original CollectionCacheEntry"); + } + + [Test] + public async Task TestNHibernateCacheLockSerializationAsync() + { + var sfImpl = Substitute.For(); + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); + var cacheEntry = new CacheLock(1234, 1, 5); + cacheEntry.Lock(123, 2); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheLock) value; + Assert.That(retrievedCacheEntry.Id, Is.EqualTo(cacheEntry.Id), + "Id is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), + "IsLock is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.WasLockedConcurrently, Is.EqualTo(cacheEntry.WasLockedConcurrently), + "WasLockedConcurrently is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), + "ToString() is different from the original CacheLock"); + } + + [Test] + public async Task TestNHibernateCachedItemSerializationAsync() + { + var sfImpl = Substitute.For(); + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); + var cacheEntry = new CachedItem("test", 111, 5); + cacheEntry.Lock(123, 2); + + var cache = GetDefaultCache(); + await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); + var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CachedItem) value; + Assert.That(retrievedCacheEntry.FreshTimestamp, Is.EqualTo(cacheEntry.FreshTimestamp), + "FreshTimestamp is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), + "IsLock is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.Value, Is.EqualTo(cacheEntry.Value), + "Value is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), + "ToString() is different from the original CachedItem"); + } + [Test] public async Task TestEnvironmentNameAsync() { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index a16ba405..757c393f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -1,11 +1,21 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml; +using System.Xml.Linq; using NHibernate.Cache; +using NHibernate.Cache.Entry; using NHibernate.Caches.Common.Tests; +using NHibernate.Collection; +using NHibernate.Engine; +using NHibernate.Persister.Entity; +using NHibernate.Type; +using NSubstitute; using NUnit.Framework; namespace NHibernate.Caches.StackExRedis.Tests @@ -20,6 +30,238 @@ public abstract partial class RedisCacheFixture : CacheFixture protected override Func ProviderBuilder => () => new RedisCacheProvider(); + [Serializable] + public class MyEntity + { + public int Id { get; set; } + } + + [Test] + public void TestNHibernateAnyTypeSerialization() + { + var objectTypeCacheEntryType = typeof(AnyType.ObjectTypeCacheEntry); + var entityNameField = objectTypeCacheEntryType.GetField("entityName", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(entityNameField, Is.Not.Null, "field entityName in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); + var idField = objectTypeCacheEntryType.GetField("id", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(idField, Is.Not.Null, "field id in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); + + var entityName = nameof(MyEntity); + var propertyValues = new Dictionary + { + {NHibernateUtil.Object, new MyEntity{Id = 2}} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + sessionImpl.BestGuessEntityName(Arg.Any()).Returns(o => o[0].GetType().Name); + sessionImpl.GetContextEntityIdentifier(Arg.Is(o => o is MyEntity)).Returns(o => ((MyEntity) o[0]).Id); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + + Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); + var anyObject = cacheEntry.DisassembledState[0]; + Assert.That(anyObject, Is.TypeOf(objectTypeCacheEntryType)); + Assert.That(entityNameField.GetValue(anyObject), Is.EqualTo(nameof(MyEntity))); + Assert.That(idField.GetValue(anyObject), Is.EqualTo(2)); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.DisassembledState, Has.Length.EqualTo(1)); + var retrievedAnyObject = retrievedCacheEntry.DisassembledState[0]; + Assert.That(retrievedAnyObject, Is.TypeOf(objectTypeCacheEntryType)); + Assert.That(entityNameField.GetValue(retrievedAnyObject), Is.EqualTo(nameof(MyEntity)), + "entityName is different from the original AnyType.ObjectTypeCacheEntry"); + Assert.That(idField.GetValue(retrievedAnyObject), Is.EqualTo(2), + "id is different from the original AnyType.ObjectTypeCacheEntry"); + } + + [Test] + public void TestNHibernateStandardTypesSerialization() + { + var entityName = nameof(MyEntity); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml("XmlDoc"); + var propertyValues = new Dictionary + { + {NHibernateUtil.AnsiString, "test"}, + {NHibernateUtil.Binary, new byte[] {1, 2, 3, 4}}, + {NHibernateUtil.BinaryBlob, new byte[] {1, 2, 3, 4}}, + {NHibernateUtil.Boolean, true}, + {NHibernateUtil.Byte, (byte) 1}, + {NHibernateUtil.Character, 'a'}, + {NHibernateUtil.CultureInfo, CultureInfo.CurrentCulture}, + {NHibernateUtil.DateTime, DateTime.Now}, + {NHibernateUtil.DateTimeNoMs, DateTime.Now}, + {NHibernateUtil.LocalDateTime, DateTime.Now}, + {NHibernateUtil.UtcDateTime, DateTime.UtcNow}, + {NHibernateUtil.LocalDateTimeNoMs, DateTime.Now}, + {NHibernateUtil.UtcDateTimeNoMs, DateTime.UtcNow}, + {NHibernateUtil.DateTimeOffset, DateTimeOffset.Now}, + {NHibernateUtil.Date, DateTime.Today}, + {NHibernateUtil.Decimal, 2.5m}, + {NHibernateUtil.Double, 2.5d}, + {NHibernateUtil.Currency, 2.5m}, + {NHibernateUtil.Guid, Guid.NewGuid()}, + {NHibernateUtil.Int16, (short) 1}, + {NHibernateUtil.Int32, 3}, + {NHibernateUtil.Int64, 3L}, + {NHibernateUtil.SByte, (sbyte) 1}, + {NHibernateUtil.UInt16, (ushort) 1}, + {NHibernateUtil.UInt32, (uint) 1}, + {NHibernateUtil.UInt64, (ulong) 1}, + {NHibernateUtil.Single, 1.1f}, + {NHibernateUtil.String, "test"}, + {NHibernateUtil.StringClob, "test"}, + {NHibernateUtil.Time, DateTime.Now}, + {NHibernateUtil.Ticks, DateTime.Now}, + {NHibernateUtil.TimeAsTimeSpan, TimeSpan.FromMilliseconds(15)}, + {NHibernateUtil.TimeSpan, TimeSpan.FromMilliseconds(1234)}, + {NHibernateUtil.DbTimestamp, DateTime.Now}, + {NHibernateUtil.TrueFalse, false}, + {NHibernateUtil.YesNo, true}, + {NHibernateUtil.Class, typeof(IType)}, + {NHibernateUtil.ClassMetaType, entityName}, + {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, + {NHibernateUtil.AnsiChar, 'a'}, + {NHibernateUtil.XmlDoc, xmlDoc}, + {NHibernateUtil.XDoc, XDocument.Parse("XDoc")}, + {NHibernateUtil.Uri, new Uri("http://test.com")} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), + "DisassembledState is different from the original CacheEntry"); + } + + [Test] + public void TestNHibernateCacheEntrySerialization() + { + var entityName = nameof(MyEntity); + var propertyValues = new Dictionary + { + {NHibernateUtil.String, "test"} + }; + + var sfImpl = Substitute.For(); + var sessionImpl = Substitute.For(); + var entityPersister = Substitute.For(); + entityPersister.EntityName.Returns(entityName); + entityPersister.IsLazyPropertiesCacheable.Returns(false); + entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); + var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheEntry) value; + Assert.That(retrievedCacheEntry.AreLazyPropertiesUnfetched, Is.EqualTo(cacheEntry.AreLazyPropertiesUnfetched), + "AreLazyPropertiesUnfetched is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), + "DisassembledState is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.Subclass, Is.EqualTo(cacheEntry.Subclass), + "Subclass is different from the original CacheEntry"); + Assert.That(retrievedCacheEntry.Version, Is.EqualTo(cacheEntry.Version), + "Version is different from the original CacheEntry"); + } + + [Test] + public void TestNHibernateCollectionCacheEntrySerialization() + { + var sfImpl = Substitute.For(); + var collection = Substitute.For(); + collection.Disassemble(null).Returns(o => new object[] {"test"}); + + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); + var cacheEntry = new CollectionCacheEntry(collection, null); + Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CollectionCacheEntry) value; + Assert.That(retrievedCacheEntry.State, Has.Length.EqualTo(1)); + Assert.That(retrievedCacheEntry.State[0], Is.EquivalentTo("test"), + "State is different from the original CollectionCacheEntry"); + } + + [Test] + public void TestNHibernateCacheLockSerialization() + { + var sfImpl = Substitute.For(); + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); + var cacheEntry = new CacheLock(1234, 1, 5); + cacheEntry.Lock(123, 2); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CacheLock) value; + Assert.That(retrievedCacheEntry.Id, Is.EqualTo(cacheEntry.Id), + "Id is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), + "IsLock is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.WasLockedConcurrently, Is.EqualTo(cacheEntry.WasLockedConcurrently), + "WasLockedConcurrently is different from the original CacheLock"); + Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), + "ToString() is different from the original CacheLock"); + } + + [Test] + public void TestNHibernateCachedItemSerialization() + { + var sfImpl = Substitute.For(); + var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); + var cacheEntry = new CachedItem("test", 111, 5); + cacheEntry.Lock(123, 2); + + var cache = GetDefaultCache(); + cache.Put(cacheKey, cacheEntry); + var value = cache.Get(cacheKey); + + Assert.That(value, Is.TypeOf()); + var retrievedCacheEntry = (CachedItem) value; + Assert.That(retrievedCacheEntry.FreshTimestamp, Is.EqualTo(cacheEntry.FreshTimestamp), + "FreshTimestamp is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), + "IsLock is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.Value, Is.EqualTo(cacheEntry.Value), + "Value is different from the original CachedItem"); + Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), + "ToString() is different from the original CachedItem"); + } + [Test] public void TestEnvironmentName() { From feeb44c792b6796e80f0bf4283d61cbfc7b6619b Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 23 Jun 2018 02:04:32 +0200 Subject: [PATCH 08/24] Updated Appveyor for running redis tests --- appveyor.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 527878e4..ac1700d7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,6 +19,10 @@ install: - curl -L -O -S -s http://downloads.northscale.com/memcached-1.4.5-amd64.zip - 7z x memcached-1.4.5-amd64.zip - ps: $MemCached = Start-Process memcached-amd64\memcached.exe -PassThru +# redis server +- curl -L -O -S -s https://github.com/ServiceStack/redis-windows/raw/master/downloads/redis64-2.8.17.zip +- 7z x redis64-2.8.17.zip +- ps: $Redis = Start-Process redis-server.exe redis.windows.conf -PassThru before_build: - which msbuild.exe - nuget restore NHibernate.Caches.Everything.sln @@ -107,7 +111,7 @@ test_script: #netFx tests If ($env:TESTS -eq 'net') { - @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache', 'CoreDistributedCache') | ForEach-Object { + @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache', 'CoreDistributedCache', 'StackExRedis') | ForEach-Object { $projects.Add($_, "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$target\NHibernate.Caches.$_.Tests.dll") } ForEach ($project in $projects.GetEnumerator()) { @@ -120,7 +124,7 @@ test_script: #core tests If ($env:TESTS -eq 'core') { - @('CoreMemoryCache', 'CoreDistributedCache', 'RtMemoryCache') | ForEach-Object { + @('CoreMemoryCache', 'CoreDistributedCache', 'RtMemoryCache', 'StackExRedis') | ForEach-Object { $projects.Add($_, "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$target\NHibernate.Caches.$_.Tests.dll") } ForEach ($project in $projects.GetEnumerator()) { @@ -151,3 +155,4 @@ artifacts: name: Binaries on_finish: - ps: Stop-Process -Id $MemCached.Id +- ps: Stop-Process -Id $Redis.Id From c6642a41d71e9871562ee294cc53fa5cafddc741 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 23 Jun 2018 14:04:59 +0200 Subject: [PATCH 09/24] Added a configuration option to control the appending of the hash code of the key to the cache key --- .../Async/RedisCacheFixture.cs | 87 ++++++++++ .../RedisCacheFixture.cs | 154 ++++++++++++++++++ .../AbstractRegionStrategy.cs | 10 +- .../DefaultRegionStrategy.cs | 4 +- .../RedisCacheConfiguration.cs | 5 + .../RedisCacheProvider.cs | 9 + .../RedisCacheRegionConfiguration.cs | 6 + .../RedisEnvironment.cs | 5 + .../RedisSectionHandler.cs | 3 +- .../RegionConfig.cs | 12 +- 10 files changed, 290 insertions(+), 5 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 26cf50ff..280f7e4c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -259,6 +259,93 @@ public async Task TestNHibernateCachedItemSerializationAsync() "ToString() is different from the original CachedItem"); } + [Test] + public async Task TestEqualObjectsWithDifferentHashCodeDefaultConfigurationAsync() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var cache = GetDefaultCache(); + + await (cache.PutAsync(obj1, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "The hash code should be used in the cache key"); + await (cache.RemoveAsync(obj1, CancellationToken.None)); + } + + [Test] + public async Task TestEqualObjectsWithDifferentHashCodeGlobalConfigurationAsync() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + props[RedisEnvironment.UseHashCode] = "false"; + cacheProvider.Start(props); + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + await (cache.PutAsync(obj1, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + await (cache.RemoveAsync(obj1, CancellationToken.None)); + } + + [Test] + public async Task TestEqualObjectsWithDifferentHashCodeRegionConfigurationAsync() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + cacheProvider.Start(props); + props["hashcode"] = "false"; + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + await (cache.PutAsync(obj1, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + await (cache.RemoveAsync(obj1, CancellationToken.None)); + } + + [Test] + public async Task TestNonEqualObjectsWithEqualToStringAsync() + { + var value = "value"; + var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); + var obj2 = new CustomCacheKey(new ObjectEqualToString(2), "test", true); + + var cache = GetDefaultCache(); + + await (cache.PutAsync(obj1, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + await (cache.RemoveAsync(obj1, CancellationToken.None)); + } + + [Test] + public async Task TestNonEqualObjectsWithEqualToStringNoHashCodeAsync() + { + var value = "value"; + var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); + var obj2 = new CustomCacheKey(new ObjectEqualToString(2), "test", true); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + props[RedisEnvironment.UseHashCode] = "false"; + cacheProvider.Start(props); + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + await (cache.PutAsync(obj1, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + await (cache.RemoveAsync(obj1, CancellationToken.None)); + } + [Test] public async Task TestEnvironmentNameAsync() { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index 757c393f..7b0a0a89 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -262,6 +262,160 @@ public void TestNHibernateCachedItemSerialization() "ToString() is different from the original CachedItem"); } + [Serializable] + protected class CustomCacheKey + { + private readonly int _hashCode; + + public CustomCacheKey(object id, string entityName, bool useIdHashCode) + { + Id = id; + EntityName = entityName; + _hashCode = useIdHashCode ? id.GetHashCode() : base.GetHashCode(); + } + + public object Id { get; } + + public string EntityName { get; } + + public override string ToString() + { + return EntityName + "#" + Id; + } + + public override bool Equals(object obj) + { + if (!(obj is CustomCacheKey other)) + { + return false; + } + return Equals(other.Id, Id) && Equals(other.EntityName, EntityName); + } + + public override int GetHashCode() + { + return _hashCode.GetHashCode(); + } + } + + [Test] + public void TestEqualObjectsWithDifferentHashCodeDefaultConfiguration() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var cache = GetDefaultCache(); + + cache.Put(obj1, value); + Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(cache.Get(obj2), Is.Null, "The hash code should be used in the cache key"); + cache.Remove(obj1); + } + + [Test] + public void TestEqualObjectsWithDifferentHashCodeGlobalConfiguration() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + props[RedisEnvironment.UseHashCode] = "false"; + cacheProvider.Start(props); + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + cache.Put(obj1, value); + Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + cache.Remove(obj1); + } + + [Test] + public void TestEqualObjectsWithDifferentHashCodeRegionConfiguration() + { + var value = "value"; + var obj1 = new CustomCacheKey(1, "test", false); + var obj2 = new CustomCacheKey(1, "test", false); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + cacheProvider.Start(props); + props["hashcode"] = "false"; + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + cache.Put(obj1, value); + Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + cache.Remove(obj1); + } + + [Serializable] + protected class ObjectEqualToString + { + public ObjectEqualToString(int id) + { + Id = id; + } + + public int Id { get; } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public override string ToString() + { + return nameof(ObjectEqualToString); + } + + public override bool Equals(object obj) + { + if (!(obj is ObjectEqualToString other)) + { + return false; + } + + return other.Id == Id; + } + } + + [Test] + public void TestNonEqualObjectsWithEqualToString() + { + var value = "value"; + var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); + var obj2 = new CustomCacheKey(new ObjectEqualToString(2), "test", true); + + var cache = GetDefaultCache(); + + cache.Put(obj1, value); + Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(cache.Get(obj2), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + cache.Remove(obj1); + } + + [Test] + public void TestNonEqualObjectsWithEqualToStringNoHashCode() + { + var value = "value"; + var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); + var obj2 = new CustomCacheKey(new ObjectEqualToString(2), "test", true); + + var props = GetDefaultProperties(); + var cacheProvider = ProviderBuilder(); + props[RedisEnvironment.UseHashCode] = "false"; + cacheProvider.Start(props); + var cache = cacheProvider.BuildCache(DefaultRegion, props); + + cache.Put(obj1, value); + Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); + Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + cache.Remove(obj1); + } + [Test] public void TestEnvironmentName() { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index 0f1966f9..bd94d506 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -63,6 +63,7 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, RegionName = configuration.RegionName; Expiration = configuration.Expiration; UseSlidingExpiration = configuration.UseSlidingExpiration; + UseHashCode = configuration.UseHashCode; RegionKey = configuration.RegionKey; ConnectionMultiplexer = connectionMultiplexer; Database = connectionMultiplexer.GetDatabase(configuration.Database); @@ -144,6 +145,11 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, /// for resetting a cached item expiration each time it is accessed. public bool UseSlidingExpiration { get; } + /// + /// Whether the hash code of the key should be added to the cache key. + /// + public bool UseHashCode { get; } + /// /// Is the expiration enabled? /// @@ -491,7 +497,9 @@ protected virtual string GetCacheKey(object value) { // Hash tag (wrap with curly brackets) the region key in order to ensure that all region keys // will be located on the same server, when a Redis cluster is used. - return string.Concat("{", RegionKey, "}:", value.ToString(), "@", value.GetHashCode()); + return UseHashCode + ? string.Concat("{", RegionKey, "}:", value.ToString(), "@", value.GetHashCode()) + : string.Concat("{", RegionKey, "}:", value.ToString()); } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index 6f100839..828aa0d0 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -296,7 +296,9 @@ protected override RedisValue[] GetAdditionalValues() /// protected override string GetCacheKey(object value) { - return string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString(), "@", value.GetHashCode()); + return UseHashCode + ? string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString(), "@", value.GetHashCode()) + : string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString()); } private void InitializeVersion() diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index 0523af28..d817260b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -39,6 +39,11 @@ public class RedisCacheConfiguration /// for resetting a cached item expiration each time it is accessed. public bool DefaultUseSlidingExpiration { get; set; } + /// + /// Whether the hash code of the key should be added to the cache key. + /// + public bool DefaultUseHashCode { get; set; } = true; + /// /// The default expiration time for the keys to expire. /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index ba7a9d00..794a57e6 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -164,6 +164,11 @@ private RedisCacheRegionConfiguration BuildRegionConfiguration(RegionConfig regi config.UseSlidingExpiration = GetBoolean("sliding", properties, regionConfig.UseSlidingExpiration ?? GetBoolean(RedisEnvironment.UseSlidingExpiration, properties, _defaultCacheConfiguration.DefaultUseSlidingExpiration)); + + config.UseHashCode = GetBoolean("hashcode", properties, + regionConfig.UseHashCode ?? GetBoolean(RedisEnvironment.UseHashCode, properties, + _defaultCacheConfiguration.DefaultUseHashCode)); + Log.Debug("Use sliding expiration for region {0}: {1}", regionConfig.Region, config.UseSlidingExpiration); return config; @@ -201,6 +206,10 @@ private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary public System.Type RegionStrategy { get; set; } + /// + /// Whether the hash code of the key should be added to the cache key. + /// + public bool UseHashCode { get; set; } + /// /// The to be used. /// @@ -88,6 +93,7 @@ public override string ToString() sb.AppendFormat("Expiration={0}s", Expiration.TotalSeconds); sb.AppendFormat("Database={0}", Database); sb.AppendFormat("UseSlidingExpiration={0}", UseSlidingExpiration); + sb.AppendFormat("UseHashCode={0}", UseHashCode); sb.AppendFormat("RegionStrategy={0}", RegionStrategy); sb.AppendFormat("Serializer={0}", Serializer); sb.AppendFormat("LockConfiguration=({0})", LockConfiguration); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs index bc4eda4b..a70a9f4a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs @@ -43,6 +43,11 @@ public static class RedisEnvironment /// public const string UseSlidingExpiration = "cache.use_sliding_expiration"; + /// + /// Whether the hash code of the key should be added to the cache key. + /// + public const string UseHashCode = "cache.use_hash_code"; + /// /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients /// uses the same Redis database. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs index fde5ebb1..0ab4b9f3 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs @@ -32,7 +32,8 @@ public object Create(object parent, object configContext, XmlNode section) GetTimespanFromSeconds(node, "expiration"), GetBoolean(node, "sliding"), GetInteger(node, "database"), - GetType(node, "strategy") + GetType(node, "strategy"), + GetBoolean(node, "hashcode") )); } else diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs index 78571b34..a2f95b37 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs @@ -15,7 +15,7 @@ public class RegionConfig /// Build a cache region configuration. /// /// The configured cache region. - public RegionConfig(string region) : this(region, null, null, null, null) + public RegionConfig(string region) : this(region, null, null, null, null, null) { } @@ -27,13 +27,16 @@ public RegionConfig(string region) : this(region, null, null, null, null) /// Whether the expiration should be sliding or not. /// The database for the region. /// The strategy for the region. - public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpiration, int? database, System.Type regionStrategy) + /// Whether the hash code of the key should be added to the cache key. + public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpiration, int? database, System.Type regionStrategy, + bool? useHashCode) { Region = region; Expiration = expiration; UseSlidingExpiration = useSlidingExpiration; Database = database; RegionStrategy = regionStrategy; + UseHashCode = useHashCode; } /// @@ -60,5 +63,10 @@ public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpirat /// The type. /// public System.Type RegionStrategy { get; } + + /// + /// Whether the hash code of the key should be added to the cache key. + /// + public bool? UseHashCode { get; } } } From 20da5f8e5d315db9672c9b74b878e1cc73ce5084 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 23 Jun 2018 15:24:56 +0200 Subject: [PATCH 10/24] Added test for concurrent locking --- .../Async/CacheFixture.cs | 47 +++++++++++++++++++ .../CacheFixture.cs | 47 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index b6987ad2..fe82eda8 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -87,6 +87,53 @@ public async Task TestLockUnlockAsync() } } + [Test] + public async Task TestConcurrentLockUnlockAsync() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + const string value = "value"; + const string key = "keyToLock"; + + var cache = GetDefaultCache(); + + await (cache.PutAsync(key, value, CancellationToken.None)); + Assert.That(await (cache.GetAsync(key, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key"); + + // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads + // Thread 1 + await (cache.LockAsync(key, CancellationToken.None)); + + // Thread 2 + try + { + Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should be locked"); + } + finally + { + await (cache.UnlockAsync(key, CancellationToken.None)); + } + + // Thread 3 + try + { + Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should still be locked"); + } + finally + { + await (cache.UnlockAsync(key, CancellationToken.None)); + } + + // Thread 1 + await (cache.UnlockAsync(key, CancellationToken.None)); + + Assert.DoesNotThrowAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should be unlocked"); + await (cache.UnlockAsync(key, CancellationToken.None)); + + await (cache.RemoveAsync(key, CancellationToken.None)); + } + [Test] public async Task TestClearAsync() { diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 04af24b3..935dbfa0 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -81,6 +81,53 @@ public void TestLockUnlock() } } + [Test] + public void TestConcurrentLockUnlock() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + const string value = "value"; + const string key = "keyToLock"; + + var cache = GetDefaultCache(); + + cache.Put(key, value); + Assert.That(cache.Get(key), Is.EqualTo(value), "Unable to retrieved cached object for key"); + + // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads + // Thread 1 + cache.Lock(key); + + // Thread 2 + try + { + Assert.Throws(() => cache.Lock(key), "The key should be locked"); + } + finally + { + cache.Unlock(key); + } + + // Thread 3 + try + { + Assert.Throws(() => cache.Lock(key), "The key should still be locked"); + } + finally + { + cache.Unlock(key); + } + + // Thread 1 + cache.Unlock(key); + + Assert.DoesNotThrow(() => cache.Lock(key), "The key should be unlocked"); + cache.Unlock(key); + + cache.Remove(key); + } + [Test] public void TestClear() { From 2350ae7d671e105a51e3eda1c4fe54825be67aa9 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 23 Jun 2018 22:15:58 +0200 Subject: [PATCH 11/24] Added performance tests --- AsyncGenerator.yml | 4 + .../Async/Caches/DistributedRedisCache.cs | 15 +- .../Async/RedisCachePerformanceFixture.cs | 118 +++++ .../RedisCachePerformanceFixture.cs | 417 ++++++++++++++++++ 4 files changed, 543 insertions(+), 11 deletions(-) create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index f743802d..efe8fa7c 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -450,6 +450,10 @@ withoutCancellationToken: - hasAttributeName: TestAttribute - hasAttributeName: TheoryAttribute + exceptionHandling: + catchMethodBody: + - all: true + result: false ignoreSearchForAsyncCounterparts: - name: Disassemble scanMethodBody: true diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs index 6f77fa7b..c3e6be2e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -24,17 +24,10 @@ public partial class DistributedRedisCache : ICache /// public Task GetAsync(object key, CancellationToken cancellationToken) { - try - { - // Use a random strategy to get the value. - // A real distributed cache should use a proper load balancing. - var strategy = _regionStrategies[_random.Next(0, _regionStrategies.Length - 1)]; - return strategy.GetAsync(key, cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); - } + // Use a random strategy to get the value. + // A real distributed cache should use a proper load balancing. + var strategy = _regionStrategies[_random.Next(0, _regionStrategies.Length - 1)]; + return strategy.GetAsync(key, cancellationToken); } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs new file mode 100644 index 00000000..010e0ca0 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + using System.Threading; + public partial class RedisCachePerformanceFixture : Fixture + { + + [Test] + public Task TestGetOperationAsync() + { + return TestOperationAsync("Get", true, (cache, key, _) => cache.GetAsync(key, CancellationToken.None)); + } + + [Test] + public Task TestGetManyOperationAsync() + { + return TestBatchOperationAsync("GetMany", true, (cache, keys, _) => cache.GetManyAsync(keys, CancellationToken.None)); + } + + [Test] + public Task TestGetOperationWithSlidingExpirationAsync() + { + var props = new Dictionary {{"sliding", "true"}}; + return TestOperationAsync("Get", true, (cache, key, _) => cache.GetAsync(key, CancellationToken.None), + caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); + } + + [Test] + public Task TestGetManyOperationWithSlidingExpirationAsync() + { + var props = new Dictionary {{"sliding", "true"}}; + return TestBatchOperationAsync("GetMany", true, (cache, keys, _) => cache.GetManyAsync(keys, CancellationToken.None), + caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); + } + + [Test] + public Task TestPutOperationAsync() + { + var props = new Dictionary {{"expiration", "0"}}; + return TestOperationAsync("Put", false, (cache, key, value) => cache.PutAsync(key, value, CancellationToken.None), + caches: new List {GetFastRedisCache(props)}); + } + + [Test] + public Task TestPutManyOperationAsync() + { + var props = new Dictionary {{"expiration", "0"}}; + return TestBatchOperationAsync("PutMany", false, (cache, keys, values) => cache.PutManyAsync(keys, values, CancellationToken.None), + caches: new List {GetFastRedisCache(props)}); + } + + [Test] + public Task TestPutOperationWithExpirationAsync() + { + return TestOperationAsync("Put", false, (cache, key, value) => cache.PutAsync(key, value, CancellationToken.None)); + } + + [Test] + public Task TestPutManyOperationWithExpirationAsync() + { + return TestBatchOperationAsync("PutMany", false, (cache, keys, values) => cache.PutManyAsync(keys, values, CancellationToken.None)); + } + + [Test] + public Task TestLockUnlockOperationAsync() + { + return TestOperationAsync("Lock/Unlock", true, async (cache, key, _) => + { + await (cache.LockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, CancellationToken.None)); + }); + } + + [Test] + public Task TestLockUnlockManyOperationAsync() + { + return TestBatchOperationAsync("LockMany/UnlockMany", true, async (cache, keys, _) => + { + var value = await (cache.LockManyAsync(keys, CancellationToken.None)); + await (cache.UnlockManyAsync(keys, value, CancellationToken.None)); + }); + } + + private async Task PutCacheDataAsync(ICache cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) + { + foreach (var pair in cacheData) + { + await (cache.PutAsync(pair.Key, pair.Value, cancellationToken)); + } + } + + private async Task RemoveCacheDataAsync(ICache cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) + { + foreach (var pair in cacheData) + { + await (cache.RemoveAsync(pair.Key, cancellationToken)); + } + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs new file mode 100644 index 00000000..516e7c1b --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + [TestFixture, Explicit] + public partial class RedisCachePerformanceFixture : Fixture + { + private const int RepeatTimes = 5; + private const int BatchSize = 20; + private const int CacheItems = 1000; + + protected override Func ProviderBuilder => + () => new RedisCacheProvider(); + + [Test] + public void TestGetOperation() + { + TestOperation("Get", true, (cache, key, _) => cache.Get(key)); + } + + [Test] + public void TestGetManyOperation() + { + TestBatchOperation("GetMany", true, (cache, keys, _) => cache.GetMany(keys)); + } + + [Test] + public void TestGetOperationWithSlidingExpiration() + { + var props = new Dictionary {{"sliding", "true"}}; + TestOperation("Get", true, (cache, key, _) => cache.Get(key), + caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); + } + + [Test] + public void TestGetManyOperationWithSlidingExpiration() + { + var props = new Dictionary {{"sliding", "true"}}; + TestBatchOperation("GetMany", true, (cache, keys, _) => cache.GetMany(keys), + caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); + } + + [Test] + public void TestPutOperation() + { + var props = new Dictionary {{"expiration", "0"}}; + TestOperation("Put", false, (cache, key, value) => cache.Put(key, value), + caches: new List {GetFastRedisCache(props)}); + } + + [Test] + public void TestPutManyOperation() + { + var props = new Dictionary {{"expiration", "0"}}; + TestBatchOperation("PutMany", false, (cache, keys, values) => cache.PutMany(keys, values), + caches: new List {GetFastRedisCache(props)}); + } + + [Test] + public void TestPutOperationWithExpiration() + { + TestOperation("Put", false, (cache, key, value) => cache.Put(key, value)); + } + + [Test] + public void TestPutManyOperationWithExpiration() + { + TestBatchOperation("PutMany", false, (cache, keys, values) => cache.PutMany(keys, values)); + } + + [Test] + public void TestLockUnlockOperation() + { + TestOperation("Lock/Unlock", true, (cache, key, _) => + { + cache.Lock(key); + cache.Unlock(key); + }); + } + + [Test] + public void TestLockUnlockManyOperation() + { + TestBatchOperation("LockMany/UnlockMany", true, (cache, keys, _) => + { + var value = cache.LockMany(keys); + cache.UnlockMany(keys, value); + }); + } + + private void TestBatchOperation(string operation, bool fillData, + Action keyValueAction, int? batchSize = null, + int? cacheItems = null, int? repeat = null, List caches = null) + { + TestOperation(operation, fillData, null, keyValueAction, batchSize, cacheItems, repeat, caches); + } + + private Task TestBatchOperationAsync(string operation, bool fillData, + Func keyValueAction, int? batchSize = null, + int? cacheItems = null, int? repeat = null, List caches = null) + { + return TestOperationAsync(operation, fillData, null, keyValueAction, batchSize, cacheItems, repeat, caches); + } + + private void TestOperation(string operation, bool fillData, + Action> keyValueAction, + int? cacheItems = null, int? repeat = null, List caches = null) + { + TestOperation(operation, fillData, keyValueAction, null, null, cacheItems, repeat, caches); + } + + private Task TestOperationAsync(string operation, bool fillData, + Func, Task> keyValueAction, + int? cacheItems = null, int? repeat = null, List caches = null) + { + return TestOperationAsync(operation, fillData, keyValueAction, null, null, cacheItems, repeat, caches); + } + + private void TestOperation(string operation, bool fillData, + Action> keyValueAction, + Action batchKeyValueAction, + int? batchSize, int? cacheItems = null, int? repeat = null, List caches = null + ) + { + caches = caches ?? new List {GetDefaultRedisCache(), GetFastRedisCache()}; + var cacheData = GetCacheData(cacheItems ?? CacheItems); + + if (fillData) + { + foreach (var cache in caches) + { + PutCacheData(cache, cacheData); + } + } + + foreach (var cache in caches) + { + var repeatPolicy = new CacheOperationRepeatPolicy(operation, cache, repeat ?? RepeatTimes, cacheData); + if (keyValueAction != null) + { + repeatPolicy.Execute(keyValueAction); + } + else + { + repeatPolicy.BatchExecute(batchKeyValueAction, batchSize ?? BatchSize); + } + } + + foreach (var cache in caches) + { + RemoveCacheData(cache, cacheData); + } + } + + private async Task TestOperationAsync(string operation, bool fillData, + Func, Task> keyValueAction, + Func batchKeyValueAction, + int? batchSize, int? cacheItems = null, int? repeat = null, List caches = null + ) + { + caches = caches ?? new List { GetDefaultRedisCache(), GetFastRedisCache() }; + var cacheData = GetCacheData(cacheItems ?? CacheItems); + + if (fillData) + { + foreach (var cache in caches) + { + PutCacheData(cache, cacheData); + } + } + + foreach (var cache in caches) + { + var repeatPolicy = new CacheOperationRepeatPolicy(operation, cache, repeat ?? RepeatTimes, cacheData); + if (keyValueAction != null) + { + await repeatPolicy.ExecuteAsync(keyValueAction); + } + else + { + await repeatPolicy.BatchExecuteAsync(batchKeyValueAction, batchSize ?? BatchSize); + } + } + + foreach (var cache in caches) + { + RemoveCacheData(cache, cacheData); + } + } + + private void PutCacheData(ICache cache, Dictionary> cacheData) + { + foreach (var pair in cacheData) + { + cache.Put(pair.Key, pair.Value); + } + } + + private void RemoveCacheData(ICache cache, Dictionary> cacheData) + { + foreach (var pair in cacheData) + { + cache.Remove(pair.Key); + } + } + + private Dictionary> GetCacheData(int numberOfKeys) + { + var keyValues = new Dictionary>(); + for (var i = 0; i < numberOfKeys; i++) + { + keyValues.Add( + new CacheKey((long) i, NHibernateUtil.Int64, nameof(GetCacheData), null), + new List + { + i, + string.Join("", Enumerable.Repeat(i, 30)), + Enumerable.Repeat((byte) i, 30).ToArray(), + null, + DateTime.Now, + i / 4.5, + Guid.NewGuid() + } + ); + } + + return keyValues; + } + + private RedisCache GetFastRedisCache(Dictionary properties = null) + { + var props = GetDefaultProperties(); + foreach (var property in properties ?? new Dictionary()) + { + props[property.Key] = property.Value; + } + props["strategy"] = typeof(FastRegionStrategy).AssemblyQualifiedName; + return (RedisCache)DefaultProvider.BuildCache(DefaultRegion, props); + } + + private RedisCache GetDefaultRedisCache(Dictionary properties = null) + { + var props = GetDefaultProperties(); + foreach (var property in properties ?? new Dictionary()) + { + props[property.Key] = property.Value; + } + return (RedisCache) DefaultProvider.BuildCache(DefaultRegion, props); + } + } + + public class CacheOperationRepeatPolicy + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(CacheOperationRepeatPolicy)); + private readonly string _operation; + private readonly RedisCache _cache; + private readonly int _repeatTimes; + private readonly Dictionary> _cacheData; + + public CacheOperationRepeatPolicy(string operation, RedisCache cache, int repeatTimes, Dictionary> cacheData) + { + _operation = operation; + _cache = cache; + _repeatTimes = repeatTimes; + _cacheData = cacheData; + } + + public void BatchExecute(Action keyValueAction, int batchSize) + { + var batchKeys = new List(); + var batchValues = new List(); + // Cold start + Iterate(); + + var timer = new Stopwatch(); + var result = new long[_repeatTimes]; + for (var i = 0; i < _repeatTimes; i++) + { + timer.Restart(); + Iterate(); + timer.Stop(); + result[i] = timer.ElapsedMilliseconds; + } + LogResult(result, batchSize); + + void Iterate() + { + foreach (var pair in _cacheData) + { + if (batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) + { + keyValueAction(_cache, batchKeys.ToArray(), batchValues.ToArray()); + batchKeys.Clear(); + batchValues.Clear(); + } + batchKeys.Add(pair.Key); + batchValues.Add(pair.Value); + } + + if (batchKeys.Count == 0) + { + return; + } + keyValueAction(_cache, batchKeys.ToArray(), batchValues.ToArray()); + batchKeys.Clear(); + batchValues.Clear(); + } + } + + public async Task BatchExecuteAsync(Func keyValueFunc, int batchSize) + { + var batchKeys = new List(); + var batchValues = new List(); + // Cold start + await Iterate(); + + var timer = new Stopwatch(); + var result = new long[_repeatTimes]; + for (var i = 0; i < _repeatTimes; i++) + { + timer.Restart(); + await Iterate(); + timer.Stop(); + result[i] = timer.ElapsedMilliseconds; + } + LogResult(result, batchSize); + + async Task Iterate() + { + foreach (var pair in _cacheData) + { + if (batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) + { + await keyValueFunc(_cache, batchKeys.ToArray(), batchValues.ToArray()); + batchKeys.Clear(); + batchValues.Clear(); + } + batchKeys.Add(pair.Key); + batchValues.Add(pair.Value); + } + + if (batchKeys.Count == 0) + { + return; + } + await keyValueFunc(_cache, batchKeys.ToArray(), batchValues.ToArray()); + batchKeys.Clear(); + batchValues.Clear(); + } + } + + public void Execute(Action> keyValueAction) + { + // Cold start + foreach (var pair in _cacheData) + { + keyValueAction(_cache, pair.Key, pair.Value); + } + + var timer = new Stopwatch(); + var result = new long[_repeatTimes]; + for (var i = 0; i < _repeatTimes; i++) + { + timer.Restart(); + foreach (var pair in _cacheData) + { + keyValueAction(_cache, pair.Key, pair.Value); + } + timer.Stop(); + result[i] = timer.ElapsedMilliseconds; + } + LogResult(result, 1); + } + + public async Task ExecuteAsync(Func, Task> keyValueFunc) + { + // Cold start + foreach (var pair in _cacheData) + { + await keyValueFunc(_cache, pair.Key, pair.Value); + } + + var timer = new Stopwatch(); + var result = new long[_repeatTimes]; + for (var i = 0; i < _repeatTimes; i++) + { + timer.Restart(); + foreach (var pair in _cacheData) + { + await keyValueFunc(_cache, pair.Key, pair.Value); + } + timer.Stop(); + result[i] = timer.ElapsedMilliseconds; + } + LogResult(result, 1); + } + + private void LogResult(long[] result, int batchSize) + { + Log.Info( + $"{_operation} operation for {_cacheData.Count} keys with region strategy {_cache.RegionStrategy.GetType().Name}:{Environment.NewLine}" + + $"Total iterations: {_repeatTimes}{Environment.NewLine}" + + $"Batch size: {batchSize}{Environment.NewLine}" + + $"Times per iteration {string.Join(",", result.Select(o => $"{o}ms"))}{Environment.NewLine}" + + $"Average {result.Average()}ms" + + ); + } + } +} From e6653bb2d6a316acef7b9e2da6d91fc5d08b06ef Mon Sep 17 00:00:00 2001 From: maca88 Date: Sun, 24 Jun 2018 13:44:58 +0200 Subject: [PATCH 12/24] Disabled using hash codes in cache keys by default --- .../Async/RedisCacheFixture.cs | 24 +++++++++---------- .../RedisCacheFixture.cs | 24 +++++++++---------- .../RedisCacheConfiguration.cs | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 280f7e4c..63fb9ee2 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -260,7 +260,7 @@ public async Task TestNHibernateCachedItemSerializationAsync() } [Test] - public async Task TestEqualObjectsWithDifferentHashCodeDefaultConfigurationAsync() + public async Task TestEqualObjectsWithDifferentHashCodeAsync() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -270,12 +270,12 @@ public async Task TestEqualObjectsWithDifferentHashCodeDefaultConfigurationAsync await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "The hash code should be used in the cache key"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } [Test] - public async Task TestEqualObjectsWithDifferentHashCodeGlobalConfigurationAsync() + public async Task TestEqualObjectsWithDifferentHashCodeAndUseHashCodeGlobalConfigurationAsync() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -283,18 +283,18 @@ public async Task TestEqualObjectsWithDifferentHashCodeGlobalConfigurationAsync( var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "false"; + props[RedisEnvironment.UseHashCode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "The hash code should be used in the cache key"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } [Test] - public async Task TestEqualObjectsWithDifferentHashCodeRegionConfigurationAsync() + public async Task TestEqualObjectsWithDifferentHashCodeAndUseHashCodeRegionConfigurationAsync() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -303,12 +303,12 @@ public async Task TestEqualObjectsWithDifferentHashCodeRegionConfigurationAsync( var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); cacheProvider.Start(props); - props["hashcode"] = "false"; + props["hashcode"] = "true"; var cache = cacheProvider.BuildCache(DefaultRegion, props); await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "The hash code should be used in the cache key"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } @@ -323,12 +323,12 @@ public async Task TestNonEqualObjectsWithEqualToStringAsync() await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } [Test] - public async Task TestNonEqualObjectsWithEqualToStringNoHashCodeAsync() + public async Task TestNonEqualObjectsWithEqualToStringUseHashCodeAsync() { var value = "value"; var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); @@ -336,13 +336,13 @@ public async Task TestNonEqualObjectsWithEqualToStringNoHashCodeAsync() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "false"; + props[RedisEnvironment.UseHashCode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index 7b0a0a89..7a8607f8 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -299,7 +299,7 @@ public override int GetHashCode() } [Test] - public void TestEqualObjectsWithDifferentHashCodeDefaultConfiguration() + public void TestEqualObjectsWithDifferentHashCode() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -309,12 +309,12 @@ public void TestEqualObjectsWithDifferentHashCodeDefaultConfiguration() cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.Null, "The hash code should be used in the cache key"); + Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); cache.Remove(obj1); } [Test] - public void TestEqualObjectsWithDifferentHashCodeGlobalConfiguration() + public void TestEqualObjectsWithDifferentHashCodeAndUseHashCodeGlobalConfiguration() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -322,18 +322,18 @@ public void TestEqualObjectsWithDifferentHashCodeGlobalConfiguration() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "false"; + props[RedisEnvironment.UseHashCode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(cache.Get(obj2), Is.Null, "The hash code should be used in the cache key"); cache.Remove(obj1); } [Test] - public void TestEqualObjectsWithDifferentHashCodeRegionConfiguration() + public void TestEqualObjectsWithDifferentHashCodeAndUseHashCodeRegionConfiguration() { var value = "value"; var obj1 = new CustomCacheKey(1, "test", false); @@ -342,12 +342,12 @@ public void TestEqualObjectsWithDifferentHashCodeRegionConfiguration() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); cacheProvider.Start(props); - props["hashcode"] = "false"; + props["hashcode"] = "true"; var cache = cacheProvider.BuildCache(DefaultRegion, props); cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(cache.Get(obj2), Is.Null, "The hash code should be used in the cache key"); cache.Remove(obj1); } @@ -393,12 +393,12 @@ public void TestNonEqualObjectsWithEqualToString() cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); cache.Remove(obj1); } [Test] - public void TestNonEqualObjectsWithEqualToStringNoHashCode() + public void TestNonEqualObjectsWithEqualToStringUseHashCode() { var value = "value"; var obj1 = new CustomCacheKey(new ObjectEqualToString(1), "test", true); @@ -406,13 +406,13 @@ public void TestNonEqualObjectsWithEqualToStringNoHashCode() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "false"; + props[RedisEnvironment.UseHashCode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.EqualTo(value), "Unable to retrieved cached object for key obj2"); + Assert.That(cache.Get(obj2), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); cache.Remove(obj1); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index d817260b..1ecaa455 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -42,7 +42,7 @@ public class RedisCacheConfiguration /// /// Whether the hash code of the key should be added to the cache key. /// - public bool DefaultUseHashCode { get; set; } = true; + public bool DefaultUseHashCode { get; set; } /// /// The default expiration time for the keys to expire. From b05fba4088862b8b96350c08cf9c98d87a046fee Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 7 Jul 2018 09:42:14 +0200 Subject: [PATCH 13/24] Renamed UseHashCode option to AppendHashcode to align with CoreDistributedCache --- .../Async/RedisCacheFixture.cs | 6 +++--- .../RedisCacheFixture.cs | 6 +++--- .../AbstractRegionStrategy.cs | 6 +++--- .../DefaultRegionStrategy.cs | 2 +- .../RedisCacheConfiguration.cs | 2 +- .../RedisCacheProvider.cs | 12 ++++++------ .../RedisCacheRegionConfiguration.cs | 4 ++-- .../RedisEnvironment.cs | 2 +- .../RedisSectionHandler.cs | 2 +- .../NHibernate.Caches.StackExRedis/RegionConfig.cs | 8 ++++---- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 63fb9ee2..3a532cc4 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -283,7 +283,7 @@ public async Task TestEqualObjectsWithDifferentHashCodeAndUseHashCodeGlobalConfi var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "true"; + props[RedisEnvironment.AppendHashcode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); @@ -303,7 +303,7 @@ public async Task TestEqualObjectsWithDifferentHashCodeAndUseHashCodeRegionConfi var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); cacheProvider.Start(props); - props["hashcode"] = "true"; + props["append-hashcode"] = "true"; var cache = cacheProvider.BuildCache(DefaultRegion, props); await (cache.PutAsync(obj1, value, CancellationToken.None)); @@ -336,7 +336,7 @@ public async Task TestNonEqualObjectsWithEqualToStringUseHashCodeAsync() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "true"; + props[RedisEnvironment.AppendHashcode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index 7a8607f8..07e729d0 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -322,7 +322,7 @@ public void TestEqualObjectsWithDifferentHashCodeAndUseHashCodeGlobalConfigurati var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "true"; + props[RedisEnvironment.AppendHashcode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); @@ -342,7 +342,7 @@ public void TestEqualObjectsWithDifferentHashCodeAndUseHashCodeRegionConfigurati var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); cacheProvider.Start(props); - props["hashcode"] = "true"; + props["append-hashcode"] = "true"; var cache = cacheProvider.BuildCache(DefaultRegion, props); cache.Put(obj1, value); @@ -406,7 +406,7 @@ public void TestNonEqualObjectsWithEqualToStringUseHashCode() var props = GetDefaultProperties(); var cacheProvider = ProviderBuilder(); - props[RedisEnvironment.UseHashCode] = "true"; + props[RedisEnvironment.AppendHashcode] = "true"; cacheProvider.Start(props); var cache = cacheProvider.BuildCache(DefaultRegion, props); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index bd94d506..ba0c6eb7 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -63,7 +63,7 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, RegionName = configuration.RegionName; Expiration = configuration.Expiration; UseSlidingExpiration = configuration.UseSlidingExpiration; - UseHashCode = configuration.UseHashCode; + AppendHashcode = configuration.AppendHashcode; RegionKey = configuration.RegionKey; ConnectionMultiplexer = connectionMultiplexer; Database = connectionMultiplexer.GetDatabase(configuration.Database); @@ -148,7 +148,7 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, /// /// Whether the hash code of the key should be added to the cache key. /// - public bool UseHashCode { get; } + public bool AppendHashcode { get; } /// /// Is the expiration enabled? @@ -497,7 +497,7 @@ protected virtual string GetCacheKey(object value) { // Hash tag (wrap with curly brackets) the region key in order to ensure that all region keys // will be located on the same server, when a Redis cluster is used. - return UseHashCode + return AppendHashcode ? string.Concat("{", RegionKey, "}:", value.ToString(), "@", value.GetHashCode()) : string.Concat("{", RegionKey, "}:", value.ToString()); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index 828aa0d0..f45f5b3e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -296,7 +296,7 @@ protected override RedisValue[] GetAdditionalValues() /// protected override string GetCacheKey(object value) { - return UseHashCode + return AppendHashcode ? string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString(), "@", value.GetHashCode()) : string.Concat("{", RegionKey, "}-", _currentVersion, ":", value.ToString()); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index 1ecaa455..04b0001b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -42,7 +42,7 @@ public class RedisCacheConfiguration /// /// Whether the hash code of the key should be added to the cache key. /// - public bool DefaultUseHashCode { get; set; } + public bool DefaultAppendHashcode { get; set; } /// /// The default expiration time for the keys to expire. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index 794a57e6..45143c3e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -165,9 +165,9 @@ private RedisCacheRegionConfiguration BuildRegionConfiguration(RegionConfig regi regionConfig.UseSlidingExpiration ?? GetBoolean(RedisEnvironment.UseSlidingExpiration, properties, _defaultCacheConfiguration.DefaultUseSlidingExpiration)); - config.UseHashCode = GetBoolean("hashcode", properties, - regionConfig.UseHashCode ?? GetBoolean(RedisEnvironment.UseHashCode, properties, - _defaultCacheConfiguration.DefaultUseHashCode)); + config.AppendHashcode = GetBoolean("append-hashcode", properties, + regionConfig.AppendHashcode ?? GetBoolean(RedisEnvironment.AppendHashcode, properties, + _defaultCacheConfiguration.DefaultAppendHashcode)); Log.Debug("Use sliding expiration for region {0}: {1}", regionConfig.Region, config.UseSlidingExpiration); @@ -206,9 +206,9 @@ private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary /// Whether the hash code of the key should be added to the cache key. /// - public bool UseHashCode { get; set; } + public bool AppendHashcode { get; set; } /// /// The to be used. @@ -93,7 +93,7 @@ public override string ToString() sb.AppendFormat("Expiration={0}s", Expiration.TotalSeconds); sb.AppendFormat("Database={0}", Database); sb.AppendFormat("UseSlidingExpiration={0}", UseSlidingExpiration); - sb.AppendFormat("UseHashCode={0}", UseHashCode); + sb.AppendFormat("AppendHashcode={0}", AppendHashcode); sb.AppendFormat("RegionStrategy={0}", RegionStrategy); sb.AppendFormat("Serializer={0}", Serializer); sb.AppendFormat("LockConfiguration=({0})", LockConfiguration); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs index a70a9f4a..5d89e8dd 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs @@ -46,7 +46,7 @@ public static class RedisEnvironment /// /// Whether the hash code of the key should be added to the cache key. /// - public const string UseHashCode = "cache.use_hash_code"; + public const string AppendHashcode = "cache.append_hashcode"; /// /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs index 0ab4b9f3..a8e14252 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs @@ -33,7 +33,7 @@ public object Create(object parent, object configContext, XmlNode section) GetBoolean(node, "sliding"), GetInteger(node, "database"), GetType(node, "strategy"), - GetBoolean(node, "hashcode") + GetBoolean(node, "append-hashcode") )); } else diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs index a2f95b37..29b7fa13 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs @@ -27,16 +27,16 @@ public RegionConfig(string region) : this(region, null, null, null, null, null) /// Whether the expiration should be sliding or not. /// The database for the region. /// The strategy for the region. - /// Whether the hash code of the key should be added to the cache key. + /// Whether the hash code of the key should be added to the cache key. public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpiration, int? database, System.Type regionStrategy, - bool? useHashCode) + bool? appendHashcode) { Region = region; Expiration = expiration; UseSlidingExpiration = useSlidingExpiration; Database = database; RegionStrategy = regionStrategy; - UseHashCode = useHashCode; + AppendHashcode = appendHashcode; } /// @@ -67,6 +67,6 @@ public RegionConfig(string region, TimeSpan? expiration, bool? useSlidingExpirat /// /// Whether the hash code of the key should be added to the cache key. /// - public bool? UseHashCode { get; } + public bool? AppendHashcode { get; } } } From 2c4cafa2c8416472946e918720e4e65e496a029f Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 7 Jul 2018 09:54:34 +0200 Subject: [PATCH 14/24] Updated the IRedisSerializer methods to be easier replaced with CacheSerializeBase --- .../AbstractRegionStrategy.cs | 2 +- .../Async/AbstractRegionStrategy.cs | 2 +- .../BinaryRedisSerializer.cs | 9 ++------- .../NHibernate.Caches.StackExRedis/IRedisSerializer.cs | 5 +++-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index ba0c6eb7..561adf39 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -251,7 +251,7 @@ public virtual void Put(object key, object value) throw new ArgumentNullException(nameof(value)); } var cacheKey = GetCacheKey(key); - var serializedValue = Serializer.Serialize(value); + RedisValue serializedValue = Serializer.Serialize(value); if (string.IsNullOrEmpty(PutScript)) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index fec5f481..b4c1493a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -129,7 +129,7 @@ public virtual Task PutAsync(object key, object value, CancellationToken cancell async Task InternalPutAsync() { var cacheKey = GetCacheKey(key); - var serializedValue = Serializer.Serialize(value); + RedisValue serializedValue = Serializer.Serialize(value); if (string.IsNullOrEmpty(PutScript)) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs index 2ca962f1..61ef756d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs @@ -10,7 +10,7 @@ namespace NHibernate.Caches.StackExRedis public class BinaryRedisSerializer : IRedisSerializer { /// - public RedisValue Serialize(object value) + public byte[] Serialize(object value) { var serializer = new BinaryFormatter(); using (var stream = new MemoryStream()) @@ -21,13 +21,8 @@ public RedisValue Serialize(object value) } /// - public object Deserialize(RedisValue value) + public object Deserialize(byte[] value) { - if (value.IsNull) - { - return null; - } - var serializer = new BinaryFormatter(); using (var stream = new MemoryStream(value)) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs index 29e957a4..91c0d3c8 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs @@ -5,6 +5,7 @@ namespace NHibernate.Caches.StackExRedis /// /// Defines methods for serializing and deserializing objects that will be stored/retrieved for Redis. /// + // TODO: Remove and use CacheSerializeBase public interface IRedisSerializer { /// @@ -12,13 +13,13 @@ public interface IRedisSerializer /// /// The object to serialize. /// A serialized that can be stored into Redis. - RedisValue Serialize(object value); + byte[] Serialize(object value); /// /// Deserialize the that was retrieved from Redis. /// /// The value to deserialize. /// The object that was serialized. - object Deserialize(RedisValue value); + object Deserialize(byte[] value); } } From 1fd0a3b112c8babb733d28d6a853cec112034fa6 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 7 Jul 2018 11:54:21 +0200 Subject: [PATCH 15/24] Added logging to region strategies --- .../AbstractRegionStrategy.cs | 75 +++++++++++++--- .../Async/AbstractRegionStrategy.cs | 87 ++++++++++++++----- .../Async/DefaultRegionStrategy.cs | 33 +++++++ .../DefaultRegionStrategy.cs | 37 +++++++- 4 files changed, 199 insertions(+), 33 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index 561adf39..efff3ebd 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -171,7 +171,9 @@ public virtual object Get(object key) { return null; } + var cacheKey = GetCacheKey(key); + Log.Debug("Fetching object with key: '{0}'.", cacheKey); RedisValue result; if (string.IsNullOrEmpty(GetScript)) { @@ -179,7 +181,7 @@ public virtual object Get(object key) } else { - var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); var values = AppendAdditionalValues(new RedisValue[] { UseSlidingExpiration && ExpirationEnabled, @@ -188,6 +190,7 @@ public virtual object Get(object key) var results = (RedisValue[]) Database.ScriptEvaluate(GetScript, keys, values); result = results[0]; } + return result.IsNullOrEmpty ? null : Serializer.Deserialize(result); } @@ -202,11 +205,15 @@ public virtual object[] GetMany(object[] keys) { return null; } + var cacheKeys = new RedisKey[keys.Length]; + Log.Debug("Fetching {0} objects...", keys.Length); for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Fetching object with key: '{0}'.", cacheKeys[i]); } + RedisValue[] results; if (string.IsNullOrEmpty(GetManyScript)) { @@ -232,6 +239,7 @@ public virtual object[] GetMany(object[] keys) objects[i] = Serializer.Deserialize(result); } } + return objects; } @@ -246,11 +254,14 @@ public virtual void Put(object key, object value) { throw new ArgumentNullException(nameof(key)); } + if (value == null) { throw new ArgumentNullException(nameof(value)); } + var cacheKey = GetCacheKey(key); + Log.Debug("Putting object with key: '{0}'.", cacheKey); RedisValue serializedValue = Serializer.Serialize(value); if (string.IsNullOrEmpty(PutScript)) @@ -264,7 +275,7 @@ public virtual void Put(object key, object value) { serializedValue, ExpirationEnabled, - (long)Expiration.TotalMilliseconds + (long) Expiration.TotalMilliseconds }); Database.ScriptEvaluate(PutScript, keys, values); } @@ -280,29 +291,36 @@ public virtual void PutMany(object[] keys, object[] values) { throw new ArgumentNullException(nameof(keys)); } + if (values == null) { throw new ArgumentNullException(nameof(values)); } + if (keys.Length != values.Length) { throw new ArgumentException($"Length of {nameof(keys)} array does not match with {nameof(values)} array."); } + + Log.Debug("Putting {0} objects...", keys.Length); if (string.IsNullOrEmpty(PutManyScript)) { if (ExpirationEnabled) { - throw new NotSupportedException($"{nameof(PutMany)} operation is not supported."); + throw new NotSupportedException($"{nameof(PutMany)} operation with expiration is not supported."); } - var pairs = new KeyValuePair[keys.Length]; + + var pairs = new KeyValuePair[keys.Length]; for (var i = 0; i < keys.Length; i++) { pairs[i] = new KeyValuePair(GetCacheKey(keys[i]), Serializer.Serialize(values[i])); + Log.Debug("Putting object with key: '{0}'.", pairs[i].Key); } + Database.StringSet(pairs); return; } - + var cacheKeys = new RedisKey[keys.Length]; var cacheValues = new RedisValue[keys.Length + 2]; @@ -310,7 +328,9 @@ public virtual void PutMany(object[] keys, object[] values) { cacheKeys[i] = GetCacheKey(keys[i]); cacheValues[i] = Serializer.Serialize(values[i]); + Log.Debug("Putting object with key: '{0}'.", cacheKeys[i]); } + cacheKeys = AppendAdditionalKeys(cacheKeys); cacheValues[keys.Length] = ExpirationEnabled; cacheValues[keys.Length + 1] = (long) Expiration.TotalMilliseconds; @@ -330,11 +350,13 @@ public virtual bool Remove(object key) } var cacheKey = GetCacheKey(key); + Log.Debug("Removing object with key: '{0}'.", cacheKey); if (string.IsNullOrEmpty(RemoveScript)) { return Database.KeyDelete(cacheKey); } - var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); var values = GetAdditionalValues(); var results = (RedisValue[]) Database.ScriptEvaluate(RemoveScript, keys, values); return (bool) results[0]; @@ -350,15 +372,20 @@ public virtual long RemoveMany(object[] keys) { throw new ArgumentNullException(nameof(keys)); } + + Log.Debug("Removing {0} objects...", keys.Length); var cacheKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Removing object with key: '{0}'.", cacheKeys[i]); } + if (string.IsNullOrEmpty(RemoveManyScript)) { return Database.KeyDelete(cacheKeys); } + cacheKeys = AppendAdditionalKeys(cacheKeys); var results = (RedisValue[]) Database.ScriptEvaluate(RemoveManyScript, cacheKeys, GetAdditionalValues()); return (long) results[0]; @@ -375,7 +402,9 @@ public virtual string Lock(object key) { throw new ArgumentNullException(nameof(key)); } + var cacheKey = GetCacheKey(key); + Log.Debug("Locking object with key: '{0}'.", cacheKey); var lockValue = _keyLocker.Lock(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues()); _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => @@ -400,15 +429,20 @@ public virtual string LockMany(object[] keys) { throw new ArgumentNullException(nameof(keys)); } + if (string.IsNullOrEmpty(LockManyScript)) { throw new NotSupportedException($"{nameof(LockMany)} operation is not supported."); } + + Log.Debug("Locking {0} objects...", keys.Length); var cacheKeys = new string[keys.Length]; for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Locking object with key: '{0}'.", cacheKeys[i]); } + return _keyLocker.LockMany(cacheKeys, LockManyScript, GetAdditionalKeys(), GetAdditionalValues()); } @@ -423,7 +457,9 @@ public virtual bool Unlock(object key) { throw new ArgumentNullException(nameof(key)); } + var cacheKey = GetCacheKey(key); + Log.Debug("Unlocking object with key: '{0}'.", cacheKey); if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) { @@ -431,7 +467,10 @@ public virtual bool Unlock(object key) $"Calling {nameof(Unlock)} method for key:'{cacheKey}' that was not locked with {nameof(Lock)} method before."); return false; } - return _keyLocker.Unlock(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues()); + + var unlocked = _keyLocker.Unlock(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues()); + Log.Debug("Unlock key '{0}' result: {1}", cacheKey, unlocked); + return unlocked; } /// @@ -446,17 +485,29 @@ public virtual int UnlockMany(object[] keys, string lockValue) { throw new ArgumentNullException(nameof(keys)); } + if (string.IsNullOrEmpty(UnlockManyScript)) { throw new NotSupportedException($"{nameof(UnlockMany)} operation is not supported."); } + + Log.Debug("Unlocking {0} objects...", keys.Length); var cacheKeys = new string[keys.Length]; for (var i = 0; i < keys.Length; i++) { - var cacheKey = GetCacheKey(keys[i]); - cacheKeys[i] = cacheKey; + cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Unlocking object with key: '{0}'.", cacheKeys[i]); } - return _keyLocker.UnlockMany(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues()); + + var unlockedKeys = + _keyLocker.UnlockMany(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues()); + if (Log.IsDebugEnabled()) + { + Log.Debug("Number of unlocked objects with keys ({0}): {1}", string.Join(",", cacheKeys.Select(o => $"'{o}'")), + unlockedKeys); + } + + return unlockedKeys; } /// @@ -513,11 +564,13 @@ protected RedisValue[] AppendAdditionalValues(RedisValue[] values) { return GetAdditionalValues(); } + var additionalValues = GetAdditionalValues(); if (additionalValues == null) { return values; } + var combinedValues = new RedisValue[values.Length + additionalValues.Length]; values.CopyTo(combinedValues, 0); additionalValues.CopyTo(combinedValues, values.Length); @@ -535,11 +588,13 @@ protected RedisKey[] AppendAdditionalKeys(RedisKey[] keys) { return GetAdditionalKeys(); } + var additionalKeys = GetAdditionalKeys(); if (additionalKeys == null) { return keys; } + var combinedKeys = new RedisKey[keys.Length + additionalKeys.Length]; keys.CopyTo(combinedKeys, 0); additionalKeys.CopyTo(combinedKeys, keys.Length); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index b4c1493a..b0f28ba2 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -35,7 +35,9 @@ public virtual async Task GetAsync(object key, CancellationToken cancell { return null; } + var cacheKey = GetCacheKey(key); + Log.Debug("Fetching object with key: '{0}'.", cacheKey); RedisValue result; if (string.IsNullOrEmpty(GetScript)) { @@ -44,7 +46,7 @@ public virtual async Task GetAsync(object key, CancellationToken cancell } else { - var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); var values = AppendAdditionalValues(new RedisValue[] { UseSlidingExpiration && ExpirationEnabled, @@ -54,6 +56,7 @@ public virtual async Task GetAsync(object key, CancellationToken cancell var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(GetScript, keys, values)).ConfigureAwait(false); result = results[0]; } + return result.IsNullOrEmpty ? null : Serializer.Deserialize(result); } @@ -70,11 +73,15 @@ public virtual async Task GetManyAsync(object[] keys, CancellationToke { return null; } + var cacheKeys = new RedisKey[keys.Length]; + Log.Debug("Fetching {0} objects...", keys.Length); for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Fetching object with key: '{0}'.", cacheKeys[i]); } + RedisValue[] results; if (string.IsNullOrEmpty(GetManyScript)) { @@ -102,6 +109,7 @@ public virtual async Task GetManyAsync(object[] keys, CancellationToke objects[i] = Serializer.Deserialize(result); } } + return objects; } @@ -117,6 +125,7 @@ public virtual Task PutAsync(object key, object value, CancellationToken cancell { throw new ArgumentNullException(nameof(key)); } + if (value == null) { throw new ArgumentNullException(nameof(value)); @@ -128,7 +137,9 @@ public virtual Task PutAsync(object key, object value, CancellationToken cancell return InternalPutAsync(); async Task InternalPutAsync() { + var cacheKey = GetCacheKey(key); + Log.Debug("Putting object with key: '{0}'.", cacheKey); RedisValue serializedValue = Serializer.Serialize(value); if (string.IsNullOrEmpty(PutScript)) @@ -143,7 +154,7 @@ async Task InternalPutAsync() { serializedValue, ExpirationEnabled, - (long)Expiration.TotalMilliseconds + (long) Expiration.TotalMilliseconds }); cancellationToken.ThrowIfCancellationRequested(); await (Database.ScriptEvaluateAsync(PutScript, keys, values)).ConfigureAwait(false); @@ -162,10 +173,12 @@ public virtual Task PutManyAsync(object[] keys, object[] values, CancellationTok { throw new ArgumentNullException(nameof(keys)); } + if (values == null) { throw new ArgumentNullException(nameof(values)); } + if (keys.Length != values.Length) { throw new ArgumentException($"Length of {nameof(keys)} array does not match with {nameof(values)} array."); @@ -177,22 +190,27 @@ public virtual Task PutManyAsync(object[] keys, object[] values, CancellationTok return InternalPutManyAsync(); async Task InternalPutManyAsync() { + + Log.Debug("Putting {0} objects...", keys.Length); if (string.IsNullOrEmpty(PutManyScript)) { if (ExpirationEnabled) { - throw new NotSupportedException($"{nameof(PutManyAsync)} operation is not supported."); + throw new NotSupportedException($"{nameof(PutManyAsync)} operation with expiration is not supported."); } - var pairs = new KeyValuePair[keys.Length]; + + var pairs = new KeyValuePair[keys.Length]; for (var i = 0; i < keys.Length; i++) { pairs[i] = new KeyValuePair(GetCacheKey(keys[i]), Serializer.Serialize(values[i])); + Log.Debug("Putting object with key: '{0}'.", pairs[i].Key); } cancellationToken.ThrowIfCancellationRequested(); + await (Database.StringSetAsync(pairs)).ConfigureAwait(false); return; } - + var cacheKeys = new RedisKey[keys.Length]; var cacheValues = new RedisValue[keys.Length + 2]; @@ -200,7 +218,9 @@ async Task InternalPutManyAsync() { cacheKeys[i] = GetCacheKey(keys[i]); cacheValues[i] = Serializer.Serialize(values[i]); + Log.Debug("Putting object with key: '{0}'.", cacheKeys[i]); } + cacheKeys = AppendAdditionalKeys(cacheKeys); cacheValues[keys.Length] = ExpirationEnabled; cacheValues[keys.Length + 1] = (long) Expiration.TotalMilliseconds; @@ -230,12 +250,14 @@ async Task InternalRemoveAsync() { var cacheKey = GetCacheKey(key); + Log.Debug("Removing object with key: '{0}'.", cacheKey); if (string.IsNullOrEmpty(RemoveScript)) { cancellationToken.ThrowIfCancellationRequested(); return await (Database.KeyDeleteAsync(cacheKey)).ConfigureAwait(false); } - var keys = AppendAdditionalKeys(new RedisKey[] { cacheKey }); + + var keys = AppendAdditionalKeys(new RedisKey[] {cacheKey}); var values = GetAdditionalValues(); cancellationToken.ThrowIfCancellationRequested(); var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(RemoveScript, keys, values)).ConfigureAwait(false); @@ -261,16 +283,21 @@ public virtual Task RemoveManyAsync(object[] keys, CancellationToken cance return InternalRemoveManyAsync(); async Task InternalRemoveManyAsync() { + + Log.Debug("Removing {0} objects...", keys.Length); var cacheKeys = new RedisKey[keys.Length]; for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Removing object with key: '{0}'.", cacheKeys[i]); } + if (string.IsNullOrEmpty(RemoveManyScript)) { cancellationToken.ThrowIfCancellationRequested(); return await (Database.KeyDeleteAsync(cacheKeys)).ConfigureAwait(false); } + cacheKeys = AppendAdditionalKeys(cacheKeys); cancellationToken.ThrowIfCancellationRequested(); var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(RemoveManyScript, cacheKeys, GetAdditionalValues())).ConfigureAwait(false); @@ -297,7 +324,9 @@ public virtual Task LockAsync(object key, CancellationToken cancellation return InternalLockAsync(); async Task InternalLockAsync() { + var cacheKey = GetCacheKey(key); + Log.Debug("Locking object with key: '{0}'.", cacheKey); var lockValue = await (_keyLocker.LockAsync(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => @@ -324,6 +353,7 @@ public virtual Task LockManyAsync(object[] keys, CancellationToken cance { throw new ArgumentNullException(nameof(keys)); } + if (string.IsNullOrEmpty(LockManyScript)) { throw new NotSupportedException($"{nameof(LockManyAsync)} operation is not supported."); @@ -334,11 +364,15 @@ public virtual Task LockManyAsync(object[] keys, CancellationToken cance } try { + + Log.Debug("Locking {0} objects...", keys.Length); var cacheKeys = new string[keys.Length]; for (var i = 0; i < keys.Length; i++) { cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Locking object with key: '{0}'.", cacheKeys[i]); } + return _keyLocker.LockManyAsync(cacheKeys, LockManyScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); } catch (Exception ex) @@ -363,21 +397,23 @@ public virtual Task UnlockAsync(object key, CancellationToken cancellation { return Task.FromCanceled(cancellationToken); } - try + return InternalUnlockAsync(); + async Task InternalUnlockAsync() { + var cacheKey = GetCacheKey(key); + Log.Debug("Unlocking object with key: '{0}'.", cacheKey); if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) { Log.Warn( $"Calling {nameof(UnlockAsync)} method for key:'{cacheKey}' that was not locked with {nameof(LockAsync)} method before."); - return Task.FromResult(false); + return false; } - return _keyLocker.UnlockAsync(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); + + var unlocked = await (_keyLocker.UnlockAsync(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); + Log.Debug("Unlock key '{0}' result: {1}", cacheKey, unlocked); + return unlocked; } } @@ -394,6 +430,7 @@ public virtual Task UnlockManyAsync(object[] keys, string lockValue, Cancel { throw new ArgumentNullException(nameof(keys)); } + if (string.IsNullOrEmpty(UnlockManyScript)) { throw new NotSupportedException($"{nameof(UnlockManyAsync)} operation is not supported."); @@ -402,19 +439,27 @@ public virtual Task UnlockManyAsync(object[] keys, string lockValue, Cancel { return Task.FromCanceled(cancellationToken); } - try + return InternalUnlockManyAsync(); + async Task InternalUnlockManyAsync() { + + Log.Debug("Unlocking {0} objects...", keys.Length); var cacheKeys = new string[keys.Length]; for (var i = 0; i < keys.Length; i++) { - var cacheKey = GetCacheKey(keys[i]); - cacheKeys[i] = cacheKey; + cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Unlocking object with key: '{0}'.", cacheKeys[i]); } - return _keyLocker.UnlockManyAsync(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); + + var unlockedKeys = + await (_keyLocker.UnlockManyAsync(cacheKeys, lockValue, UnlockManyScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); + if (Log.IsDebugEnabled()) + { + Log.Debug("Number of unlocked objects with keys ({0}): {1}", string.Join(",", cacheKeys.Select(o => $"'{o}'")), + unlockedKeys); + } + + return unlockedKeys; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs index 22b5efcf..01db74a6 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; +using System.Linq; using NHibernate.Cache; using StackExchange.Redis; using static NHibernate.Caches.StackExRedis.ConfigurationHelper; @@ -30,8 +31,13 @@ public override async Task GetAsync(object key, CancellationToken cancel } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to fetch the object with key: '{0}'", CurrentVersion, GetCacheKey(key)); + } return await (base.GetAsync(key, cancellationToken)).ConfigureAwait(false); } } @@ -46,8 +52,15 @@ public override async Task GetManyAsync(object[] keys, CancellationTok } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to fetch objects with keys: {0}", + CurrentVersion, + string.Join(",", keys.Select(o => $"'{GetCacheKey(o)}'"))); + } return await (base.GetManyAsync(keys, cancellationToken)).ConfigureAwait(false); } } @@ -62,8 +75,13 @@ public override async Task LockAsync(object key, CancellationToken cance } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to lock the object with key: '{0}'", CurrentVersion, GetCacheKey(key)); + } return await (base.LockAsync(key, cancellationToken)).ConfigureAwait(false); } } @@ -78,8 +96,15 @@ public override async Task LockManyAsync(object[] keys, CancellationToke } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to lock objects with keys: {0}", + CurrentVersion, + string.Join(",", keys.Select(o => $"'{GetCacheKey(o)}'"))); + } return await (base.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); } } @@ -94,6 +119,7 @@ public override async Task PutAsync(object key, object value, CancellationToken } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // Here we don't know if the operation was executed after as successful lock, so @@ -111,6 +137,7 @@ public override async Task PutManyAsync(object[] keys, object[] values, Cancella } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // Here we don't know if the operation was executed after as successful lock, so @@ -128,6 +155,7 @@ public override async Task RemoveAsync(object key, CancellationToken cance } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // There is no point removing the key in the new version. @@ -145,6 +173,7 @@ public override async Task RemoveManyAsync(object[] keys, CancellationToke } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // There is no point removing the keys in the new version. @@ -162,6 +191,7 @@ public override async Task UnlockAsync(object key, CancellationToken cance } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // If the lock was acquired in the old version we are unable to unlock the key. @@ -179,6 +209,7 @@ public override async Task UnlockManyAsync(object[] keys, string lockValue, } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); cancellationToken.ThrowIfCancellationRequested(); await (InitializeVersionAsync()).ConfigureAwait(false); // If the lock was acquired in the old version we are unable to unlock the keys. @@ -189,6 +220,8 @@ public override async Task UnlockManyAsync(object[] keys, string lockValue, /// public override async Task ClearAsync(CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + Log.Debug("Clearing region: '{0}'.", RegionKey); cancellationToken.ThrowIfCancellationRequested(); var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(UpdateVersionLuaScript, _regionKeyArray, _maxVersionNumber)).ConfigureAwait(false); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index f45f5b3e..50e327a1 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using NHibernate.Cache; using StackExchange.Redis; using static NHibernate.Caches.StackExRedis.ConfigurationHelper; @@ -65,8 +66,8 @@ public DefaultRegionStrategy(ConnectionMultiplexer connectionMultiplexer, _usePubSub = GetBoolean("cache.region_strategy.default.use_pubsub", properties, true); Log.Debug("Use pubsub for region {0}: {1}", RegionName, _usePubSub); - _regionKeyArray = new RedisKey[] { RegionKey }; - _maxVersionNumber = new RedisValue[] { maxVersion }; + _regionKeyArray = new RedisKey[] {RegionKey}; + _maxVersionNumber = new RedisValue[] {maxVersion}; InitializeVersion(); if (_usePubSub) @@ -122,7 +123,12 @@ public override object Get(object key) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to fetch the object with key: '{0}'", CurrentVersion, GetCacheKey(key)); + } return base.Get(key); } } @@ -136,7 +142,14 @@ public override object[] GetMany(object[] keys) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to fetch objects with keys: {0}", + CurrentVersion, + string.Join(",", keys.Select(o => $"'{GetCacheKey(o)}'"))); + } return base.GetMany(keys); } } @@ -150,7 +163,12 @@ public override string Lock(object key) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to lock the object with key: '{0}'", CurrentVersion, GetCacheKey(key)); + } return base.Lock(key); } } @@ -164,7 +182,14 @@ public override string LockMany(object[] keys) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); + if (Log.IsDebugEnabled()) + { + Log.Debug("Retry to lock objects with keys: {0}", + CurrentVersion, + string.Join(",", keys.Select(o => $"'{GetCacheKey(o)}'"))); + } return base.LockMany(keys); } } @@ -178,6 +203,7 @@ public override void Put(object key, object value) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // Here we don't know if the operation was executed after as successful lock, so // the easiest solution is to skip the operation @@ -193,6 +219,7 @@ public override void PutMany(object[] keys, object[] values) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // Here we don't know if the operation was executed after as successful lock, so // the easiest solution is to skip the operation @@ -208,6 +235,7 @@ public override bool Remove(object key) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // There is no point removing the key in the new version. return false; @@ -223,6 +251,7 @@ public override long RemoveMany(object[] keys) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // There is no point removing the keys in the new version. return 0L; @@ -238,6 +267,7 @@ public override bool Unlock(object key) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // If the lock was acquired in the old version we are unable to unlock the key. return false; @@ -253,6 +283,7 @@ public override int UnlockMany(object[] keys, string lockValue) } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { + Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); InitializeVersion(); // If the lock was acquired in the old version we are unable to unlock the keys. return 0; @@ -262,6 +293,7 @@ public override int UnlockMany(object[] keys, string lockValue) /// public override void Clear() { + Log.Debug("Clearing region: '{0}'.", RegionKey); var results = (RedisValue[]) Database.ScriptEvaluate(UpdateVersionLuaScript, _regionKeyArray, _maxVersionNumber); var version = results[0]; @@ -310,6 +342,7 @@ private void InitializeVersion() private void UpdateVersion(RedisValue version) { + Log.Debug("Updating version from '{0}' to '{1}'.", CurrentVersion, version); _currentVersion = version; _currentVersionArray = new[] {version}; } From 91669a93a8756d0f2fa4b91df9148b795e98151d Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 7 Jul 2018 12:10:50 +0200 Subject: [PATCH 16/24] Removed the dictionary of locked keys as it will not be needed with NH 5.2 --- .../Async/Caches/DistributedRedisCache.cs | 17 +++++--- .../Async/RedisCachePerformanceFixture.cs | 4 +- .../Caches/DistributedRedisCache.cs | 17 +++++--- .../RedisCachePerformanceFixture.cs | 4 +- .../AbstractRegionStrategy.cs | 41 ++----------------- .../Async/AbstractRegionStrategy.cs | 32 ++++----------- .../Async/DefaultRegionStrategy.cs | 4 +- .../Async/RedisCache.cs | 32 +++++++++++++-- .../DefaultRegionStrategy.cs | 4 +- .../RedisCache.cs | 20 +++++++-- 10 files changed, 87 insertions(+), 88 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs index c3e6be2e..6c93382f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -62,19 +62,23 @@ public async Task LockAsync(object key, CancellationToken cancellationToken) { // A simple locking that requires all instances to obtain the lock // A real distributed cache should use something like the Redlock algorithm. + var lockValues = new string[_regionStrategies.Length]; try { - foreach (var strategy in _regionStrategies) + for (var i = 0; i < _regionStrategies.Length; i++) { - await (strategy.LockAsync(key, cancellationToken)); + lockValues[i] = await (_regionStrategies[i].LockAsync(key, cancellationToken)); } - } catch (CacheException) { - foreach (var strategy in _regionStrategies) + for (var i = 0; i < _regionStrategies.Length; i++) { - await (strategy.UnlockAsync(key, cancellationToken)); + if (lockValues[i] == null) + { + continue; + } + await (_regionStrategies[i].UnlockAsync(key, lockValues[i], cancellationToken)); } throw; } @@ -85,7 +89,8 @@ public async Task UnlockAsync(object key, CancellationToken cancellationToken) { foreach (var strategy in _regionStrategies) { - await (strategy.UnlockAsync(key, cancellationToken)); + // TODO: use the lockValue when upgrading to NH 5.2 + await (strategy.UnlockAsync(key, null, cancellationToken)); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs index 010e0ca0..d22e438f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs @@ -84,8 +84,8 @@ public Task TestLockUnlockOperationAsync() { return TestOperationAsync("Lock/Unlock", true, async (cache, key, _) => { - await (cache.LockAsync(key, CancellationToken.None)); - await (cache.UnlockAsync(key, CancellationToken.None)); + var value = await (cache.LockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, value, CancellationToken.None)); }); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs index c58ab101..2d123be3 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs @@ -83,19 +83,23 @@ public void Lock(object key) { // A simple locking that requires all instances to obtain the lock // A real distributed cache should use something like the Redlock algorithm. + var lockValues = new string[_regionStrategies.Length]; try { - foreach (var strategy in _regionStrategies) + for (var i = 0; i < _regionStrategies.Length; i++) { - strategy.Lock(key); + lockValues[i] = _regionStrategies[i].Lock(key); } - } catch (CacheException) { - foreach (var strategy in _regionStrategies) + for (var i = 0; i < _regionStrategies.Length; i++) { - strategy.Unlock(key); + if (lockValues[i] == null) + { + continue; + } + _regionStrategies[i].Unlock(key, lockValues[i]); } throw; } @@ -106,7 +110,8 @@ public void Unlock(object key) { foreach (var strategy in _regionStrategies) { - strategy.Unlock(key); + // TODO: use the lockValue when upgrading to NH 5.2 + strategy.Unlock(key, null); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs index 516e7c1b..8971fa79 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs @@ -80,8 +80,8 @@ public void TestLockUnlockOperation() { TestOperation("Lock/Unlock", true, (cache, key, _) => { - cache.Lock(key); - cache.Unlock(key); + var value = cache.Lock(key); + cache.Unlock(key, value); }); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index efff3ebd..e0225333 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -34,22 +34,6 @@ public abstract partial class AbstractRegionStrategy private readonly RedisKeyLocker _keyLocker; - /// - /// With the current NHibernate version (5.1) the method does not have a - /// return value that would indicate which lock value was used to lock the key, that would then be - /// passed to the method in order to prevent unlocking keys that were locked - /// by others. Luckily, the and methods are currently - /// always called inside a lock statement which we abuse by storing the locked key when the - /// is called and use it when is called. This - /// means that our and are not thread safe and have to be called - /// sequentially in order prevent overriding the lock key for a given key. - /// Currently, the is always called after the method - /// even if an exception occurs which we are also abusing in order to avoid having a special mechanism to - /// clear the dictionary in order to prevent memory leaks. - /// - private readonly ConcurrentDictionary _acquiredKeyLocks = new ConcurrentDictionary(); - - /// /// The constructor for creating the region strategy. /// @@ -405,17 +389,7 @@ public virtual string Lock(object key) var cacheKey = GetCacheKey(key); Log.Debug("Locking object with key: '{0}'.", cacheKey); - var lockValue = _keyLocker.Lock(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues()); - - _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => - { - Log.Warn( - $"Calling {nameof(Lock)} method for key:'{cacheKey}' that was already locked. " + - $"{nameof(Unlock)} method must be called after each {nameof(Lock)} call for " + - $"the same key prior calling {nameof(Lock)} again with the same key."); - return lockValue; - }); - return lockValue; + return _keyLocker.Lock(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues()); } /// @@ -450,8 +424,9 @@ public virtual string LockMany(object[] keys) /// Unlocks the key. /// /// The key to unlock. + /// The value used to lock the key. /// Whether the key was unlocked - public virtual bool Unlock(object key) + public virtual bool Unlock(object key, string lockValue) { if (key == null) { @@ -460,14 +435,6 @@ public virtual bool Unlock(object key) var cacheKey = GetCacheKey(key); Log.Debug("Unlocking object with key: '{0}'.", cacheKey); - - if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) - { - Log.Warn( - $"Calling {nameof(Unlock)} method for key:'{cacheKey}' that was not locked with {nameof(Lock)} method before."); - return false; - } - var unlocked = _keyLocker.Unlock(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues()); Log.Debug("Unlock key '{0}' result: {1}", cacheKey, unlocked); return unlocked; @@ -477,7 +444,7 @@ public virtual bool Unlock(object key) /// Unlocks many keys at once. /// /// The keys to unlock. - /// The value used to lock the keys + /// The value used to lock the keys. /// The number of unlocked keys. public virtual int UnlockMany(object[] keys, string lockValue) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index b0f28ba2..27a09084 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -321,23 +321,16 @@ public virtual Task LockAsync(object key, CancellationToken cancellation { return Task.FromCanceled(cancellationToken); } - return InternalLockAsync(); - async Task InternalLockAsync() + try { var cacheKey = GetCacheKey(key); Log.Debug("Locking object with key: '{0}'.", cacheKey); - var lockValue = await (_keyLocker.LockAsync(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); - - _acquiredKeyLocks.AddOrUpdate(cacheKey, _ => lockValue, (_, currValue) => - { - Log.Warn( - $"Calling {nameof(LockAsync)} method for key:'{cacheKey}' that was already locked. " + - $"{nameof(UnlockAsync)} method must be called after each {nameof(LockAsync)} call for " + - $"the same key prior calling {nameof(LockAsync)} again with the same key."); - return lockValue; - }); - return lockValue; + return _keyLocker.LockAsync(cacheKey, LockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); } } @@ -385,9 +378,10 @@ public virtual Task LockManyAsync(object[] keys, CancellationToken cance /// Unlocks the key. /// /// The key to unlock. + /// The value used to lock the key. /// A cancellation token that can be used to cancel the work /// Whether the key was unlocked - public virtual Task UnlockAsync(object key, CancellationToken cancellationToken) + public virtual Task UnlockAsync(object key, string lockValue, CancellationToken cancellationToken) { if (key == null) { @@ -403,14 +397,6 @@ async Task InternalUnlockAsync() var cacheKey = GetCacheKey(key); Log.Debug("Unlocking object with key: '{0}'.", cacheKey); - - if (!_acquiredKeyLocks.TryRemove(cacheKey, out var lockValue)) - { - Log.Warn( - $"Calling {nameof(UnlockAsync)} method for key:'{cacheKey}' that was not locked with {nameof(LockAsync)} method before."); - return false; - } - var unlocked = await (_keyLocker.UnlockAsync(cacheKey, lockValue, UnlockScript, GetAdditionalKeys(), GetAdditionalValues(), cancellationToken)).ConfigureAwait(false); Log.Debug("Unlock key '{0}' result: {1}", cacheKey, unlocked); return unlocked; @@ -421,7 +407,7 @@ async Task InternalUnlockAsync() /// Unlocks many keys at once. /// /// The keys to unlock. - /// The value used to lock the keys + /// The value used to lock the keys. /// A cancellation token that can be used to cancel the work /// The number of unlocked keys. public virtual Task UnlockManyAsync(object[] keys, string lockValue, CancellationToken cancellationToken) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs index 01db74a6..ebf973f8 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs @@ -182,12 +182,12 @@ public override async Task RemoveManyAsync(object[] keys, CancellationToke } /// - public override async Task UnlockAsync(object key, CancellationToken cancellationToken) + public override async Task UnlockAsync(object key, string lockValue, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); try { - return await (base.UnlockAsync(key, cancellationToken)).ConfigureAwait(false); + return await (base.UnlockAsync(key, lockValue, cancellationToken)).ConfigureAwait(false); } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs index 176b4856..121c0ad1 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs @@ -92,13 +92,20 @@ public Task ClearAsync(CancellationToken cancellationToken) } /// - public Task LockAsync(object key, CancellationToken cancellationToken) + Task ICache.LockAsync(object key, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return RegionStrategy.LockAsync(key, cancellationToken); + return LockAsync(key, cancellationToken); + } + + /// + public async Task LockAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return await (RegionStrategy.LockAsync(key, cancellationToken)).ConfigureAwait(false); } /// @@ -109,13 +116,30 @@ public async Task LockManyAsync(object[] keys, CancellationToken cancell } /// - public Task UnlockAsync(object key, CancellationToken cancellationToken) + public Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + return RegionStrategy.UnlockAsync(key, (string)lockValue, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + Task ICache.UnlockAsync(object key, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return RegionStrategy.UnlockAsync(key, cancellationToken); + return UnlockAsync(key, null, cancellationToken); } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index 50e327a1..c610efe4 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -259,11 +259,11 @@ public override long RemoveMany(object[] keys) } /// - public override bool Unlock(object key) + public override bool Unlock(object key, string lockValue) { try { - return base.Unlock(key); + return base.Unlock(key, lockValue); } catch (RedisServerException e) when (e.Message == InvalidVersionMessage) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs index 68821f0b..aef16f2f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs @@ -85,9 +85,15 @@ public void Destroy() } /// - public void Lock(object key) + void ICache.Lock(object key) { - RegionStrategy.Lock(key); + Lock(key); + } + + /// + public object Lock(object key) + { + return RegionStrategy.Lock(key); } /// @@ -97,9 +103,15 @@ public object LockMany(object[] keys) } /// - public void Unlock(object key) + public void Unlock(object key, object lockValue) + { + RegionStrategy.Unlock(key, (string)lockValue); + } + + /// + void ICache.Unlock(object key) { - RegionStrategy.Unlock(key); + Unlock(key, null); } /// From 54371f2bd78c704ac4fd4335c24bac25965053b7 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 7 Jul 2018 14:49:04 +0200 Subject: [PATCH 17/24] Added the ability to provide a custom IDatabase and IConnectionMultiplexer instance by code or NH configuration. --- .../CustomObjectsFactory.cs | 42 +++++++++ .../DistributedRedisCacheProvider.cs | 13 ++- .../RedisCacheProviderFixture.cs | 43 +++++++++ .../AbstractRegionStrategy.cs | 6 +- .../ConfigurationHelper.cs | 31 ++++++- .../DefaultCacheRegionStrategyFactory.cs | 2 +- .../DefaultConnectionMultiplexerProvider.cs | 25 ++++++ .../DefaultDatabaseProvider.cs | 19 ++++ .../DefaultRegionStrategy.cs | 2 +- .../FastRegionStrategy.cs | 2 +- .../ICacheRegionStrategyFactory.cs | 2 +- .../IConnectionMultiplexerProvider.cs | 22 +++++ .../IDatabaseProvider.cs | 23 +++++ .../RedisCacheConfiguration.cs | 10 +++ .../RedisCacheProvider.cs | 87 +++++++++---------- .../RedisCacheRegionConfiguration.cs | 6 ++ .../RedisEnvironment.cs | 10 +++ 17 files changed, 280 insertions(+), 65 deletions(-) create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs create mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs new file mode 100644 index 00000000..abd5f844 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NHibernate.Bytecode; + +namespace NHibernate.Caches.StackExRedis.Tests +{ + public class CustomObjectsFactory : IObjectsFactory + { + private readonly Dictionary _registeredTypes = new Dictionary(); + private readonly Dictionary _registeredSingletons = new Dictionary(); + + public void Register() + { + _registeredTypes.Add(typeof(TBaseType), typeof(TConcreteType)); + } + + public void RegisterSingleton(TBaseType value) + { + _registeredSingletons.Add(typeof(TBaseType), value); + } + + public object CreateInstance(System.Type type) + { + return _registeredSingletons.TryGetValue(type, out var singleton) + ? singleton + : Activator.CreateInstance(_registeredTypes.TryGetValue(type, out var concreteType) ? concreteType : type); + } + + public object CreateInstance(System.Type type, bool nonPublic) + { + throw new NotSupportedException(); + } + + public object CreateInstance(System.Type type, params object[] ctorArgs) + { + throw new NotSupportedException(); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs index 43d2d865..15850022 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs @@ -15,29 +15,26 @@ namespace NHibernate.Caches.StackExRedis.Tests.Providers /// public class DistributedRedisCacheProvider : RedisCacheProvider { - private readonly List _connectionMultiplexers = new List(); + private readonly List _connectionMultiplexers = new List(); /// - protected override void Start(string configurationString, IDictionary properties, TextWriter textWriter) + protected override void Start(string configurationString, IDictionary properties) { foreach (var instanceConfiguration in configurationString.Split(';')) { - var configuration = ConfigurationOptions.Parse(instanceConfiguration); - var connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); - connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting + var connectionMultiplexer = CacheConfiguration.ConnectionMultiplexerProvider.Get(instanceConfiguration); _connectionMultiplexers.Add(connectionMultiplexer); } } /// - protected override ICache BuildCache(RedisCacheConfiguration defaultConfiguration, RedisCacheRegionConfiguration regionConfiguration, - IDictionary properties) + protected override ICache BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) { var strategies = new List(); foreach (var connectionMultiplexer in _connectionMultiplexers) { var regionStrategy = - defaultConfiguration.RegionStrategyFactory.Create(connectionMultiplexer, regionConfiguration, properties); + CacheConfiguration.RegionStrategyFactory.Create(connectionMultiplexer, regionConfiguration, properties); regionStrategy.Validate(); strategies.Add(regionStrategy); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs index 870b6a71..dc79ee07 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -1,11 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using Castle.DynamicProxy.Generators.Emitters.SimpleAST; +using NHibernate.Bytecode; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; +using NSubstitute; +using NSubstitute.Extensions; using NUnit.Framework; +using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis.Tests { @@ -49,5 +55,42 @@ public void TestExpiration() "unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); } + [Test] + public void TestUserProvidedObjectsFactory() + { + // TODO: update when upgraded to NH 5.2 + var field = typeof(AbstractBytecodeProvider).GetField("objectsFactory", + BindingFlags.Instance | BindingFlags.NonPublic); + + var customObjectsFactory = new CustomObjectsFactory(); + var regionStrategyFactory = Substitute.For(); + var serialzer = Substitute.For(); + var lockValueProvider = Substitute.For(); + var retryDelayProvider = Substitute.For(); + var databaseProvider = Substitute.For(); + var connectionProvider = Substitute.For(); + + customObjectsFactory.RegisterSingleton(serialzer); + customObjectsFactory.RegisterSingleton(lockValueProvider); + customObjectsFactory.RegisterSingleton(regionStrategyFactory); + customObjectsFactory.RegisterSingleton(retryDelayProvider); + customObjectsFactory.RegisterSingleton(databaseProvider); + customObjectsFactory.RegisterSingleton(connectionProvider); + + field.SetValue(Cfg.Environment.BytecodeProvider, customObjectsFactory); + + var provider = (RedisCacheProvider)GetNewProvider(); + var config = provider.CacheConfiguration; + + Assert.That(regionStrategyFactory, Is.EqualTo(config.RegionStrategyFactory)); + Assert.That(serialzer, Is.EqualTo(config.Serializer)); + Assert.That(lockValueProvider, Is.EqualTo(config.LockConfiguration.ValueProvider)); + Assert.That(retryDelayProvider, Is.EqualTo(config.LockConfiguration.RetryDelayProvider)); + Assert.That(databaseProvider, Is.EqualTo(config.DatabaseProvider)); + Assert.That(connectionProvider, Is.EqualTo(config.ConnectionMultiplexerProvider)); + + field.SetValue(Cfg.Environment.BytecodeProvider, new ActivatorObjectsFactory()); + } + } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index e0225333..2f4d7799 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -20,7 +20,7 @@ public abstract partial class AbstractRegionStrategy /// /// The Redis connection. /// - protected readonly ConnectionMultiplexer ConnectionMultiplexer; + protected readonly IConnectionMultiplexer ConnectionMultiplexer; /// /// The Redis database where the keys are stored. @@ -40,7 +40,7 @@ public abstract partial class AbstractRegionStrategy /// The Redis connection. /// The region configuration. /// The NHibernate configuration properties. - protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + protected AbstractRegionStrategy(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) { Log = NHibernateLogger.For(GetType()); @@ -50,7 +50,7 @@ protected AbstractRegionStrategy(ConnectionMultiplexer connectionMultiplexer, AppendHashcode = configuration.AppendHashcode; RegionKey = configuration.RegionKey; ConnectionMultiplexer = connectionMultiplexer; - Database = connectionMultiplexer.GetDatabase(configuration.Database); + Database = configuration.DatabaseProvider.Get(connectionMultiplexer, configuration.Database); Serializer = configuration.Serializer; LockTimeout = configuration.LockConfiguration.KeyTimeout; _keyLocker = new RedisKeyLocker(RegionName, Database, configuration.LockConfiguration); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs index 622fa60a..559e4e4f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NHibernate.Bytecode; using NHibernate.Cache; namespace NHibernate.Caches.StackExRedis @@ -15,6 +16,7 @@ public static string GetString(string key, IDictionary propertie { return defaultValue; } + return properties.TryGetValue(key, out var value) ? value : defaultValue; } @@ -24,6 +26,7 @@ public static bool GetBoolean(string key, IDictionary properties { return defaultValue; } + return properties.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue; } @@ -42,6 +45,7 @@ public static TimeSpan GetTimeSpanFromSeconds(string key, IDictionary { return defaultValue; } + try { return System.Type.GetType(typeName, true); @@ -77,19 +83,38 @@ public static System.Type GetSystemType(string key, IDictionary } } - public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue) + public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue, + INHibernateLogger logger) { + var objectsFactory = Cfg.Environment.BytecodeProvider.ObjectsFactory; var type = GetSystemType(key, properties, null); if (type == null) { + // Try to get the instance from the base type if the user provided a custom IObjectsFactory + if (!(objectsFactory is ActivatorObjectsFactory)) + { + try + { + return (TType) objectsFactory.CreateInstance(typeof(TType)); + } + catch (Exception e) + { + // The user most likely did not register the TType + logger.Debug( + "Failed to create an instance of type '{0}' by using IObjectsFactory, most probably was not registered. Exception: {1}", + typeof(TType), e); + } + } return defaultValue; } + if (!typeof(TType).IsAssignableFrom(type)) { - throw new CacheException($"Type '{type}' from the configuration property '{key}' is not assignable to '{typeof(TType)}'"); + throw new CacheException( + $"Type '{type}' from the configuration property '{key}' is not assignable to '{typeof(TType)}'"); } - return (TType) Cfg.Environment.BytecodeProvider.ObjectsFactory.CreateInstance(type); + return (TType) objectsFactory.CreateInstance(type); } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs index 5bdb51ed..7924d379 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs @@ -8,7 +8,7 @@ namespace NHibernate.Caches.StackExRedis public class DefaultCacheRegionStrategyFactory : ICacheRegionStrategyFactory { /// - public AbstractRegionStrategy Create(ConnectionMultiplexer connectionMultiplexer, + public AbstractRegionStrategy Create(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) { return (AbstractRegionStrategy) Activator.CreateInstance(configuration.RegionStrategy, connectionMultiplexer, diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs new file mode 100644 index 00000000..6712a637 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + public class DefaultConnectionMultiplexerProvider : IConnectionMultiplexerProvider + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(DefaultConnectionMultiplexerProvider)); + + /// + public IConnectionMultiplexer Get(string configuration) + { + TextWriter textWriter = Log.IsDebugEnabled() ? new NHibernateTextWriter(Log) : null; + var connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); + connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting + return connectionMultiplexer; + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs new file mode 100644 index 00000000..feee30e3 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + public class DefaultDatabaseProvider : IDatabaseProvider + { + /// + public IDatabase Get(IConnectionMultiplexer connectionMultiplexer, int database) + { + return connectionMultiplexer.GetDatabase(database); + } + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index c610efe4..40a7a76f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -56,7 +56,7 @@ static DefaultRegionStrategy() /// /// Default constructor. /// - public DefaultRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + public DefaultRegionStrategy(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) : base(connectionMultiplexer, configuration, properties) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs index 14abe8e5..8a594f65 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs @@ -34,7 +34,7 @@ static FastRegionStrategy() /// /// Default constructor. /// - public FastRegionStrategy(ConnectionMultiplexer connectionMultiplexer, + public FastRegionStrategy(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) : base(connectionMultiplexer, configuration, properties) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs index c320236b..aa1491f5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs @@ -15,7 +15,7 @@ public interface ICacheRegionStrategyFactory /// The region configuration. /// The properties from NHibernate configuration. /// - AbstractRegionStrategy Create(ConnectionMultiplexer connectionMultiplexer, + AbstractRegionStrategy Create(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties); } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs new file mode 100644 index 00000000..88c8834c --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines a method to provide an instance. + /// + public interface IConnectionMultiplexerProvider + { + /// + /// Provide the for the StackExchange.Redis configuration string. + /// + /// The StackExchange.Redis configuration string + /// The instance. + IConnectionMultiplexer Get(string configuration); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs new file mode 100644 index 00000000..72f17818 --- /dev/null +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace NHibernate.Caches.StackExRedis +{ + /// + /// Defines a method to provide an instance. + /// + public interface IDatabaseProvider + { + /// + /// Provide the for the given and database index. + /// + /// The connection multiplexer. + /// The database index. + /// The instance. + IDatabase Get(IConnectionMultiplexer connectionMultiplexer, int database); + } +} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index 04b0001b..2b51404d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -59,6 +59,16 @@ public class RedisCacheConfiguration /// public ICacheRegionStrategyFactory RegionStrategyFactory { get; set; } = new DefaultCacheRegionStrategyFactory(); + /// + /// The instance. + /// + public IConnectionMultiplexerProvider ConnectionMultiplexerProvider { get; set; } = new DefaultConnectionMultiplexerProvider(); + + /// + /// The instance. + /// + public IDatabaseProvider DatabaseProvider { get; set; } = new DefaultDatabaseProvider(); + /// /// The default type. /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index 45143c3e..ebb21bd5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -1,12 +1,7 @@ using System; using System.Collections.Generic; using System.Configuration; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; using NHibernate.Cache; -using NHibernate.Util; using StackExchange.Redis; using static NHibernate.Caches.StackExRedis.ConfigurationHelper; @@ -21,8 +16,7 @@ public class RedisCacheProvider : ICacheProvider private static readonly Dictionary ConfiguredCacheRegions; private static readonly CacheConfig ConfiguredCache; - private ConnectionMultiplexer _connectionMultiplexer; - private RedisCacheConfiguration _defaultCacheConfiguration; + private IConnectionMultiplexer _connectionMultiplexer; static RedisCacheProvider() { @@ -39,6 +33,11 @@ static RedisCacheProvider() } } + /// + /// The Redis cache configuration that is populated by the NHibernate configuration. + /// + public RedisCacheConfiguration CacheConfiguration { get; } = new RedisCacheConfiguration(); + /// public ICache BuildCache(string regionName, IDictionary properties) { @@ -47,13 +46,11 @@ public ICache BuildCache(string regionName, IDictionary properti regionName = string.Empty; } - var regionConfiguration = ConfiguredCacheRegions.TryGetValue(regionName, out var regionConfig) - ? BuildRegionConfiguration(regionConfig, properties) + var regionConfiguration = ConfiguredCacheRegions.TryGetValue(regionName, out var regionConfig) + ? BuildRegionConfiguration(regionConfig, properties) : BuildRegionConfiguration(regionName, properties); - Log.Debug("Building cache: {0}", regionConfiguration.ToString()); - - return BuildCache(_defaultCacheConfiguration, regionConfiguration, properties); + return BuildCache(regionConfiguration, properties); } /// @@ -72,13 +69,8 @@ public void Start(IDictionary properties) } Log.Debug("Starting with configuration string: {0}", configurationString); - - _defaultCacheConfiguration = BuildDefaultConfiguration(properties); - - Log.Debug("Default configuration: {0}", _defaultCacheConfiguration); - - TextWriter textWriter = Log.IsDebugEnabled() ? new NHibernateTextWriter(Log) : null; - Start(configurationString, properties, textWriter); + BuildDefaultConfiguration(properties); + Start(configurationString, properties); } /// @@ -90,6 +82,7 @@ public virtual void Stop() { Log.Debug("Releasing connection."); } + _connectionMultiplexer.Dispose(); _connectionMultiplexer = null; } @@ -103,28 +96,23 @@ public virtual void Stop() /// Callback to perform any necessary initialization of the underlying cache implementation /// during ISessionFactory construction. /// - /// The Redis configuration string. + /// The StackExchange.Redis configuration string. /// NHibernate configuration settings. - /// The text writer. - protected virtual void Start(string configurationString, IDictionary properties, TextWriter textWriter) + protected virtual void Start(string configurationString, IDictionary properties) { - var configuration = ConfigurationOptions.Parse(configurationString); - _connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); - _connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting + _connectionMultiplexer = CacheConfiguration.ConnectionMultiplexerProvider.Get(configurationString); } /// /// Builds the cache. /// - /// The default cache configuration. /// The region cache configuration. /// NHibernate configuration settings. /// The builded cache. - protected virtual ICache BuildCache(RedisCacheConfiguration defaultConfiguration, - RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) + protected virtual ICache BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) { var regionStrategy = - defaultConfiguration.RegionStrategyFactory.Create(_connectionMultiplexer, regionConfiguration, properties); + CacheConfiguration.RegionStrategyFactory.Create(_connectionMultiplexer, regionConfiguration, properties); regionStrategy.Validate(); @@ -139,44 +127,45 @@ private RedisCacheRegionConfiguration BuildRegionConfiguration(RegionConfig regi { var config = new RedisCacheRegionConfiguration(regionConfig.Region) { - LockConfiguration = _defaultCacheConfiguration.LockConfiguration, - RegionPrefix = _defaultCacheConfiguration.RegionPrefix, - Serializer = _defaultCacheConfiguration.Serializer, - EnvironmentName = _defaultCacheConfiguration.EnvironmentName, - CacheKeyPrefix = _defaultCacheConfiguration.CacheKeyPrefix + LockConfiguration = CacheConfiguration.LockConfiguration, + RegionPrefix = CacheConfiguration.RegionPrefix, + Serializer = CacheConfiguration.Serializer, + EnvironmentName = CacheConfiguration.EnvironmentName, + CacheKeyPrefix = CacheConfiguration.CacheKeyPrefix, + DatabaseProvider = CacheConfiguration.DatabaseProvider }; config.Database = GetInteger("database", properties, regionConfig.Database ?? GetInteger(RedisEnvironment.Database, properties, - _defaultCacheConfiguration.DefaultDatabase)); + CacheConfiguration.DefaultDatabase)); Log.Debug("Database for region {0}: {1}", regionConfig.Region, config.Database); config.Expiration = GetTimeSpanFromSeconds("expiration", properties, regionConfig.Expiration ?? GetTimeSpanFromSeconds(Cfg.Environment.CacheDefaultExpiration, properties, - _defaultCacheConfiguration.DefaultExpiration)); + CacheConfiguration.DefaultExpiration)); Log.Debug("Expiration for region {0}: {1} seconds", regionConfig.Region, config.Expiration.TotalSeconds); config.RegionStrategy = GetSystemType("strategy", properties, regionConfig.RegionStrategy ?? GetSystemType(RedisEnvironment.RegionStrategy, properties, - _defaultCacheConfiguration.DefaultRegionStrategy)); + CacheConfiguration.DefaultRegionStrategy)); Log.Debug("Region strategy for region {0}: {1}", regionConfig.Region, config.RegionStrategy); config.UseSlidingExpiration = GetBoolean("sliding", properties, regionConfig.UseSlidingExpiration ?? GetBoolean(RedisEnvironment.UseSlidingExpiration, properties, - _defaultCacheConfiguration.DefaultUseSlidingExpiration)); + CacheConfiguration.DefaultUseSlidingExpiration)); config.AppendHashcode = GetBoolean("append-hashcode", properties, regionConfig.AppendHashcode ?? GetBoolean(RedisEnvironment.AppendHashcode, properties, - _defaultCacheConfiguration.DefaultAppendHashcode)); + CacheConfiguration.DefaultAppendHashcode)); Log.Debug("Use sliding expiration for region {0}: {1}", regionConfig.Region, config.UseSlidingExpiration); return config; } - private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary properties) + private void BuildDefaultConfiguration(IDictionary properties) { - var config = new RedisCacheConfiguration(); + var config = CacheConfiguration; config.CacheKeyPrefix = GetString(RedisEnvironment.KeyPrefix, properties, config.CacheKeyPrefix); Log.Debug("Cache key prefix: {0}", config.CacheKeyPrefix); @@ -187,12 +176,18 @@ private RedisCacheConfiguration BuildDefaultConfiguration(IDictionary public IRedisSerializer Serializer { get; set; } + /// + /// The instance. + /// + public IDatabaseProvider DatabaseProvider { get; set; } = new DefaultDatabaseProvider(); + /// /// The configuration for locking keys. /// @@ -96,6 +101,7 @@ public override string ToString() sb.AppendFormat("AppendHashcode={0}", AppendHashcode); sb.AppendFormat("RegionStrategy={0}", RegionStrategy); sb.AppendFormat("Serializer={0}", Serializer); + sb.AppendFormat("DatabaseProvider={0}", DatabaseProvider); sb.AppendFormat("LockConfiguration=({0})", LockConfiguration); return sb.ToString(); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs index 5d89e8dd..4ee434d0 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs @@ -38,6 +38,16 @@ public static class RedisEnvironment /// public const string RegionStrategyFactory = "cache.region_strategy_factory"; + /// + /// The assembly qualified name of the connection multiplexer provider. + /// + public const string ConnectionMultiplexerProvider = "cache.connection_multiplexer_provider"; + + /// + /// The assembly qualified name of the database provider. + /// + public const string DatabaseProvider = "cache.database_provider"; + /// /// Whether the expiration delay should be sliding. /// From a6b785129d030b0e05bbc78bb42b8b2869dae546 Mon Sep 17 00:00:00 2001 From: maca88 Date: Wed, 11 Jul 2018 18:03:24 +0200 Subject: [PATCH 18/24] Added the ability to set the default configuration options by a static property --- .../RedisCacheProviderFixture.cs | 59 ++++++++++++++----- .../RedisCacheConfiguration.cs | 46 ++++++++++++--- .../RedisCacheLockConfiguration.cs | 18 +++++- .../RedisCacheProvider.cs | 44 +++++++++++++- .../RedisCacheRegionConfiguration.cs | 22 +++---- 5 files changed, 152 insertions(+), 37 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs index dc79ee07..aa0c2b9a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -55,6 +55,37 @@ public void TestExpiration() "unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); } + [Test] + public void TestDefaultCacheConfiguration() + { + var connectionProvider = Substitute.For(); + var databaseProvider = Substitute.For(); + var retryDelayProvider = Substitute.For(); + var lockValueProvider = Substitute.For(); + var regionStrategyFactory = Substitute.For(); + var serialzer = Substitute.For(); + + var defaultConfig = RedisCacheProvider.DefaultCacheConfiguration; + defaultConfig.ConnectionMultiplexerProvider = connectionProvider; + defaultConfig.DatabaseProvider = databaseProvider; + defaultConfig.LockConfiguration.ValueProvider = lockValueProvider; + defaultConfig.LockConfiguration.RetryDelayProvider = retryDelayProvider; + defaultConfig.RegionStrategyFactory = regionStrategyFactory; + defaultConfig.Serializer = serialzer; + + var provider = (RedisCacheProvider) GetNewProvider(); + var config = provider.CacheConfiguration; + + Assert.That(config.ConnectionMultiplexerProvider, Is.EqualTo(connectionProvider)); + Assert.That(config.DatabaseProvider, Is.EqualTo(databaseProvider)); + Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); + Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); + Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); + Assert.That(config.Serializer, Is.EqualTo(serialzer)); + + RedisCacheProvider.DefaultCacheConfiguration = new RedisCacheConfiguration(); + } + [Test] public void TestUserProvidedObjectsFactory() { @@ -63,31 +94,31 @@ public void TestUserProvidedObjectsFactory() BindingFlags.Instance | BindingFlags.NonPublic); var customObjectsFactory = new CustomObjectsFactory(); + var connectionProvider = Substitute.For(); + var databaseProvider = Substitute.For(); + var retryDelayProvider = Substitute.For(); + var lockValueProvider = Substitute.For(); var regionStrategyFactory = Substitute.For(); var serialzer = Substitute.For(); - var lockValueProvider = Substitute.For(); - var retryDelayProvider = Substitute.For(); - var databaseProvider = Substitute.For(); - var connectionProvider = Substitute.For(); - customObjectsFactory.RegisterSingleton(serialzer); + customObjectsFactory.RegisterSingleton(connectionProvider); + customObjectsFactory.RegisterSingleton(databaseProvider); + customObjectsFactory.RegisterSingleton(retryDelayProvider); customObjectsFactory.RegisterSingleton(lockValueProvider); customObjectsFactory.RegisterSingleton(regionStrategyFactory); - customObjectsFactory.RegisterSingleton(retryDelayProvider); - customObjectsFactory.RegisterSingleton(databaseProvider); - customObjectsFactory.RegisterSingleton(connectionProvider); + customObjectsFactory.RegisterSingleton(serialzer); field.SetValue(Cfg.Environment.BytecodeProvider, customObjectsFactory); var provider = (RedisCacheProvider)GetNewProvider(); var config = provider.CacheConfiguration; - Assert.That(regionStrategyFactory, Is.EqualTo(config.RegionStrategyFactory)); - Assert.That(serialzer, Is.EqualTo(config.Serializer)); - Assert.That(lockValueProvider, Is.EqualTo(config.LockConfiguration.ValueProvider)); - Assert.That(retryDelayProvider, Is.EqualTo(config.LockConfiguration.RetryDelayProvider)); - Assert.That(databaseProvider, Is.EqualTo(config.DatabaseProvider)); - Assert.That(connectionProvider, Is.EqualTo(config.ConnectionMultiplexerProvider)); + Assert.That(config.ConnectionMultiplexerProvider, Is.EqualTo(connectionProvider)); + Assert.That(config.DatabaseProvider, Is.EqualTo(databaseProvider)); + Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); + Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); + Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); + Assert.That(config.Serializer, Is.EqualTo(serialzer)); field.SetValue(Cfg.Environment.BytecodeProvider, new ActivatorObjectsFactory()); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index 2b51404d..006ee1ce 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NHibernate.Caches.StackExRedis { @@ -11,10 +7,26 @@ namespace NHibernate.Caches.StackExRedis /// public class RedisCacheConfiguration { + private static readonly IRedisSerializer DefaultSerializer = new BinaryRedisSerializer(); + private static readonly ICacheRegionStrategyFactory DefaultRegionStrategyFactory = new DefaultCacheRegionStrategyFactory(); + private static readonly IConnectionMultiplexerProvider DefaultConnectionMultiplexerProvider = new DefaultConnectionMultiplexerProvider(); + private static readonly IDatabaseProvider DefaultDatabaseProvider = new DefaultDatabaseProvider(); + private static readonly System.Type DefaultRegionStrategyType = typeof(DefaultRegionStrategy); + + private IRedisSerializer _serializer; + private ICacheRegionStrategyFactory _regionStrategyFactory; + private IConnectionMultiplexerProvider _connectionMultiplexerProvider; + private IDatabaseProvider _databaseProvider; + private System.Type _defaultRegionStrategy; + /// /// The instance. /// - public IRedisSerializer Serializer { get; set; } = new BinaryRedisSerializer(); + public IRedisSerializer Serializer + { + get => _serializer ?? DefaultSerializer; + set => _serializer = value; + } /// /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients @@ -57,22 +69,38 @@ public class RedisCacheConfiguration /// /// The instance. /// - public ICacheRegionStrategyFactory RegionStrategyFactory { get; set; } = new DefaultCacheRegionStrategyFactory(); + public ICacheRegionStrategyFactory RegionStrategyFactory + { + get => _regionStrategyFactory ?? DefaultRegionStrategyFactory; + set => _regionStrategyFactory = value; + } /// /// The instance. /// - public IConnectionMultiplexerProvider ConnectionMultiplexerProvider { get; set; } = new DefaultConnectionMultiplexerProvider(); + public IConnectionMultiplexerProvider ConnectionMultiplexerProvider + { + get => _connectionMultiplexerProvider ?? DefaultConnectionMultiplexerProvider; + set => _connectionMultiplexerProvider = value; + } /// /// The instance. /// - public IDatabaseProvider DatabaseProvider { get; set; } = new DefaultDatabaseProvider(); + public IDatabaseProvider DatabaseProvider + { + get => _databaseProvider ?? DefaultDatabaseProvider; + set => _databaseProvider = value; + } /// /// The default type. /// - public System.Type DefaultRegionStrategy { get; set; } = typeof(DefaultRegionStrategy); + public System.Type DefaultRegionStrategy + { + get => _defaultRegionStrategy ?? DefaultRegionStrategyType; + set => _defaultRegionStrategy = value; + } /// /// The configuration for locking keys. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs index f1f3743e..1bdf3f0d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs @@ -8,6 +8,12 @@ namespace NHibernate.Caches.StackExRedis /// public class RedisCacheLockConfiguration { + private static readonly ICacheLockValueProvider DefaultValueProvider = new DefaultCacheLockValueProvider(); + private static readonly ICacheLockRetryDelayProvider DefaultRetryDelayProvider = new DefaultCacheLockRetryDelayProvider(); + + private ICacheLockValueProvider _valueProvider; + private ICacheLockRetryDelayProvider _retryDelayProvider; + /// /// The timeout for a lock key to expire. /// @@ -41,12 +47,20 @@ public class RedisCacheLockConfiguration /// /// The instance. /// - public ICacheLockValueProvider ValueProvider { get; set; } = new DefaultCacheLockValueProvider(); + public ICacheLockValueProvider ValueProvider + { + get => _valueProvider ?? DefaultValueProvider; + set => _valueProvider = value; + } /// /// The instance. /// - public ICacheLockRetryDelayProvider RetryDelayProvider { get; set; } = new DefaultCacheLockRetryDelayProvider(); + public ICacheLockRetryDelayProvider RetryDelayProvider + { + get => _retryDelayProvider ?? DefaultRetryDelayProvider; + set => _retryDelayProvider = value; + } /// public override string ToString() diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index ebb21bd5..506da11a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -15,9 +15,19 @@ public class RedisCacheProvider : ICacheProvider private static readonly INHibernateLogger Log; private static readonly Dictionary ConfiguredCacheRegions; private static readonly CacheConfig ConfiguredCache; + private static RedisCacheConfiguration _defaultCacheConfiguration = new RedisCacheConfiguration(); private IConnectionMultiplexer _connectionMultiplexer; + /// + /// The default configuration that will be used for creating the . + /// + public static RedisCacheConfiguration DefaultCacheConfiguration + { + get => _defaultCacheConfiguration; + set => _defaultCacheConfiguration = value ?? new RedisCacheConfiguration(); + } + static RedisCacheProvider() { Log = NHibernateLogger.For(typeof(RedisCacheProvider)); @@ -33,10 +43,42 @@ static RedisCacheProvider() } } + private static RedisCacheConfiguration CreateCacheConfiguration() + { + var defaultConfiguration = DefaultCacheConfiguration; + var defaultLockConfiguration = defaultConfiguration.LockConfiguration; + return new RedisCacheConfiguration + { + DefaultRegionStrategy = defaultConfiguration.DefaultRegionStrategy, + DatabaseProvider = defaultConfiguration.DatabaseProvider, + ConnectionMultiplexerProvider = defaultConfiguration.ConnectionMultiplexerProvider, + RegionPrefix = defaultConfiguration.RegionPrefix, + LockConfiguration = + { + RetryTimes = defaultLockConfiguration.RetryTimes, + RetryDelayProvider = defaultLockConfiguration.RetryDelayProvider, + MaxRetryDelay = defaultLockConfiguration.MaxRetryDelay, + ValueProvider = defaultLockConfiguration.ValueProvider, + KeyTimeout = defaultLockConfiguration.KeyTimeout, + AcquireTimeout = defaultLockConfiguration.AcquireTimeout, + KeySuffix = defaultLockConfiguration.KeySuffix, + MinRetryDelay = defaultLockConfiguration.MinRetryDelay + }, + Serializer = defaultConfiguration.Serializer, + RegionStrategyFactory = defaultConfiguration.RegionStrategyFactory, + CacheKeyPrefix = defaultConfiguration.CacheKeyPrefix, + DefaultUseSlidingExpiration = defaultConfiguration.DefaultUseSlidingExpiration, + DefaultExpiration = defaultConfiguration.DefaultExpiration, + DefaultDatabase = defaultConfiguration.DefaultDatabase, + DefaultAppendHashcode = defaultConfiguration.DefaultAppendHashcode, + EnvironmentName = defaultConfiguration.EnvironmentName + }; + } + /// /// The Redis cache configuration that is populated by the NHibernate configuration. /// - public RedisCacheConfiguration CacheConfiguration { get; } = new RedisCacheConfiguration(); + public RedisCacheConfiguration CacheConfiguration { get; } = CreateCacheConfiguration(); /// public ICache BuildCache(string regionName, IDictionary properties) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs index a3bba8ca..68c79a7f 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs @@ -35,58 +35,58 @@ public RedisCacheRegionConfiguration(string regionName) /// The name of the environment that will be prepended before each cache key in order to allow having /// multiple environments on the same Redis database. /// - public string EnvironmentName { get; set; } + public string EnvironmentName { get; internal set; } /// /// The prefix that will be prepended before each cache key in order to avoid having collisions when multiple clients /// uses the same Redis database. /// - public string CacheKeyPrefix { get; set; } + public string CacheKeyPrefix { get; internal set; } /// /// The expiration time for the keys to expire. /// - public TimeSpan Expiration { get; set; } + public TimeSpan Expiration { get; internal set; } /// /// The prefix that will be prepended before the region name when building a cache key. /// - public string RegionPrefix { get; set; } + public string RegionPrefix { get; internal set; } /// /// The Redis database index. /// - public int Database { get; set; } + public int Database { get; internal set; } /// /// Whether the expiration is sliding or not. /// - public bool UseSlidingExpiration { get; set; } + public bool UseSlidingExpiration { get; internal set; } /// /// The type. /// - public System.Type RegionStrategy { get; set; } + public System.Type RegionStrategy { get; internal set; } /// /// Whether the hash code of the key should be added to the cache key. /// - public bool AppendHashcode { get; set; } + public bool AppendHashcode { get; internal set; } /// /// The to be used. /// - public IRedisSerializer Serializer { get; set; } + public IRedisSerializer Serializer { get; internal set; } /// /// The instance. /// - public IDatabaseProvider DatabaseProvider { get; set; } = new DefaultDatabaseProvider(); + public IDatabaseProvider DatabaseProvider { get; internal set; } /// /// The configuration for locking keys. /// - public RedisCacheLockConfiguration LockConfiguration { get; set; } + public RedisCacheLockConfiguration LockConfiguration { get; internal set; } /// public override string ToString() From 832b216d0e27e0368b42a6f8ccc0a3e9eb96b136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Wed, 5 Dec 2018 12:30:58 +0100 Subject: [PATCH 19/24] Update to NH5.2 And some cleanup --- .../Async/CacheFixture.cs | 18 +++--- .../CacheFixture.cs | 18 +++--- .../Async/Caches/DistributedRedisCache.cs | 28 ++++---- .../Async/RedisCacheDefaultStrategyFixture.cs | 6 +- .../Async/RedisCacheFixture.cs | 38 +++++------ .../Async/RedisCachePerformanceFixture.cs | 4 +- .../Caches/DistributedRedisCache.cs | 34 +++++----- .../CustomObjectsFactory.cs | 3 - .../DistributedRedisCacheFixture.cs | 4 -- .../DistributedRedisCacheProvider.cs | 9 +-- .../RedisCacheDefaultStrategyFixture.cs | 5 -- .../RedisCacheFastStrategyFixture.cs | 9 +-- .../RedisCacheFixture.cs | 37 ++++++----- .../RedisCachePerformanceFixture.cs | 4 +- .../RedisCacheProviderFixture.cs | 18 ++---- .../AbstractRegionStrategy.cs | 1 - .../Async/AbstractRegionStrategy.cs | 1 - .../Async/FastRegionStrategy.cs | 4 +- .../Async/RedisCache.cs | 64 +++++-------------- .../Async/RedisKeyLocker.cs | 3 +- .../BinaryRedisSerializer.cs | 1 - .../CacheConfig.cs | 2 - .../ConfigurationHelper.cs | 2 +- .../DefaultConnectionMultiplexerProvider.cs | 7 +- .../DefaultDatabaseProvider.cs | 7 +- .../DefaultRegionStrategy.cs | 2 +- .../FastRegionStrategy.cs | 3 - .../IConnectionMultiplexerProvider.cs | 7 +- .../IDatabaseProvider.cs | 7 +- .../LuaScriptProvider.cs | 5 +- .../NHibernate.Caches.StackExRedis.csproj | 3 +- .../RedisCache.cs | 60 +++++------------ .../RedisCacheLockConfiguration.cs | 2 +- .../RedisCacheProvider.cs | 6 +- .../RedisCacheRegionConfiguration.cs | 3 - .../RedisEnvironment.cs | 2 +- .../RedisKeyLocker.cs | 4 +- .../RegionConfig.cs | 4 -- .../RetryPolicy.cs | 5 +- Tools/packages.config | 2 +- 40 files changed, 150 insertions(+), 292 deletions(-) diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index fe82eda8..9e46911e 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -82,8 +82,8 @@ public async Task TestLockUnlockAsync() for (var i = 0; i < 2; i++) { - await (cache.LockAsync(key, CancellationToken.None)); - await (cache.UnlockAsync(key, CancellationToken.None)); + var lockValue = await (cache.LockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); } } @@ -103,7 +103,7 @@ public async Task TestConcurrentLockUnlockAsync() // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads // Thread 1 - await (cache.LockAsync(key, CancellationToken.None)); + var lockValue = await (cache.LockAsync(key, CancellationToken.None)); // Thread 2 try @@ -112,7 +112,7 @@ public async Task TestConcurrentLockUnlockAsync() } finally { - await (cache.UnlockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); } // Thread 3 @@ -122,14 +122,14 @@ public async Task TestConcurrentLockUnlockAsync() } finally { - await (cache.UnlockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); } - + // Thread 1 - await (cache.UnlockAsync(key, CancellationToken.None)); + await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); - Assert.DoesNotThrowAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should be unlocked"); - await (cache.UnlockAsync(key, CancellationToken.None)); + Assert.DoesNotThrowAsync(async () => lockValue = await (cache.LockAsync(key, CancellationToken.None)), "The key should be unlocked"); + await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); await (cache.RemoveAsync(key, CancellationToken.None)); } diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 935dbfa0..89a0df57 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -76,8 +76,8 @@ public void TestLockUnlock() for (var i = 0; i < 2; i++) { - cache.Lock(key); - cache.Unlock(key); + var lockValue = cache.Lock(key); + cache.Unlock(key, lockValue); } } @@ -97,7 +97,7 @@ public void TestConcurrentLockUnlock() // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads // Thread 1 - cache.Lock(key); + var lockValue = cache.Lock(key); // Thread 2 try @@ -106,7 +106,7 @@ public void TestConcurrentLockUnlock() } finally { - cache.Unlock(key); + cache.Unlock(key, lockValue); } // Thread 3 @@ -116,14 +116,14 @@ public void TestConcurrentLockUnlock() } finally { - cache.Unlock(key); + cache.Unlock(key, lockValue); } - + // Thread 1 - cache.Unlock(key); + cache.Unlock(key, lockValue); - Assert.DoesNotThrow(() => cache.Lock(key), "The key should be unlocked"); - cache.Unlock(key); + Assert.DoesNotThrow(() => lockValue = cache.Lock(key), "The key should be unlocked"); + cache.Unlock(key, lockValue); cache.Remove(key); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs index 6c93382f..4c8329ed 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -11,18 +11,17 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; namespace NHibernate.Caches.StackExRedis.Tests.Caches { - public partial class DistributedRedisCache : ICache + using System.Threading.Tasks; + using System.Threading; + public partial class DistributedRedisCache : CacheBase { /// - public Task GetAsync(object key, CancellationToken cancellationToken) + public override Task GetAsync(object key, CancellationToken cancellationToken) { // Use a random strategy to get the value. // A real distributed cache should use a proper load balancing. @@ -31,7 +30,7 @@ public Task GetAsync(object key, CancellationToken cancellationToken) } /// - public async Task PutAsync(object key, object value, CancellationToken cancellationToken) + public override async Task PutAsync(object key, object value, CancellationToken cancellationToken) { foreach (var strategy in _regionStrategies) { @@ -40,7 +39,7 @@ public async Task PutAsync(object key, object value, CancellationToken cancellat } /// - public async Task RemoveAsync(object key, CancellationToken cancellationToken) + public override async Task RemoveAsync(object key, CancellationToken cancellationToken) { foreach (var strategy in _regionStrategies) { @@ -49,7 +48,7 @@ public async Task RemoveAsync(object key, CancellationToken cancellationToken) } /// - public async Task ClearAsync(CancellationToken cancellationToken) + public override async Task ClearAsync(CancellationToken cancellationToken) { foreach (var strategy in _regionStrategies) { @@ -58,7 +57,7 @@ public async Task ClearAsync(CancellationToken cancellationToken) } /// - public async Task LockAsync(object key, CancellationToken cancellationToken) + public override async Task LockAsync(object key, CancellationToken cancellationToken) { // A simple locking that requires all instances to obtain the lock // A real distributed cache should use something like the Redlock algorithm. @@ -69,6 +68,8 @@ public async Task LockAsync(object key, CancellationToken cancellationToken) { lockValues[i] = await (_regionStrategies[i].LockAsync(key, cancellationToken)); } + + return lockValues; } catch (CacheException) { @@ -85,14 +86,13 @@ public async Task LockAsync(object key, CancellationToken cancellationToken) } /// - public async Task UnlockAsync(object key, CancellationToken cancellationToken) + public override async Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) { - foreach (var strategy in _regionStrategies) + var lockValues = (string[]) lockValue; + for (var i = 0; i < _regionStrategies.Length; i++) { - // TODO: use the lockValue when upgrading to NH 5.2 - await (strategy.UnlockAsync(key, null, cancellationToken)); + await (_regionStrategies[i].UnlockAsync(key, lockValues[i], cancellationToken)); } } - } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs index 105c3bca..bb7017ef 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs @@ -9,17 +9,13 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; -using NHibernate.Caches.Common.Tests; using NUnit.Framework; namespace NHibernate.Caches.StackExRedis.Tests { + using System.Threading.Tasks; public partial class RedisCacheDefaultStrategyFixture : RedisCacheFixture { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 3a532cc4..09955d26 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -13,9 +13,7 @@ using System.Globalization; using System.Linq; using System.Reflection; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using NHibernate.Cache; @@ -30,6 +28,7 @@ namespace NHibernate.Caches.StackExRedis.Tests { + using System.Threading.Tasks; public abstract partial class RedisCacheFixture : CacheFixture { @@ -58,7 +57,7 @@ public async Task TestNHibernateAnyTypeSerializationAsync() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null, CancellationToken.None)); Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); var anyObject = cacheEntry.DisassembledState[0]; @@ -126,7 +125,7 @@ public async Task TestNHibernateStandardTypesSerializationAsync() {NHibernateUtil.TrueFalse, false}, {NHibernateUtil.YesNo, true}, {NHibernateUtil.Class, typeof(IType)}, - {NHibernateUtil.ClassMetaType, entityName}, + {NHibernateUtil.MetaType, entityName}, {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, {NHibernateUtil.AnsiChar, 'a'}, {NHibernateUtil.XmlDoc, xmlDoc}, @@ -142,7 +141,7 @@ public async Task TestNHibernateStandardTypesSerializationAsync() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null, CancellationToken.None)); var cache = GetDefaultCache(); await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); @@ -171,7 +170,7 @@ public async Task TestNHibernateCacheEntrySerializationAsync() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); + var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null, CancellationToken.None)); var cache = GetDefaultCache(); await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); @@ -197,7 +196,7 @@ public async Task TestNHibernateCollectionCacheEntrySerializationAsync() collection.Disassemble(null).Returns(o => new object[] {"test"}); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); - var cacheEntry = new CollectionCacheEntry(collection, null); + var cacheEntry = await (CollectionCacheEntry.CreateAsync(collection, null, CancellationToken.None)); Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); var cache = GetDefaultCache(); @@ -216,7 +215,10 @@ public async Task TestNHibernateCacheLockSerializationAsync() { var sfImpl = Substitute.For(); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); - var cacheEntry = new CacheLock(1234, 1, 5); + var cacheEntry = new CacheLock + { + Timeout = 1234, Id = 1, Version = 5 + }; cacheEntry.Lock(123, 2); var cache = GetDefaultCache(); @@ -240,7 +242,10 @@ public async Task TestNHibernateCachedItemSerializationAsync() { var sfImpl = Substitute.For(); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); - var cacheEntry = new CachedItem("test", 111, 5); + var cacheEntry = new CachedItem + { + Value = "test", FreshTimestamp = 111, Version = 5 + }; cacheEntry.Lock(123, 2); var cache = GetDefaultCache(); @@ -342,7 +347,7 @@ public async Task TestNonEqualObjectsWithEqualToStringUseHashCodeAsync() await (cache.PutAsync(obj1, value, CancellationToken.None)); Assert.That(await (cache.GetAsync(obj1, CancellationToken.None)), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + Assert.That(await (cache.GetAsync(obj2, CancellationToken.None)), Is.Null, "Unexpectedly found a cache entry for key obj2 after obj1 put"); await (cache.RemoveAsync(obj1, CancellationToken.None)); } @@ -392,7 +397,8 @@ public async Task TestPutManyAsync() var cache = (RedisCache) GetDefaultCache(); // Due to async version, it may already be there. - await (cache.RemoveManyAsync(keys, CancellationToken.None)); + foreach (var key in keys) + await (cache.RemoveAsync(key, CancellationToken.None)); Assert.That(await (cache.GetManyAsync(keys, CancellationToken.None)), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); @@ -428,7 +434,8 @@ public async Task TestRemoveManyAsync() Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); // remove it - await (cache.RemoveManyAsync(keys, CancellationToken.None)); + foreach (var key in keys) + await (cache.RemoveAsync(key, CancellationToken.None)); // make sure it's not there items = await (cache.GetManyAsync(keys, CancellationToken.None)); @@ -508,12 +515,5 @@ public async Task TestNullKeyGetManyAsync() var items = await (cache.GetManyAsync(null, CancellationToken.None)); Assert.IsNull(items); } - - [Test] - public void TestNullKeyRemoveManyAsync() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.ThrowsAsync(() => cache.RemoveManyAsync(null, CancellationToken.None)); - } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs index d22e438f..632b5f7d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs @@ -99,7 +99,7 @@ public Task TestLockUnlockManyOperationAsync() }); } - private async Task PutCacheDataAsync(ICache cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) + private async Task PutCacheDataAsync(CacheBase cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) { foreach (var pair in cacheData) { @@ -107,7 +107,7 @@ public Task TestLockUnlockManyOperationAsync() } } - private async Task RemoveCacheDataAsync(ICache cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) + private async Task RemoveCacheDataAsync(CacheBase cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) { foreach (var pair in cacheData) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs index 2d123be3..9439ad0b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; namespace NHibernate.Caches.StackExRedis.Tests.Caches @@ -11,7 +8,7 @@ namespace NHibernate.Caches.StackExRedis.Tests.Caches /// /// Operates with multiple independent Redis instances. /// - public partial class DistributedRedisCache : ICache + public partial class DistributedRedisCache : CacheBase { private readonly AbstractRegionStrategy[] _regionStrategies; private readonly Random _random = new Random(); @@ -29,16 +26,16 @@ public DistributedRedisCache(RedisCacheRegionConfiguration configuration, IEnume public IEnumerable RegionStrategies => _regionStrategies; /// - public int Timeout { get; } + public override int Timeout { get; } /// - public string RegionName { get; } + public override string RegionName { get; } /// - public long NextTimestamp() => Timestamper.Next(); + public override long NextTimestamp() => Timestamper.Next(); /// - public object Get(object key) + public override object Get(object key) { // Use a random strategy to get the value. // A real distributed cache should use a proper load balancing. @@ -47,7 +44,7 @@ public object Get(object key) } /// - public void Put(object key, object value) + public override void Put(object key, object value) { foreach (var strategy in _regionStrategies) { @@ -56,7 +53,7 @@ public void Put(object key, object value) } /// - public void Remove(object key) + public override void Remove(object key) { foreach (var strategy in _regionStrategies) { @@ -65,7 +62,7 @@ public void Remove(object key) } /// - public void Clear() + public override void Clear() { foreach (var strategy in _regionStrategies) { @@ -74,12 +71,12 @@ public void Clear() } /// - public void Destroy() + public override void Destroy() { } /// - public void Lock(object key) + public override object Lock(object key) { // A simple locking that requires all instances to obtain the lock // A real distributed cache should use something like the Redlock algorithm. @@ -90,6 +87,8 @@ public void Lock(object key) { lockValues[i] = _regionStrategies[i].Lock(key); } + + return lockValues; } catch (CacheException) { @@ -106,14 +105,13 @@ public void Lock(object key) } /// - public void Unlock(object key) + public override void Unlock(object key, object lockValue) { - foreach (var strategy in _regionStrategies) + var lockValues = (string[]) lockValue; + for (var i = 0; i < _regionStrategies.Length; i++) { - // TODO: use the lockValue when upgrading to NH 5.2 - strategy.Unlock(key, null); + _regionStrategies[i].Unlock(key, lockValues[i]); } } - } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs index abd5f844..aea90b29 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NHibernate.Bytecode; namespace NHibernate.Caches.StackExRedis.Tests diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs index 94e39cb3..ae541055 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs @@ -1,9 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; using NHibernate.Caches.StackExRedis.Tests.Providers; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs index 15850022..ad75efd0 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using NHibernate.Cache; using NHibernate.Caches.StackExRedis.Tests.Caches; using StackExchange.Redis; @@ -28,7 +23,7 @@ protected override void Start(string configurationString, IDictionary - protected override ICache BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) + protected override CacheBase BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) { var strategies = new List(); foreach (var connectionMultiplexer in _connectionMultiplexers) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs index 7e061984..a6c9380e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs @@ -1,11 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; -using NHibernate.Caches.Common.Tests; using NUnit.Framework; namespace NHibernate.Caches.StackExRedis.Tests diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs index 978ab4e6..0b7884fc 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using NHibernate.Cache; -using NHibernate.Caches.Common.Tests; +using System.Collections.Generic; using NUnit.Framework; namespace NHibernate.Caches.StackExRedis.Tests diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index 07e729d0..a3ff3600 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -3,9 +3,7 @@ using System.Globalization; using System.Linq; using System.Reflection; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using NHibernate.Cache; @@ -61,7 +59,7 @@ public void TestNHibernateAnyTypeSerialization() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); var anyObject = cacheEntry.DisassembledState[0]; @@ -129,7 +127,7 @@ public void TestNHibernateStandardTypesSerialization() {NHibernateUtil.TrueFalse, false}, {NHibernateUtil.YesNo, true}, {NHibernateUtil.Class, typeof(IType)}, - {NHibernateUtil.ClassMetaType, entityName}, + {NHibernateUtil.MetaType, entityName}, {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, {NHibernateUtil.AnsiChar, 'a'}, {NHibernateUtil.XmlDoc, xmlDoc}, @@ -145,7 +143,7 @@ public void TestNHibernateStandardTypesSerialization() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); + var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); var cache = GetDefaultCache(); cache.Put(cacheKey, cacheEntry); @@ -174,7 +172,7 @@ public void TestNHibernateCacheEntrySerialization() entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = new CacheEntry(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); + var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); var cache = GetDefaultCache(); cache.Put(cacheKey, cacheEntry); @@ -200,7 +198,7 @@ public void TestNHibernateCollectionCacheEntrySerialization() collection.Disassemble(null).Returns(o => new object[] {"test"}); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); - var cacheEntry = new CollectionCacheEntry(collection, null); + var cacheEntry = CollectionCacheEntry.Create(collection, null); Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); var cache = GetDefaultCache(); @@ -219,7 +217,10 @@ public void TestNHibernateCacheLockSerialization() { var sfImpl = Substitute.For(); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); - var cacheEntry = new CacheLock(1234, 1, 5); + var cacheEntry = new CacheLock + { + Timeout = 1234, Id = 1, Version = 5 + }; cacheEntry.Lock(123, 2); var cache = GetDefaultCache(); @@ -243,7 +244,10 @@ public void TestNHibernateCachedItemSerialization() { var sfImpl = Substitute.For(); var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); - var cacheEntry = new CachedItem("test", 111, 5); + var cacheEntry = new CachedItem + { + Value = "test", FreshTimestamp = 111, Version = 5 + }; cacheEntry.Lock(123, 2); var cache = GetDefaultCache(); @@ -412,7 +416,7 @@ public void TestNonEqualObjectsWithEqualToStringUseHashCode() cache.Put(obj1, value); Assert.That(cache.Get(obj1), Is.EqualTo(value), "Unable to retrieved cached object for key obj1"); - Assert.That(cache.Get(obj2), Is.Null, "Unexectedly found a cache entry for key obj2 after obj1 put"); + Assert.That(cache.Get(obj2), Is.Null, "Unexpectedly found a cache entry for key obj2 after obj1 put"); cache.Remove(obj1); } @@ -462,7 +466,8 @@ public void TestPutMany() var cache = (RedisCache) GetDefaultCache(); // Due to async version, it may already be there. - cache.RemoveMany(keys); + foreach (var key in keys) + cache.Remove(key); Assert.That(cache.GetMany(keys), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); @@ -498,7 +503,8 @@ public void TestRemoveMany() Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); // remove it - cache.RemoveMany(keys); + foreach (var key in keys) + cache.Remove(key); // make sure it's not there items = cache.GetMany(keys); @@ -578,12 +584,5 @@ public void TestNullKeyGetMany() var items = cache.GetMany(null); Assert.IsNull(items); } - - [Test] - public void TestNullKeyRemoveMany() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.Throws(() => cache.RemoveMany(null)); - } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs index 8971fa79..4b4cbe7c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs @@ -195,7 +195,7 @@ private async Task TestOperationAsync(string operation, bool fillData, } } - private void PutCacheData(ICache cache, Dictionary> cacheData) + private void PutCacheData(CacheBase cache, Dictionary> cacheData) { foreach (var pair in cacheData) { @@ -203,7 +203,7 @@ private void PutCacheData(ICache cache, Dictionary> cache } } - private void RemoveCacheData(ICache cache, Dictionary> cacheData) + private void RemoveCacheData(CacheBase cache, Dictionary> cacheData) { foreach (var pair in cacheData) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs index aa0c2b9a..c56d5eb8 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -1,17 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using NHibernate.Bytecode; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; using NSubstitute; -using NSubstitute.Extensions; using NUnit.Framework; -using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis.Tests { @@ -63,7 +57,7 @@ public void TestDefaultCacheConfiguration() var retryDelayProvider = Substitute.For(); var lockValueProvider = Substitute.For(); var regionStrategyFactory = Substitute.For(); - var serialzer = Substitute.For(); + var serializer = Substitute.For(); var defaultConfig = RedisCacheProvider.DefaultCacheConfiguration; defaultConfig.ConnectionMultiplexerProvider = connectionProvider; @@ -71,7 +65,7 @@ public void TestDefaultCacheConfiguration() defaultConfig.LockConfiguration.ValueProvider = lockValueProvider; defaultConfig.LockConfiguration.RetryDelayProvider = retryDelayProvider; defaultConfig.RegionStrategyFactory = regionStrategyFactory; - defaultConfig.Serializer = serialzer; + defaultConfig.Serializer = serializer; var provider = (RedisCacheProvider) GetNewProvider(); var config = provider.CacheConfiguration; @@ -81,7 +75,7 @@ public void TestDefaultCacheConfiguration() Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); - Assert.That(config.Serializer, Is.EqualTo(serialzer)); + Assert.That(config.Serializer, Is.EqualTo(serializer)); RedisCacheProvider.DefaultCacheConfiguration = new RedisCacheConfiguration(); } @@ -99,14 +93,14 @@ public void TestUserProvidedObjectsFactory() var retryDelayProvider = Substitute.For(); var lockValueProvider = Substitute.For(); var regionStrategyFactory = Substitute.For(); - var serialzer = Substitute.For(); + var serializer = Substitute.For(); customObjectsFactory.RegisterSingleton(connectionProvider); customObjectsFactory.RegisterSingleton(databaseProvider); customObjectsFactory.RegisterSingleton(retryDelayProvider); customObjectsFactory.RegisterSingleton(lockValueProvider); customObjectsFactory.RegisterSingleton(regionStrategyFactory); - customObjectsFactory.RegisterSingleton(serialzer); + customObjectsFactory.RegisterSingleton(serializer); field.SetValue(Cfg.Environment.BytecodeProvider, customObjectsFactory); @@ -118,7 +112,7 @@ public void TestUserProvidedObjectsFactory() Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); - Assert.That(config.Serializer, Is.EqualTo(serialzer)); + Assert.That(config.Serializer, Is.EqualTo(serializer)); field.SetValue(Cfg.Environment.BytecodeProvider, new ActivatorObjectsFactory()); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index 2f4d7799..edb57ab4 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using NHibernate.Cache; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index 27a09084..024fb7af 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -9,7 +9,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using NHibernate.Cache; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs index 6ee99103..36bab894 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs @@ -10,13 +10,11 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { + using System.Threading.Tasks; using System.Threading; public partial class FastRegionStrategy : AbstractRegionStrategy { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs index 121c0ad1..b117352e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs @@ -8,21 +8,17 @@ //------------------------------------------------------------------------------ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using NHibernate.Cache; -using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { - public partial class RedisCache : ICache + using System.Threading.Tasks; + using System.Threading; + public partial class RedisCache : CacheBase { /// - public Task GetAsync(object key, CancellationToken cancellationToken) + public override Task GetAsync(object key, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -32,7 +28,7 @@ public Task GetAsync(object key, CancellationToken cancellationToken) } /// - public Task GetManyAsync(object[] keys, CancellationToken cancellationToken) + public override Task GetManyAsync(object[] keys, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -42,7 +38,7 @@ public Task GetManyAsync(object[] keys, CancellationToken cancellation } /// - public Task PutAsync(object key, object value, CancellationToken cancellationToken) + public override Task PutAsync(object key, object value, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -52,7 +48,7 @@ public Task PutAsync(object key, object value, CancellationToken cancellationTok } /// - public Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) + public override Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -62,7 +58,7 @@ public Task PutManyAsync(object[] keys, object[] values, CancellationToken cance } /// - public Task RemoveAsync(object key, CancellationToken cancellationToken) + public override Task RemoveAsync(object key, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -72,17 +68,7 @@ public Task RemoveAsync(object key, CancellationToken cancellationToken) } /// - public Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return RegionStrategy.RemoveManyAsync(keys, cancellationToken); - } - - /// - public Task ClearAsync(CancellationToken cancellationToken) + public override Task ClearAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -92,31 +78,21 @@ public Task ClearAsync(CancellationToken cancellationToken) } /// - Task ICache.LockAsync(object key, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return LockAsync(key, cancellationToken); - } - - /// - public async Task LockAsync(object key, CancellationToken cancellationToken) + public override async Task LockAsync(object key, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return await (RegionStrategy.LockAsync(key, cancellationToken)).ConfigureAwait(false); } /// - public async Task LockManyAsync(object[] keys, CancellationToken cancellationToken) + public override async Task LockManyAsync(object[] keys, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return await (RegionStrategy.LockManyAsync(keys, cancellationToken)).ConfigureAwait(false); } /// - public Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) + public override Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -126,24 +102,14 @@ public Task UnlockAsync(object key, object lockValue, CancellationToken cancella { return RegionStrategy.UnlockAsync(key, (string)lockValue, cancellationToken); } - catch (Exception ex) + catch (System.Exception ex) { return Task.FromException(ex); } } /// - Task ICache.UnlockAsync(object key, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return UnlockAsync(key, null, cancellationToken); - } - - /// - public Task UnlockManyAsync(object[] keys, object lockValue, CancellationToken cancellationToken) + public override Task UnlockManyAsync(object[] keys, object lockValue, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -153,7 +119,7 @@ public Task UnlockManyAsync(object[] keys, object lockValue, CancellationToken c { return RegionStrategy.UnlockManyAsync(keys, (string) lockValue, cancellationToken); } - catch (Exception ex) + catch (System.Exception ex) { return Task.FromException(ex); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs index e41e1798..1c4e0b68 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs @@ -9,15 +9,14 @@ using System; -using System.Diagnostics; using System.Linq; -using System.Threading; using NHibernate.Cache; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { using System.Threading.Tasks; + using System.Threading; internal partial class RedisKeyLocker { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs index 61ef756d..df0d09ca 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs @@ -1,6 +1,5 @@ using System.IO; using System.Runtime.Serialization.Formatters.Binary; -using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs index ebca790b..fe4f0df2 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs @@ -1,5 +1,3 @@ -using System; - namespace NHibernate.Caches.StackExRedis { /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs index 559e4e4f..5870dd18 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -86,7 +86,7 @@ public static System.Type GetSystemType(string key, IDictionary public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue, INHibernateLogger logger) { - var objectsFactory = Cfg.Environment.BytecodeProvider.ObjectsFactory; + var objectsFactory = Cfg.Environment.ObjectsFactory; var type = GetSystemType(key, properties, null); if (type == null) { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs index 6712a637..b070865d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs index feee30e3..96d30c49 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using StackExchange.Redis; +using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs index 40a7a76f..90d9bc74 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs @@ -9,7 +9,7 @@ namespace NHibernate.Caches.StackExRedis /// /// The default region strategy. This strategy uses a special key that contains the region current version number which is appended /// after the region prefix. Each time a clear operation is performed the version number is increased and an event is send to all - /// clients so that they can update thier local versions. Even if the event was not sent to all clients, each operation has a + /// clients so that they can update their local versions. Even if the event was not sent to all clients, each operation has a /// version check in order to prevent working with stale data. /// public partial class DefaultRegionStrategy : AbstractRegionStrategy diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs index 8a594f65..384b8a7d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs index 88c8834c..e068aebf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using StackExchange.Redis; +using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs index 72f17818..c0524c02 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using StackExchange.Redis; +using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs index 78be27ae..66cd2c0b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj index 1f2346aa..43f40daf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj @@ -18,11 +18,10 @@ - - + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs index aef16f2f..a2f83e14 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs @@ -1,20 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using NHibernate.Cache; -using StackExchange.Redis; +using NHibernate.Cache; namespace NHibernate.Caches.StackExRedis { /// /// A cache used to store objects into a Redis cache. /// - public partial class RedisCache : ICache + public partial class RedisCache : CacheBase { - private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(RedisCache)); - /// /// Default constructor. /// @@ -26,10 +18,10 @@ public RedisCache(string regionName, AbstractRegionStrategy regionStrategy) } /// - public int Timeout { get; } + public override int Timeout { get; } /// - public string RegionName { get; } + public override string RegionName { get; } /// /// The region strategy used by the cache. @@ -37,91 +29,73 @@ public RedisCache(string regionName, AbstractRegionStrategy regionStrategy) public AbstractRegionStrategy RegionStrategy { get; } /// - public object Get(object key) + public override object Get(object key) { return RegionStrategy.Get(key); } /// - public object[] GetMany(object[] keys) + public override object[] GetMany(object[] keys) { return RegionStrategy.GetMany(keys); } /// - public void Put(object key, object value) + public override void Put(object key, object value) { RegionStrategy.Put(key, value); } /// - public void PutMany(object[] keys, object[] values) + public override void PutMany(object[] keys, object[] values) { RegionStrategy.PutMany(keys, values); } /// - public void Remove(object key) + public override void Remove(object key) { RegionStrategy.Remove(key); } /// - public void RemoveMany(object[] keys) - { - RegionStrategy.RemoveMany(keys); - } - - /// - public void Clear() + public override void Clear() { RegionStrategy.Clear(); } /// - public void Destroy() + public override void Destroy() { - // We cannot destroy the region cache as there may be other clients using it. + // No resources to clean-up. } /// - void ICache.Lock(object key) - { - Lock(key); - } - - /// - public object Lock(object key) + public override object Lock(object key) { return RegionStrategy.Lock(key); } /// - public object LockMany(object[] keys) + public override object LockMany(object[] keys) { return RegionStrategy.LockMany(keys); } /// - public void Unlock(object key, object lockValue) + public override void Unlock(object key, object lockValue) { RegionStrategy.Unlock(key, (string)lockValue); } /// - void ICache.Unlock(object key) - { - Unlock(key, null); - } - - /// - public void UnlockMany(object[] keys, object lockValue) + public override void UnlockMany(object[] keys, object lockValue) { RegionStrategy.UnlockMany(keys, (string) lockValue); } /// - public long NextTimestamp() + public override long NextTimestamp() { return Timestamper.Next(); } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs index 1bdf3f0d..34f82ecc 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs @@ -40,7 +40,7 @@ public class RedisCacheLockConfiguration public TimeSpan MaxRetryDelay { get; set; } = TimeSpan.FromMilliseconds(400); /// - /// The minumum delay before retrying to acquire the lock. + /// The minimum delay before retrying to acquire the lock. /// public TimeSpan MinRetryDelay { get; set; } = TimeSpan.FromMilliseconds(10); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index 506da11a..042a428c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -81,7 +81,9 @@ private static RedisCacheConfiguration CreateCacheConfiguration() public RedisCacheConfiguration CacheConfiguration { get; } = CreateCacheConfiguration(); /// +#pragma warning disable 618 public ICache BuildCache(string regionName, IDictionary properties) +#pragma warning restore 618 { if (regionName == null) { @@ -150,8 +152,8 @@ protected virtual void Start(string configurationString, IDictionary /// The region cache configuration. /// NHibernate configuration settings. - /// The builded cache. - protected virtual ICache BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) + /// The built cache. + protected virtual CacheBase BuildCache(RedisCacheRegionConfiguration regionConfiguration, IDictionary properties) { var regionStrategy = CacheConfiguration.RegionStrategyFactory.Create(_connectionMultiplexer, regionConfiguration, properties); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs index 68c79a7f..b5586574 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs index 4ee434d0..b6672698 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs @@ -85,7 +85,7 @@ public static class RedisEnvironment public const string LockMaxRetryDelay = "cache.lock.max_retry_delay"; /// - /// The minumum delay before retrying to acquire the lock in milliseconds. + /// The minimum delay before retrying to acquire the lock in milliseconds. /// public const string LockMinRetryDelay = "cache.lock.min_retry_delay"; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs index ecf8b779..4703efec 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs @@ -1,7 +1,5 @@ using System; -using System.Diagnostics; using System.Linq; -using System.Threading; using NHibernate.Cache; using StackExchange.Redis; @@ -46,7 +44,7 @@ public RedisKeyLocker( () => lockRetryDelayProvider.GetValue(minRetryDelay, maxRetryDelay) ) .ShouldRetry(s => s == null) - .OnFaliure((totalAttempts, elapsedMs, getKeysFn) => + .OnFailure((totalAttempts, elapsedMs, getKeysFn) => throw new CacheException("Unable to acquire cache lock: " + $"region='{regionName}', " + $"keys='{getKeysFn()}', " + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs index 29b7fa13..9d396bd4 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace NHibernate.Caches.StackExRedis { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs index c758cff8..76c8762c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -34,7 +31,7 @@ public RetryPolicy ShouldRetry(Predicate predicate) return this; } - public RetryPolicy OnFaliure(Action callback) + public RetryPolicy OnFailure(Action callback) { _onFailureCallback = callback; return this; diff --git a/Tools/packages.config b/Tools/packages.config index d8019d39..cb540977 100644 --- a/Tools/packages.config +++ b/Tools/packages.config @@ -7,5 +7,5 @@ - + From 094256da1efb998dd81b83511c158a5a41d95059 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 8 Dec 2018 15:24:10 +0100 Subject: [PATCH 20/24] Implemented TODOs --- .../Async/CacheFixture.cs | 152 ++++++- .../CacheFixture.cs | 152 ++++++- .../Async/Caches/DistributedRedisCache.cs | 48 +++ .../Async/RedisCacheFixture.cs | 364 ----------------- .../Async/RedisCachePerformanceFixture.cs | 8 +- .../Caches/DistributedRedisCache.cs | 51 ++- .../DistributedRedisCacheProvider.cs | 4 +- .../RedisCacheFixture.cs | 370 ------------------ .../RedisCachePerformanceFixture.cs | 26 +- .../RedisCacheProviderFixture.cs | 71 ++-- .../AbstractRegionStrategy.cs | 7 +- .../Async/AbstractRegionStrategy.cs | 68 ++-- .../BinaryRedisSerializer.cs | 32 -- .../ConfigurationHelper.cs | 38 +- .../DefaultCacheRegionStrategyFactory.cs | 17 +- .../IRedisSerializer.cs | 25 -- .../NHibernate.Caches.StackExRedis.csproj | 3 + .../RedisCacheConfiguration.cs | 9 +- .../RedisCacheProvider.cs | 12 +- .../RedisCacheRegionConfiguration.cs | 5 +- 20 files changed, 513 insertions(+), 949 deletions(-) delete mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs delete mode 100644 StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index 9e46911e..ddd5e68e 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using NHibernate.Cache; using NUnit.Framework; @@ -104,26 +105,10 @@ public async Task TestConcurrentLockUnlockAsync() // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads // Thread 1 var lockValue = await (cache.LockAsync(key, CancellationToken.None)); - // Thread 2 - try - { - Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should be locked"); - } - finally - { - await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); - } - + Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should be locked"); // Thread 3 - try - { - Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should still be locked"); - } - finally - { - await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); - } + Assert.ThrowsAsync(() => cache.LockAsync(key, CancellationToken.None), "The key should still be locked"); // Thread 1 await (cache.UnlockAsync(key, lockValue, CancellationToken.None)); @@ -207,6 +192,137 @@ public async Task TestRegionsAsync() Assert.That(get2, Is.EqualTo(s2), "Unexpected value in cache2"); } + [Test] + public async Task TestPutManyAsync() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestPut{i}"; + values[i] = $"valuePut{i}"; + } + + var cache = GetDefaultCache(); + // Due to async version, it may already be there. + foreach (var key in keys) + await (cache.RemoveAsync(key, CancellationToken.None)); + + Assert.That(await (cache.GetManyAsync(keys, CancellationToken.None)), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); + + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + var items = await (cache.GetManyAsync(keys, CancellationToken.None)); + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); + Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); + } + } + + [Test] + public async Task TestRemoveManyAsync() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestRemove{i}"; + values[i] = $"valueRemove{i}"; + } + + var cache = GetDefaultCache(); + + // add the item + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + + // make sure it's there + var items = await (cache.GetManyAsync(keys, CancellationToken.None)); + Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); + + // remove it + foreach (var key in keys) + await (cache.RemoveAsync(key, CancellationToken.None)); + + // make sure it's not there + items = await (cache.GetManyAsync(keys, CancellationToken.None)); + Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); + } + + [Test] + public async Task TestLockUnlockManyAsync() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestLock{i}"; + values[i] = $"valueLock{i}"; + } + + var cache = GetDefaultCache(); + + // add the item + await (cache.PutManyAsync(keys, values, CancellationToken.None)); + await (cache.LockManyAsync(keys, CancellationToken.None)); + Assert.ThrowsAsync(() => cache.LockManyAsync(keys, CancellationToken.None), "all items should be locked"); + + await (Task.Delay(cache.Timeout / Timestamper.OneMs)); + + for (var i = 0; i < 2; i++) + { + Assert.DoesNotThrowAsync(async () => + { + await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys, CancellationToken.None)), CancellationToken.None)); + }, "the items should be unlocked"); + } + + // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. + var lockValue = await (cache.LockManyAsync(keys.Take(5).ToArray(), CancellationToken.None)); + + Assert.ThrowsAsync(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "the fifth key should be locked"); + + Assert.DoesNotThrowAsync(async () => + { + await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys.Skip(5).ToArray(), CancellationToken.None)), CancellationToken.None)); + }, "the last 5 keys should not be locked."); + + // Unlock the first 5 keys + await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); + + Assert.DoesNotThrowAsync(async () => + { + lockValue = await (cache.LockManyAsync(keys, CancellationToken.None)); + await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); + }, "the first 5 keys should not be locked."); + } + + [Test] + public void TestNullKeyPutManyAsync() + { + var cache = GetDefaultCache(); + Assert.ThrowsAsync(() => cache.PutManyAsync(null, null, CancellationToken.None)); + } + + [Test] + public void TestNullValuePutManyAsync() + { + var cache = GetDefaultCache(); + Assert.ThrowsAsync(() => cache.PutManyAsync(new object[] { "keyTestNullValuePut" }, null, CancellationToken.None)); + } + + [Test] + public async Task TestNullKeyGetManyAsync() + { + var cache = GetDefaultCache(); + await (cache.PutAsync("keyTestNullKeyGet", "value", CancellationToken.None)); + Assert.ThrowsAsync(() => cache.GetManyAsync(null, CancellationToken.None)); + } + [Test] public async Task TestNonEqualObjectsWithEqualHashCodeAndToStringAsync() { diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 89a0df57..7973bde9 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using NHibernate.Cache; using NUnit.Framework; @@ -98,26 +99,10 @@ public void TestConcurrentLockUnlock() // Simulate NHibernate ReadWriteCache behavior with multiple concurrent threads // Thread 1 var lockValue = cache.Lock(key); - // Thread 2 - try - { - Assert.Throws(() => cache.Lock(key), "The key should be locked"); - } - finally - { - cache.Unlock(key, lockValue); - } - + Assert.Throws(() => cache.Lock(key), "The key should be locked"); // Thread 3 - try - { - Assert.Throws(() => cache.Lock(key), "The key should still be locked"); - } - finally - { - cache.Unlock(key, lockValue); - } + Assert.Throws(() => cache.Lock(key), "The key should still be locked"); // Thread 1 cache.Unlock(key, lockValue); @@ -208,6 +193,137 @@ public void TestRegions() Assert.That(get2, Is.EqualTo(s2), "Unexpected value in cache2"); } + [Test] + public void TestPutMany() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestPut{i}"; + values[i] = $"valuePut{i}"; + } + + var cache = GetDefaultCache(); + // Due to async version, it may already be there. + foreach (var key in keys) + cache.Remove(key); + + Assert.That(cache.GetMany(keys), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); + + cache.PutMany(keys, values); + var items = cache.GetMany(keys); + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); + Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); + } + } + + [Test] + public void TestRemoveMany() + { + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestRemove{i}"; + values[i] = $"valueRemove{i}"; + } + + var cache = GetDefaultCache(); + + // add the item + cache.PutMany(keys, values); + + // make sure it's there + var items = cache.GetMany(keys); + Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); + + // remove it + foreach (var key in keys) + cache.Remove(key); + + // make sure it's not there + items = cache.GetMany(keys); + Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); + } + + [Test] + public void TestLockUnlockMany() + { + if (!SupportsLocking) + Assert.Ignore("Test not supported by provider"); + + var keys = new object[10]; + var values = new object[10]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = $"keyTestLock{i}"; + values[i] = $"valueLock{i}"; + } + + var cache = GetDefaultCache(); + + // add the item + cache.PutMany(keys, values); + cache.LockMany(keys); + Assert.Throws(() => cache.LockMany(keys), "all items should be locked"); + + Thread.Sleep(cache.Timeout / Timestamper.OneMs); + + for (var i = 0; i < 2; i++) + { + Assert.DoesNotThrow(() => + { + cache.UnlockMany(keys, cache.LockMany(keys)); + }, "the items should be unlocked"); + } + + // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. + var lockValue = cache.LockMany(keys.Take(5).ToArray()); + + Assert.Throws(() => cache.LockMany(keys.Skip(4).ToArray()), "the fifth key should be locked"); + + Assert.DoesNotThrow(() => + { + cache.UnlockMany(keys, cache.LockMany(keys.Skip(5).ToArray())); + }, "the last 5 keys should not be locked."); + + // Unlock the first 5 keys + cache.UnlockMany(keys, lockValue); + + Assert.DoesNotThrow(() => + { + lockValue = cache.LockMany(keys); + cache.UnlockMany(keys, lockValue); + }, "the first 5 keys should not be locked."); + } + + [Test] + public void TestNullKeyPutMany() + { + var cache = GetDefaultCache(); + Assert.Throws(() => cache.PutMany(null, null)); + } + + [Test] + public void TestNullValuePutMany() + { + var cache = GetDefaultCache(); + Assert.Throws(() => cache.PutMany(new object[] { "keyTestNullValuePut" }, null)); + } + + [Test] + public void TestNullKeyGetMany() + { + var cache = GetDefaultCache(); + cache.Put("keyTestNullKeyGet", "value"); + Assert.Throws(() => cache.GetMany(null)); + } + [Serializable] protected class SomeObject { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs index 4c8329ed..984827fa 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -38,6 +38,15 @@ public override async Task PutAsync(object key, object value, CancellationToken } } + /// + public override async Task PutManyAsync(object[] keys, object[] values, CancellationToken cancellationToken) + { + foreach (var strategy in _regionStrategies) + { + await (strategy.PutManyAsync(keys, values, cancellationToken)); + } + } + /// public override async Task RemoveAsync(object key, CancellationToken cancellationToken) { @@ -85,6 +94,35 @@ public override async Task LockAsync(object key, CancellationToken cance } } + /// + public override async Task LockManyAsync(object[] keys, CancellationToken cancellationToken) + { + // A simple locking that requires all instances to obtain the lock + // A real distributed cache should use something like the Redlock algorithm. + var lockValues = new string[_regionStrategies.Length]; + try + { + for (var i = 0; i < _regionStrategies.Length; i++) + { + lockValues[i] = await (_regionStrategies[i].LockManyAsync(keys, cancellationToken)); + } + + return lockValues; + } + catch (CacheException) + { + for (var i = 0; i < _regionStrategies.Length; i++) + { + if (lockValues[i] == null) + { + continue; + } + await (_regionStrategies[i].UnlockManyAsync(keys, lockValues[i], cancellationToken)); + } + throw; + } + } + /// public override async Task UnlockAsync(object key, object lockValue, CancellationToken cancellationToken) { @@ -94,5 +132,15 @@ public override async Task UnlockAsync(object key, object lockValue, Cancellatio await (_regionStrategies[i].UnlockAsync(key, lockValues[i], cancellationToken)); } } + + /// + public override async Task UnlockManyAsync(object[] keys, object lockValue, CancellationToken cancellationToken) + { + var lockValues = (string[]) lockValue; + for (var i = 0; i < _regionStrategies.Length; i++) + { + await (_regionStrategies[i].UnlockManyAsync(keys, lockValues[i], cancellationToken)); + } + } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs index 09955d26..43a26502 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs @@ -32,238 +32,6 @@ namespace NHibernate.Caches.StackExRedis.Tests public abstract partial class RedisCacheFixture : CacheFixture { - [Test] - public async Task TestNHibernateAnyTypeSerializationAsync() - { - var objectTypeCacheEntryType = typeof(AnyType.ObjectTypeCacheEntry); - var entityNameField = objectTypeCacheEntryType.GetField("entityName", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.That(entityNameField, Is.Not.Null, "field entityName in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); - var idField = objectTypeCacheEntryType.GetField("id", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.That(idField, Is.Not.Null, "field id in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); - - var entityName = nameof(MyEntity); - var propertyValues = new Dictionary - { - {NHibernateUtil.Object, new MyEntity{Id = 2}} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - sessionImpl.BestGuessEntityName(Arg.Any()).Returns(o => o[0].GetType().Name); - sessionImpl.GetContextEntityIdentifier(Arg.Is(o => o is MyEntity)).Returns(o => ((MyEntity) o[0]).Id); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null, CancellationToken.None)); - - Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); - var anyObject = cacheEntry.DisassembledState[0]; - Assert.That(anyObject, Is.TypeOf(objectTypeCacheEntryType)); - Assert.That(entityNameField.GetValue(anyObject), Is.EqualTo(nameof(MyEntity))); - Assert.That(idField.GetValue(anyObject), Is.EqualTo(2)); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.DisassembledState, Has.Length.EqualTo(1)); - var retrievedAnyObject = retrievedCacheEntry.DisassembledState[0]; - Assert.That(retrievedAnyObject, Is.TypeOf(objectTypeCacheEntryType)); - Assert.That(entityNameField.GetValue(retrievedAnyObject), Is.EqualTo(nameof(MyEntity)), - "entityName is different from the original AnyType.ObjectTypeCacheEntry"); - Assert.That(idField.GetValue(retrievedAnyObject), Is.EqualTo(2), - "id is different from the original AnyType.ObjectTypeCacheEntry"); - } - - [Test] - public async Task TestNHibernateStandardTypesSerializationAsync() - { - var entityName = nameof(MyEntity); - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml("XmlDoc"); - var propertyValues = new Dictionary - { - {NHibernateUtil.AnsiString, "test"}, - {NHibernateUtil.Binary, new byte[] {1, 2, 3, 4}}, - {NHibernateUtil.BinaryBlob, new byte[] {1, 2, 3, 4}}, - {NHibernateUtil.Boolean, true}, - {NHibernateUtil.Byte, (byte) 1}, - {NHibernateUtil.Character, 'a'}, - {NHibernateUtil.CultureInfo, CultureInfo.CurrentCulture}, - {NHibernateUtil.DateTime, DateTime.Now}, - {NHibernateUtil.DateTimeNoMs, DateTime.Now}, - {NHibernateUtil.LocalDateTime, DateTime.Now}, - {NHibernateUtil.UtcDateTime, DateTime.UtcNow}, - {NHibernateUtil.LocalDateTimeNoMs, DateTime.Now}, - {NHibernateUtil.UtcDateTimeNoMs, DateTime.UtcNow}, - {NHibernateUtil.DateTimeOffset, DateTimeOffset.Now}, - {NHibernateUtil.Date, DateTime.Today}, - {NHibernateUtil.Decimal, 2.5m}, - {NHibernateUtil.Double, 2.5d}, - {NHibernateUtil.Currency, 2.5m}, - {NHibernateUtil.Guid, Guid.NewGuid()}, - {NHibernateUtil.Int16, (short) 1}, - {NHibernateUtil.Int32, 3}, - {NHibernateUtil.Int64, 3L}, - {NHibernateUtil.SByte, (sbyte) 1}, - {NHibernateUtil.UInt16, (ushort) 1}, - {NHibernateUtil.UInt32, (uint) 1}, - {NHibernateUtil.UInt64, (ulong) 1}, - {NHibernateUtil.Single, 1.1f}, - {NHibernateUtil.String, "test"}, - {NHibernateUtil.StringClob, "test"}, - {NHibernateUtil.Time, DateTime.Now}, - {NHibernateUtil.Ticks, DateTime.Now}, - {NHibernateUtil.TimeAsTimeSpan, TimeSpan.FromMilliseconds(15)}, - {NHibernateUtil.TimeSpan, TimeSpan.FromMilliseconds(1234)}, - {NHibernateUtil.DbTimestamp, DateTime.Now}, - {NHibernateUtil.TrueFalse, false}, - {NHibernateUtil.YesNo, true}, - {NHibernateUtil.Class, typeof(IType)}, - {NHibernateUtil.MetaType, entityName}, - {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, - {NHibernateUtil.AnsiChar, 'a'}, - {NHibernateUtil.XmlDoc, xmlDoc}, - {NHibernateUtil.XDoc, XDocument.Parse("XDoc")}, - {NHibernateUtil.Uri, new Uri("http://test.com")} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null, CancellationToken.None)); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), - "DisassembledState is different from the original CacheEntry"); - } - - [Test] - public async Task TestNHibernateCacheEntrySerializationAsync() - { - var entityName = nameof(MyEntity); - var propertyValues = new Dictionary - { - {NHibernateUtil.String, "test"} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = await (CacheEntry.CreateAsync(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null, CancellationToken.None)); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.AreLazyPropertiesUnfetched, Is.EqualTo(cacheEntry.AreLazyPropertiesUnfetched), - "AreLazyPropertiesUnfetched is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), - "DisassembledState is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.Subclass, Is.EqualTo(cacheEntry.Subclass), - "Subclass is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.Version, Is.EqualTo(cacheEntry.Version), - "Version is different from the original CacheEntry"); - } - - [Test] - public async Task TestNHibernateCollectionCacheEntrySerializationAsync() - { - var sfImpl = Substitute.For(); - var collection = Substitute.For(); - collection.Disassemble(null).Returns(o => new object[] {"test"}); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); - var cacheEntry = await (CollectionCacheEntry.CreateAsync(collection, null, CancellationToken.None)); - Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CollectionCacheEntry) value; - Assert.That(retrievedCacheEntry.State, Has.Length.EqualTo(1)); - Assert.That(retrievedCacheEntry.State[0], Is.EquivalentTo("test"), - "State is different from the original CollectionCacheEntry"); - } - - [Test] - public async Task TestNHibernateCacheLockSerializationAsync() - { - var sfImpl = Substitute.For(); - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); - var cacheEntry = new CacheLock - { - Timeout = 1234, Id = 1, Version = 5 - }; - cacheEntry.Lock(123, 2); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheLock) value; - Assert.That(retrievedCacheEntry.Id, Is.EqualTo(cacheEntry.Id), - "Id is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), - "IsLock is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.WasLockedConcurrently, Is.EqualTo(cacheEntry.WasLockedConcurrently), - "WasLockedConcurrently is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), - "ToString() is different from the original CacheLock"); - } - - [Test] - public async Task TestNHibernateCachedItemSerializationAsync() - { - var sfImpl = Substitute.For(); - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); - var cacheEntry = new CachedItem - { - Value = "test", FreshTimestamp = 111, Version = 5 - }; - cacheEntry.Lock(123, 2); - - var cache = GetDefaultCache(); - await (cache.PutAsync(cacheKey, cacheEntry, CancellationToken.None)); - var value = await (cache.GetAsync(cacheKey, CancellationToken.None)); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CachedItem) value; - Assert.That(retrievedCacheEntry.FreshTimestamp, Is.EqualTo(cacheEntry.FreshTimestamp), - "FreshTimestamp is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), - "IsLock is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.Value, Is.EqualTo(cacheEntry.Value), - "Value is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), - "ToString() is different from the original CachedItem"); - } - [Test] public async Task TestEqualObjectsWithDifferentHashCodeAsync() { @@ -383,137 +151,5 @@ public async Task TestEnvironmentNameAsync() developProvider.Stop(); releaseProvider.Stop(); } - - [Test] - public async Task TestPutManyAsync() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestPut{i}"; - values[i] = $"valuePut{i}"; - } - - var cache = (RedisCache) GetDefaultCache(); - // Due to async version, it may already be there. - foreach (var key in keys) - await (cache.RemoveAsync(key, CancellationToken.None)); - - Assert.That(await (cache.GetManyAsync(keys, CancellationToken.None)), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); - - await (cache.PutManyAsync(keys, values, CancellationToken.None)); - var items = await (cache.GetManyAsync(keys, CancellationToken.None)); - - for (var i = 0; i < items.Length; i++) - { - var item = items[i]; - Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); - Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); - } - } - - [Test] - public async Task TestRemoveManyAsync() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestRemove{i}"; - values[i] = $"valueRemove{i}"; - } - - var cache = (RedisCache) GetDefaultCache(); - - // add the item - await (cache.PutManyAsync(keys, values, CancellationToken.None)); - - // make sure it's there - var items = await (cache.GetManyAsync(keys, CancellationToken.None)); - Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); - - // remove it - foreach (var key in keys) - await (cache.RemoveAsync(key, CancellationToken.None)); - - // make sure it's not there - items = await (cache.GetManyAsync(keys, CancellationToken.None)); - Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); - } - - [Test] - public async Task TestLockUnlockManyAsync() - { - if (!SupportsLocking) - Assert.Ignore("Test not supported by provider"); - - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestLock{i}"; - values[i] = $"valueLock{i}"; - } - - var cache = (RedisCache)GetDefaultCache(); - - // add the item - await (cache.PutManyAsync(keys, values, CancellationToken.None)); - await (cache.LockManyAsync(keys, CancellationToken.None)); - Assert.ThrowsAsync(() => cache.LockManyAsync(keys, CancellationToken.None), "all items should be locked"); - - await (Task.Delay(cache.Timeout / Timestamper.OneMs)); - - for (var i = 0; i < 2; i++) - { - Assert.DoesNotThrowAsync(async () => - { - await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys, CancellationToken.None)), CancellationToken.None)); - }, "the items should be unlocked"); - } - - // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. - var lockValue = await (cache.LockManyAsync(keys.Take(5).ToArray(), CancellationToken.None)); - - Assert.ThrowsAsync(() => cache.LockManyAsync(keys.Skip(4).ToArray(), CancellationToken.None), "the fifth key should be locked"); - - Assert.DoesNotThrowAsync(async () => - { - await (cache.UnlockManyAsync(keys, await (cache.LockManyAsync(keys.Skip(5).ToArray(), CancellationToken.None)), CancellationToken.None)); - }, "the last 5 keys should not be locked."); - - // Unlock the first 5 keys - await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); - - Assert.DoesNotThrowAsync(async () => - { - lockValue = await (cache.LockManyAsync(keys, CancellationToken.None)); - await (cache.UnlockManyAsync(keys, lockValue, CancellationToken.None)); - }, "the first 5 keys should not be locked."); - } - - [Test] - public void TestNullKeyPutManyAsync() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.ThrowsAsync(() => cache.PutManyAsync(null, null, CancellationToken.None)); - } - - [Test] - public void TestNullValuePutManyAsync() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.ThrowsAsync(() => cache.PutManyAsync(new object[] { "keyTestNullValuePut" }, null, CancellationToken.None)); - } - - [Test] - public async Task TestNullKeyGetManyAsync() - { - var cache = (RedisCache) GetDefaultCache(); - await (cache.PutAsync("keyTestNullKeyGet", "value", CancellationToken.None)); - var items = await (cache.GetManyAsync(null, CancellationToken.None)); - Assert.IsNull(items); - } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs index 632b5f7d..7b709643 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs @@ -32,7 +32,7 @@ public Task TestGetOperationAsync() [Test] public Task TestGetManyOperationAsync() { - return TestBatchOperationAsync("GetMany", true, (cache, keys, _) => cache.GetManyAsync(keys, CancellationToken.None)); + return TestBatchOperationAsync("GetMany", true, (cache, keys, _) => cache.GetManyAsync(keys, CancellationToken.None), BatchSize); } [Test] @@ -48,6 +48,7 @@ public Task TestGetManyOperationWithSlidingExpirationAsync() { var props = new Dictionary {{"sliding", "true"}}; return TestBatchOperationAsync("GetMany", true, (cache, keys, _) => cache.GetManyAsync(keys, CancellationToken.None), + batchSize: BatchSize, caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); } @@ -64,6 +65,7 @@ public Task TestPutManyOperationAsync() { var props = new Dictionary {{"expiration", "0"}}; return TestBatchOperationAsync("PutMany", false, (cache, keys, values) => cache.PutManyAsync(keys, values, CancellationToken.None), + batchSize: null, caches: new List {GetFastRedisCache(props)}); } @@ -76,7 +78,7 @@ public Task TestPutOperationWithExpirationAsync() [Test] public Task TestPutManyOperationWithExpirationAsync() { - return TestBatchOperationAsync("PutMany", false, (cache, keys, values) => cache.PutManyAsync(keys, values, CancellationToken.None)); + return TestBatchOperationAsync("PutMany", false, (cache, keys, values) => cache.PutManyAsync(keys, values, CancellationToken.None), null); } [Test] @@ -96,7 +98,7 @@ public Task TestLockUnlockManyOperationAsync() { var value = await (cache.LockManyAsync(keys, CancellationToken.None)); await (cache.UnlockManyAsync(keys, value, CancellationToken.None)); - }); + }, null); } private async Task PutCacheDataAsync(CacheBase cache, Dictionary> cacheData, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs index 9439ad0b..fce1bb72 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs @@ -6,7 +6,8 @@ namespace NHibernate.Caches.StackExRedis.Tests.Caches { /// - /// Operates with multiple independent Redis instances. + /// Operates with multiple independent Redis instances. This cache should not be used in a real environment + /// as its purpose is just to demonstrate that can be extended for a distributed environment. /// public partial class DistributedRedisCache : CacheBase { @@ -52,6 +53,15 @@ public override void Put(object key, object value) } } + /// + public override void PutMany(object[] keys, object[] values) + { + foreach (var strategy in _regionStrategies) + { + strategy.PutMany(keys, values); + } + } + /// public override void Remove(object key) { @@ -104,6 +114,35 @@ public override object Lock(object key) } } + /// + public override object LockMany(object[] keys) + { + // A simple locking that requires all instances to obtain the lock + // A real distributed cache should use something like the Redlock algorithm. + var lockValues = new string[_regionStrategies.Length]; + try + { + for (var i = 0; i < _regionStrategies.Length; i++) + { + lockValues[i] = _regionStrategies[i].LockMany(keys); + } + + return lockValues; + } + catch (CacheException) + { + for (var i = 0; i < _regionStrategies.Length; i++) + { + if (lockValues[i] == null) + { + continue; + } + _regionStrategies[i].UnlockMany(keys, lockValues[i]); + } + throw; + } + } + /// public override void Unlock(object key, object lockValue) { @@ -113,5 +152,15 @@ public override void Unlock(object key, object lockValue) _regionStrategies[i].Unlock(key, lockValues[i]); } } + + /// + public override void UnlockMany(object[] keys, object lockValue) + { + var lockValues = (string[]) lockValue; + for (var i = 0; i < _regionStrategies.Length; i++) + { + _regionStrategies[i].UnlockMany(keys, lockValues[i]); + } + } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs index ad75efd0..80cddf81 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs @@ -6,7 +6,9 @@ namespace NHibernate.Caches.StackExRedis.Tests.Providers { /// - /// Provider for building a cache capable of operating with multiple independent Redis instances. + /// Provider for building a cache capable of operating with multiple independent Redis instances. This provider + /// should not be used in a real environment as its purpose is just to demonstrate that + /// can be extended for a distributed environment. /// public class DistributedRedisCacheProvider : RedisCacheProvider { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs index a3ff3600..8d91d12a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs @@ -28,244 +28,6 @@ public abstract partial class RedisCacheFixture : CacheFixture protected override Func ProviderBuilder => () => new RedisCacheProvider(); - [Serializable] - public class MyEntity - { - public int Id { get; set; } - } - - [Test] - public void TestNHibernateAnyTypeSerialization() - { - var objectTypeCacheEntryType = typeof(AnyType.ObjectTypeCacheEntry); - var entityNameField = objectTypeCacheEntryType.GetField("entityName", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.That(entityNameField, Is.Not.Null, "field entityName in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); - var idField = objectTypeCacheEntryType.GetField("id", BindingFlags.Instance | BindingFlags.NonPublic); - Assert.That(idField, Is.Not.Null, "field id in NHibernate.Type.AnyType.ObjectTypeCacheEntry was not found"); - - var entityName = nameof(MyEntity); - var propertyValues = new Dictionary - { - {NHibernateUtil.Object, new MyEntity{Id = 2}} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - sessionImpl.BestGuessEntityName(Arg.Any()).Returns(o => o[0].GetType().Name); - sessionImpl.GetContextEntityIdentifier(Arg.Is(o => o is MyEntity)).Returns(o => ((MyEntity) o[0]).Id); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); - - Assert.That(cacheEntry.DisassembledState, Has.Length.EqualTo(1)); - var anyObject = cacheEntry.DisassembledState[0]; - Assert.That(anyObject, Is.TypeOf(objectTypeCacheEntryType)); - Assert.That(entityNameField.GetValue(anyObject), Is.EqualTo(nameof(MyEntity))); - Assert.That(idField.GetValue(anyObject), Is.EqualTo(2)); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.DisassembledState, Has.Length.EqualTo(1)); - var retrievedAnyObject = retrievedCacheEntry.DisassembledState[0]; - Assert.That(retrievedAnyObject, Is.TypeOf(objectTypeCacheEntryType)); - Assert.That(entityNameField.GetValue(retrievedAnyObject), Is.EqualTo(nameof(MyEntity)), - "entityName is different from the original AnyType.ObjectTypeCacheEntry"); - Assert.That(idField.GetValue(retrievedAnyObject), Is.EqualTo(2), - "id is different from the original AnyType.ObjectTypeCacheEntry"); - } - - [Test] - public void TestNHibernateStandardTypesSerialization() - { - var entityName = nameof(MyEntity); - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml("XmlDoc"); - var propertyValues = new Dictionary - { - {NHibernateUtil.AnsiString, "test"}, - {NHibernateUtil.Binary, new byte[] {1, 2, 3, 4}}, - {NHibernateUtil.BinaryBlob, new byte[] {1, 2, 3, 4}}, - {NHibernateUtil.Boolean, true}, - {NHibernateUtil.Byte, (byte) 1}, - {NHibernateUtil.Character, 'a'}, - {NHibernateUtil.CultureInfo, CultureInfo.CurrentCulture}, - {NHibernateUtil.DateTime, DateTime.Now}, - {NHibernateUtil.DateTimeNoMs, DateTime.Now}, - {NHibernateUtil.LocalDateTime, DateTime.Now}, - {NHibernateUtil.UtcDateTime, DateTime.UtcNow}, - {NHibernateUtil.LocalDateTimeNoMs, DateTime.Now}, - {NHibernateUtil.UtcDateTimeNoMs, DateTime.UtcNow}, - {NHibernateUtil.DateTimeOffset, DateTimeOffset.Now}, - {NHibernateUtil.Date, DateTime.Today}, - {NHibernateUtil.Decimal, 2.5m}, - {NHibernateUtil.Double, 2.5d}, - {NHibernateUtil.Currency, 2.5m}, - {NHibernateUtil.Guid, Guid.NewGuid()}, - {NHibernateUtil.Int16, (short) 1}, - {NHibernateUtil.Int32, 3}, - {NHibernateUtil.Int64, 3L}, - {NHibernateUtil.SByte, (sbyte) 1}, - {NHibernateUtil.UInt16, (ushort) 1}, - {NHibernateUtil.UInt32, (uint) 1}, - {NHibernateUtil.UInt64, (ulong) 1}, - {NHibernateUtil.Single, 1.1f}, - {NHibernateUtil.String, "test"}, - {NHibernateUtil.StringClob, "test"}, - {NHibernateUtil.Time, DateTime.Now}, - {NHibernateUtil.Ticks, DateTime.Now}, - {NHibernateUtil.TimeAsTimeSpan, TimeSpan.FromMilliseconds(15)}, - {NHibernateUtil.TimeSpan, TimeSpan.FromMilliseconds(1234)}, - {NHibernateUtil.DbTimestamp, DateTime.Now}, - {NHibernateUtil.TrueFalse, false}, - {NHibernateUtil.YesNo, true}, - {NHibernateUtil.Class, typeof(IType)}, - {NHibernateUtil.MetaType, entityName}, - {NHibernateUtil.Serializable, new MyEntity {Id = 1}}, - {NHibernateUtil.AnsiChar, 'a'}, - {NHibernateUtil.XmlDoc, xmlDoc}, - {NHibernateUtil.XDoc, XDocument.Parse("XDoc")}, - {NHibernateUtil.Uri, new Uri("http://test.com")} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, false, null, sessionImpl, null); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), - "DisassembledState is different from the original CacheEntry"); - } - - [Test] - public void TestNHibernateCacheEntrySerialization() - { - var entityName = nameof(MyEntity); - var propertyValues = new Dictionary - { - {NHibernateUtil.String, "test"} - }; - - var sfImpl = Substitute.For(); - var sessionImpl = Substitute.For(); - var entityPersister = Substitute.For(); - entityPersister.EntityName.Returns(entityName); - entityPersister.IsLazyPropertiesCacheable.Returns(false); - entityPersister.PropertyTypes.Returns(propertyValues.Keys.ToArray()); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, entityName, sfImpl); - var cacheEntry = CacheEntry.Create(propertyValues.Values.ToArray(), entityPersister, true, 4, sessionImpl, null); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheEntry) value; - Assert.That(retrievedCacheEntry.AreLazyPropertiesUnfetched, Is.EqualTo(cacheEntry.AreLazyPropertiesUnfetched), - "AreLazyPropertiesUnfetched is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.DisassembledState, Is.EquivalentTo(cacheEntry.DisassembledState), - "DisassembledState is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.Subclass, Is.EqualTo(cacheEntry.Subclass), - "Subclass is different from the original CacheEntry"); - Assert.That(retrievedCacheEntry.Version, Is.EqualTo(cacheEntry.Version), - "Version is different from the original CacheEntry"); - } - - [Test] - public void TestNHibernateCollectionCacheEntrySerialization() - { - var sfImpl = Substitute.For(); - var collection = Substitute.For(); - collection.Disassemble(null).Returns(o => new object[] {"test"}); - - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "MyCollection", sfImpl); - var cacheEntry = CollectionCacheEntry.Create(collection, null); - Assert.That(cacheEntry.State, Has.Length.EqualTo(1)); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CollectionCacheEntry) value; - Assert.That(retrievedCacheEntry.State, Has.Length.EqualTo(1)); - Assert.That(retrievedCacheEntry.State[0], Is.EquivalentTo("test"), - "State is different from the original CollectionCacheEntry"); - } - - [Test] - public void TestNHibernateCacheLockSerialization() - { - var sfImpl = Substitute.For(); - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CacheLock", sfImpl); - var cacheEntry = new CacheLock - { - Timeout = 1234, Id = 1, Version = 5 - }; - cacheEntry.Lock(123, 2); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CacheLock) value; - Assert.That(retrievedCacheEntry.Id, Is.EqualTo(cacheEntry.Id), - "Id is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), - "IsLock is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.WasLockedConcurrently, Is.EqualTo(cacheEntry.WasLockedConcurrently), - "WasLockedConcurrently is different from the original CacheLock"); - Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), - "ToString() is different from the original CacheLock"); - } - - [Test] - public void TestNHibernateCachedItemSerialization() - { - var sfImpl = Substitute.For(); - var cacheKey = new CacheKey(1, NHibernateUtil.Int32, "CachedItem", sfImpl); - var cacheEntry = new CachedItem - { - Value = "test", FreshTimestamp = 111, Version = 5 - }; - cacheEntry.Lock(123, 2); - - var cache = GetDefaultCache(); - cache.Put(cacheKey, cacheEntry); - var value = cache.Get(cacheKey); - - Assert.That(value, Is.TypeOf()); - var retrievedCacheEntry = (CachedItem) value; - Assert.That(retrievedCacheEntry.FreshTimestamp, Is.EqualTo(cacheEntry.FreshTimestamp), - "FreshTimestamp is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.IsLock, Is.EqualTo(cacheEntry.IsLock), - "IsLock is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.Value, Is.EqualTo(cacheEntry.Value), - "Value is different from the original CachedItem"); - Assert.That(retrievedCacheEntry.ToString(), Is.EqualTo(cacheEntry.ToString()), - "ToString() is different from the original CachedItem"); - } - [Serializable] protected class CustomCacheKey { @@ -452,137 +214,5 @@ public void TestEnvironmentName() developProvider.Stop(); releaseProvider.Stop(); } - - [Test] - public void TestPutMany() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestPut{i}"; - values[i] = $"valuePut{i}"; - } - - var cache = (RedisCache) GetDefaultCache(); - // Due to async version, it may already be there. - foreach (var key in keys) - cache.Remove(key); - - Assert.That(cache.GetMany(keys), Is.EquivalentTo(new object[10]), "cache returned items we didn't add !?!"); - - cache.PutMany(keys, values); - var items = cache.GetMany(keys); - - for (var i = 0; i < items.Length; i++) - { - var item = items[i]; - Assert.That(item, Is.Not.Null, "unable to retrieve cached item"); - Assert.That(item, Is.EqualTo(values[i]), "didn't return the item we added"); - } - } - - [Test] - public void TestRemoveMany() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestRemove{i}"; - values[i] = $"valueRemove{i}"; - } - - var cache = (RedisCache) GetDefaultCache(); - - // add the item - cache.PutMany(keys, values); - - // make sure it's there - var items = cache.GetMany(keys); - Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); - - // remove it - foreach (var key in keys) - cache.Remove(key); - - // make sure it's not there - items = cache.GetMany(keys); - Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); - } - - [Test] - public void TestLockUnlockMany() - { - if (!SupportsLocking) - Assert.Ignore("Test not supported by provider"); - - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestLock{i}"; - values[i] = $"valueLock{i}"; - } - - var cache = (RedisCache)GetDefaultCache(); - - // add the item - cache.PutMany(keys, values); - cache.LockMany(keys); - Assert.Throws(() => cache.LockMany(keys), "all items should be locked"); - - Thread.Sleep(cache.Timeout / Timestamper.OneMs); - - for (var i = 0; i < 2; i++) - { - Assert.DoesNotThrow(() => - { - cache.UnlockMany(keys, cache.LockMany(keys)); - }, "the items should be unlocked"); - } - - // Test partial locks by locking the first 5 keys and afterwards try to lock last 6 keys. - var lockValue = cache.LockMany(keys.Take(5).ToArray()); - - Assert.Throws(() => cache.LockMany(keys.Skip(4).ToArray()), "the fifth key should be locked"); - - Assert.DoesNotThrow(() => - { - cache.UnlockMany(keys, cache.LockMany(keys.Skip(5).ToArray())); - }, "the last 5 keys should not be locked."); - - // Unlock the first 5 keys - cache.UnlockMany(keys, lockValue); - - Assert.DoesNotThrow(() => - { - lockValue = cache.LockMany(keys); - cache.UnlockMany(keys, lockValue); - }, "the first 5 keys should not be locked."); - } - - [Test] - public void TestNullKeyPutMany() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.Throws(() => cache.PutMany(null, null)); - } - - [Test] - public void TestNullValuePutMany() - { - var cache = (RedisCache) GetDefaultCache(); - Assert.Throws(() => cache.PutMany(new object[] { "keyTestNullValuePut" }, null)); - } - - [Test] - public void TestNullKeyGetMany() - { - var cache = (RedisCache) GetDefaultCache(); - cache.Put("keyTestNullKeyGet", "value"); - var items = cache.GetMany(null); - Assert.IsNull(items); - } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs index 4b4cbe7c..df157700 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs @@ -28,7 +28,7 @@ public void TestGetOperation() [Test] public void TestGetManyOperation() { - TestBatchOperation("GetMany", true, (cache, keys, _) => cache.GetMany(keys)); + TestBatchOperation("GetMany", true, (cache, keys, _) => cache.GetMany(keys), BatchSize); } [Test] @@ -44,6 +44,7 @@ public void TestGetManyOperationWithSlidingExpiration() { var props = new Dictionary {{"sliding", "true"}}; TestBatchOperation("GetMany", true, (cache, keys, _) => cache.GetMany(keys), + batchSize: BatchSize, caches: new List {GetDefaultRedisCache(props), GetFastRedisCache(props)}); } @@ -60,6 +61,7 @@ public void TestPutManyOperation() { var props = new Dictionary {{"expiration", "0"}}; TestBatchOperation("PutMany", false, (cache, keys, values) => cache.PutMany(keys, values), + batchSize: null, caches: new List {GetFastRedisCache(props)}); } @@ -72,7 +74,7 @@ public void TestPutOperationWithExpiration() [Test] public void TestPutManyOperationWithExpiration() { - TestBatchOperation("PutMany", false, (cache, keys, values) => cache.PutMany(keys, values)); + TestBatchOperation("PutMany", false, (cache, keys, values) => cache.PutMany(keys, values), null); } [Test] @@ -92,18 +94,18 @@ public void TestLockUnlockManyOperation() { var value = cache.LockMany(keys); cache.UnlockMany(keys, value); - }); + }, null); } private void TestBatchOperation(string operation, bool fillData, - Action keyValueAction, int? batchSize = null, + Action keyValueAction, int? batchSize, int? cacheItems = null, int? repeat = null, List caches = null) { TestOperation(operation, fillData, null, keyValueAction, batchSize, cacheItems, repeat, caches); } private Task TestBatchOperationAsync(string operation, bool fillData, - Func keyValueAction, int? batchSize = null, + Func keyValueAction, int? batchSize, int? cacheItems = null, int? repeat = null, List caches = null) { return TestOperationAsync(operation, fillData, null, keyValueAction, batchSize, cacheItems, repeat, caches); @@ -149,7 +151,7 @@ private void TestOperation(string operation, bool fillData, } else { - repeatPolicy.BatchExecute(batchKeyValueAction, batchSize ?? BatchSize); + repeatPolicy.BatchExecute(batchKeyValueAction, batchSize); } } @@ -185,7 +187,7 @@ private async Task TestOperationAsync(string operation, bool fillData, } else { - await repeatPolicy.BatchExecuteAsync(batchKeyValueAction, batchSize ?? BatchSize); + await repeatPolicy.BatchExecuteAsync(batchKeyValueAction, batchSize); } } @@ -272,7 +274,7 @@ public CacheOperationRepeatPolicy(string operation, RedisCache cache, int repeat _cacheData = cacheData; } - public void BatchExecute(Action keyValueAction, int batchSize) + public void BatchExecute(Action keyValueAction, int? batchSize) { var batchKeys = new List(); var batchValues = new List(); @@ -294,7 +296,7 @@ void Iterate() { foreach (var pair in _cacheData) { - if (batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) + if (batchSize.HasValue && batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) { keyValueAction(_cache, batchKeys.ToArray(), batchValues.ToArray()); batchKeys.Clear(); @@ -314,7 +316,7 @@ void Iterate() } } - public async Task BatchExecuteAsync(Func keyValueFunc, int batchSize) + public async Task BatchExecuteAsync(Func keyValueFunc, int? batchSize) { var batchKeys = new List(); var batchValues = new List(); @@ -336,7 +338,7 @@ async Task Iterate() { foreach (var pair in _cacheData) { - if (batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) + if (batchSize.HasValue && batchKeys.Count > 0 && batchKeys.Count % batchSize == 0) { await keyValueFunc(_cache, batchKeys.ToArray(), batchValues.ToArray()); batchKeys.Clear(); @@ -402,7 +404,7 @@ public async Task ExecuteAsync(Func, Task> ke LogResult(result, 1); } - private void LogResult(long[] result, int batchSize) + private void LogResult(long[] result, int? batchSize) { Log.Info( $"{_operation} operation for {_cacheData.Count} keys with region strategy {_cache.RegionStrategy.GetType().Name}:{Environment.NewLine}" + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs index c56d5eb8..db64bb48 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Reflection; -using NHibernate.Bytecode; using NHibernate.Cache; +using NHibernate.Caches.Common; using NHibernate.Caches.Common.Tests; using NSubstitute; using NUnit.Framework; @@ -57,7 +56,7 @@ public void TestDefaultCacheConfiguration() var retryDelayProvider = Substitute.For(); var lockValueProvider = Substitute.For(); var regionStrategyFactory = Substitute.For(); - var serializer = Substitute.For(); + var serializer = Substitute.For(); var defaultConfig = RedisCacheProvider.DefaultCacheConfiguration; defaultConfig.ConnectionMultiplexerProvider = connectionProvider; @@ -83,38 +82,40 @@ public void TestDefaultCacheConfiguration() [Test] public void TestUserProvidedObjectsFactory() { - // TODO: update when upgraded to NH 5.2 - var field = typeof(AbstractBytecodeProvider).GetField("objectsFactory", - BindingFlags.Instance | BindingFlags.NonPublic); - - var customObjectsFactory = new CustomObjectsFactory(); - var connectionProvider = Substitute.For(); - var databaseProvider = Substitute.For(); - var retryDelayProvider = Substitute.For(); - var lockValueProvider = Substitute.For(); - var regionStrategyFactory = Substitute.For(); - var serializer = Substitute.For(); - - customObjectsFactory.RegisterSingleton(connectionProvider); - customObjectsFactory.RegisterSingleton(databaseProvider); - customObjectsFactory.RegisterSingleton(retryDelayProvider); - customObjectsFactory.RegisterSingleton(lockValueProvider); - customObjectsFactory.RegisterSingleton(regionStrategyFactory); - customObjectsFactory.RegisterSingleton(serializer); - - field.SetValue(Cfg.Environment.BytecodeProvider, customObjectsFactory); - - var provider = (RedisCacheProvider)GetNewProvider(); - var config = provider.CacheConfiguration; - - Assert.That(config.ConnectionMultiplexerProvider, Is.EqualTo(connectionProvider)); - Assert.That(config.DatabaseProvider, Is.EqualTo(databaseProvider)); - Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); - Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); - Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); - Assert.That(config.Serializer, Is.EqualTo(serializer)); - - field.SetValue(Cfg.Environment.BytecodeProvider, new ActivatorObjectsFactory()); + var originalObjectsFactory = Cfg.Environment.ObjectsFactory; + try + { + var customObjectsFactory = new CustomObjectsFactory(); + Cfg.Environment.ObjectsFactory = customObjectsFactory; + + var connectionProvider = Substitute.For(); + var databaseProvider = Substitute.For(); + var retryDelayProvider = Substitute.For(); + var lockValueProvider = Substitute.For(); + var regionStrategyFactory = Substitute.For(); + var serializer = Substitute.For(); + + customObjectsFactory.RegisterSingleton(connectionProvider); + customObjectsFactory.RegisterSingleton(databaseProvider); + customObjectsFactory.RegisterSingleton(retryDelayProvider); + customObjectsFactory.RegisterSingleton(lockValueProvider); + customObjectsFactory.RegisterSingleton(regionStrategyFactory); + customObjectsFactory.RegisterSingleton(serializer); + + var provider = (RedisCacheProvider) GetNewProvider(); + var config = provider.CacheConfiguration; + + Assert.That(config.ConnectionMultiplexerProvider, Is.EqualTo(connectionProvider)); + Assert.That(config.DatabaseProvider, Is.EqualTo(databaseProvider)); + Assert.That(config.LockConfiguration.RetryDelayProvider, Is.EqualTo(retryDelayProvider)); + Assert.That(config.LockConfiguration.ValueProvider, Is.EqualTo(lockValueProvider)); + Assert.That(config.RegionStrategyFactory, Is.EqualTo(regionStrategyFactory)); + Assert.That(config.Serializer, Is.EqualTo(serializer)); + } + finally + { + Cfg.Environment.ObjectsFactory = originalObjectsFactory; + } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs index edb57ab4..d627a118 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cache; +using NHibernate.Caches.Common; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis @@ -27,9 +28,9 @@ public abstract partial class AbstractRegionStrategy protected readonly IDatabase Database; /// - /// The instance. + /// The instance. /// - protected readonly IRedisSerializer Serializer; + protected readonly CacheSerializerBase Serializer; private readonly RedisKeyLocker _keyLocker; @@ -186,7 +187,7 @@ public virtual object[] GetMany(object[] keys) { if (keys == null) { - return null; + throw new ArgumentNullException(nameof(keys)); } var cacheKeys = new RedisKey[keys.Length]; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs index 024fb7af..3318ae87 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using NHibernate.Cache; +using NHibernate.Caches.Common; using StackExchange.Redis; namespace NHibernate.Caches.StackExRedis @@ -65,51 +66,58 @@ public virtual async Task GetAsync(object key, CancellationToken cancell /// The keys of the objects to retrieve. /// A cancellation token that can be used to cancel the work /// An array of objects behind the keys or if the key was not found. - public virtual async Task GetManyAsync(object[] keys, CancellationToken cancellationToken) + public virtual Task GetManyAsync(object[] keys, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); if (keys == null) { - return null; - } - - var cacheKeys = new RedisKey[keys.Length]; - Log.Debug("Fetching {0} objects...", keys.Length); - for (var i = 0; i < keys.Length; i++) - { - cacheKeys[i] = GetCacheKey(keys[i]); - Log.Debug("Fetching object with key: '{0}'.", cacheKeys[i]); + throw new ArgumentNullException(nameof(keys)); } - - RedisValue[] results; - if (string.IsNullOrEmpty(GetManyScript)) + if (cancellationToken.IsCancellationRequested) { - cancellationToken.ThrowIfCancellationRequested(); - results = await (Database.StringGetAsync(cacheKeys)).ConfigureAwait(false); + return Task.FromCanceled(cancellationToken); } - else + return InternalGetManyAsync(); + async Task InternalGetManyAsync() { - cacheKeys = AppendAdditionalKeys(cacheKeys); - var values = AppendAdditionalValues(new RedisValue[] + + var cacheKeys = new RedisKey[keys.Length]; + Log.Debug("Fetching {0} objects...", keys.Length); + for (var i = 0; i < keys.Length; i++) + { + cacheKeys[i] = GetCacheKey(keys[i]); + Log.Debug("Fetching object with key: '{0}'.", cacheKeys[i]); + } + + RedisValue[] results; + if (string.IsNullOrEmpty(GetManyScript)) + { + cancellationToken.ThrowIfCancellationRequested(); + results = await (Database.StringGetAsync(cacheKeys)).ConfigureAwait(false); + } + else + { + cacheKeys = AppendAdditionalKeys(cacheKeys); + var values = AppendAdditionalValues(new RedisValue[] { UseSlidingExpiration && ExpirationEnabled, (long) Expiration.TotalMilliseconds }); - cancellationToken.ThrowIfCancellationRequested(); - results = (RedisValue[]) await (Database.ScriptEvaluateAsync(GetManyScript, cacheKeys, values)).ConfigureAwait(false); - } + cancellationToken.ThrowIfCancellationRequested(); + results = (RedisValue[]) await (Database.ScriptEvaluateAsync(GetManyScript, cacheKeys, values)).ConfigureAwait(false); + } - var objects = new object[keys.Length]; - for (var i = 0; i < results.Length; i++) - { - var result = results[i]; - if (!result.IsNullOrEmpty) + var objects = new object[keys.Length]; + for (var i = 0; i < results.Length; i++) { - objects[i] = Serializer.Deserialize(result); + var result = results[i]; + if (!result.IsNullOrEmpty) + { + objects[i] = Serializer.Deserialize(result); + } } - } - return objects; + return objects; + } } /// diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs deleted file mode 100644 index df0d09ca..00000000 --- a/StackExRedis/NHibernate.Caches.StackExRedis/BinaryRedisSerializer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - -namespace NHibernate.Caches.StackExRedis -{ - /// - /// A Redis serializer that uses to serialize and deserialize objects. - /// - public class BinaryRedisSerializer : IRedisSerializer - { - /// - public byte[] Serialize(object value) - { - var serializer = new BinaryFormatter(); - using (var stream = new MemoryStream()) - { - serializer.Serialize(stream, value); - return stream.ToArray(); - } - } - - /// - public object Deserialize(byte[] value) - { - var serializer = new BinaryFormatter(); - using (var stream = new MemoryStream(value)) - { - return serializer.Deserialize(stream); - } - } - } -} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs index 5870dd18..85e4d325 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NHibernate.Bytecode; using NHibernate.Cache; +using NHibernate.Util; namespace NHibernate.Caches.StackExRedis { @@ -68,28 +69,23 @@ public static TimeSpan GetTimeSpanFromMilliseconds(string key, IDictionary properties, System.Type defaultValue) { var typeName = GetString(key, properties, null); - if (typeName == null) - { - return defaultValue; - } - - try - { - return System.Type.GetType(typeName, true); - } - catch (Exception e) - { - throw new CacheException($"Unable to acquire type '{typeName}' from the configuration property '{key}'", e); - } + return typeName == null ? defaultValue : ReflectHelper.ClassForName(typeName); } - public static TType GetInstanceOfType(string key, IDictionary properties, TType defaultValue, + public static TType GetInstance(string key, IDictionary properties, TType defaultValue, INHibernateLogger logger) { var objectsFactory = Cfg.Environment.ObjectsFactory; - var type = GetSystemType(key, properties, null); - if (type == null) + var className = GetString(key, properties, null); + System.Type type = null; + try { + if (className != null) + { + type = ReflectHelper.ClassForName(className); + return (TType) objectsFactory.CreateInstance(type); + } + // Try to get the instance from the base type if the user provided a custom IObjectsFactory if (!(objectsFactory is ActivatorObjectsFactory)) { @@ -105,16 +101,14 @@ public static TType GetInstanceOfType(string key, IDictionary - public AbstractRegionStrategy Create(IConnectionMultiplexer connectionMultiplexer, + public virtual AbstractRegionStrategy Create(IConnectionMultiplexer connectionMultiplexer, RedisCacheRegionConfiguration configuration, IDictionary properties) { - return (AbstractRegionStrategy) Activator.CreateInstance(configuration.RegionStrategy, connectionMultiplexer, - configuration, properties); + if (configuration.RegionStrategy == typeof(DefaultRegionStrategy)) + { + return new DefaultRegionStrategy(connectionMultiplexer, configuration, properties); + } + if (configuration.RegionStrategy == typeof(FastRegionStrategy)) + { + return new FastRegionStrategy(connectionMultiplexer, configuration, properties); + } + + throw new CacheException( + $"{configuration.RegionStrategy} is not supported by {GetType()}, register " + + $"a custom {typeof(ICacheRegionStrategyFactory)} or use a supported one."); } } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs b/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs deleted file mode 100644 index 91c0d3c8..00000000 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IRedisSerializer.cs +++ /dev/null @@ -1,25 +0,0 @@ -using StackExchange.Redis; - -namespace NHibernate.Caches.StackExRedis -{ - /// - /// Defines methods for serializing and deserializing objects that will be stored/retrieved for Redis. - /// - // TODO: Remove and use CacheSerializeBase - public interface IRedisSerializer - { - /// - /// Serialize the object to a to be stored into Redis. - /// - /// The object to serialize. - /// A serialized that can be stored into Redis. - byte[] Serialize(object value); - - /// - /// Deserialize the that was retrieved from Redis. - /// - /// The value to deserialize. - /// The object that was serialized. - object Deserialize(byte[] value); - } -} diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj index 43f40daf..fff45f0a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj @@ -32,4 +32,7 @@ ./NHibernate.Caches.license.txt + + + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs index 006ee1ce..0abaac3e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs @@ -1,4 +1,5 @@ using System; +using NHibernate.Caches.Common; namespace NHibernate.Caches.StackExRedis { @@ -7,22 +8,22 @@ namespace NHibernate.Caches.StackExRedis /// public class RedisCacheConfiguration { - private static readonly IRedisSerializer DefaultSerializer = new BinaryRedisSerializer(); + private static readonly CacheSerializerBase DefaultSerializer = new BinaryCacheSerializer(); private static readonly ICacheRegionStrategyFactory DefaultRegionStrategyFactory = new DefaultCacheRegionStrategyFactory(); private static readonly IConnectionMultiplexerProvider DefaultConnectionMultiplexerProvider = new DefaultConnectionMultiplexerProvider(); private static readonly IDatabaseProvider DefaultDatabaseProvider = new DefaultDatabaseProvider(); private static readonly System.Type DefaultRegionStrategyType = typeof(DefaultRegionStrategy); - private IRedisSerializer _serializer; + private CacheSerializerBase _serializer; private ICacheRegionStrategyFactory _regionStrategyFactory; private IConnectionMultiplexerProvider _connectionMultiplexerProvider; private IDatabaseProvider _databaseProvider; private System.Type _defaultRegionStrategy; /// - /// The instance. + /// The instance. /// - public IRedisSerializer Serializer + public CacheSerializerBase Serializer { get => _serializer ?? DefaultSerializer; set => _serializer = value; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs index 042a428c..0890bfd6 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs @@ -220,16 +220,16 @@ private void BuildDefaultConfiguration(IDictionary properties) config.RegionPrefix = GetString(Cfg.Environment.CacheRegionPrefix, properties, config.RegionPrefix); Log.Debug("Region prefix: {0}", config.RegionPrefix); - config.Serializer = GetInstanceOfType(RedisEnvironment.Serializer, properties, config.Serializer, Log); + config.Serializer = GetInstance(RedisEnvironment.Serializer, properties, config.Serializer, Log); Log.Debug("Serializer: {0}", config.Serializer); - config.RegionStrategyFactory = GetInstanceOfType(RedisEnvironment.RegionStrategyFactory, properties, config.RegionStrategyFactory, Log); + config.RegionStrategyFactory = GetInstance(RedisEnvironment.RegionStrategyFactory, properties, config.RegionStrategyFactory, Log); Log.Debug("Region strategy factory: {0}", config.RegionStrategyFactory); - config.ConnectionMultiplexerProvider = GetInstanceOfType(RedisEnvironment.ConnectionMultiplexerProvider, properties, config.ConnectionMultiplexerProvider, Log); + config.ConnectionMultiplexerProvider = GetInstance(RedisEnvironment.ConnectionMultiplexerProvider, properties, config.ConnectionMultiplexerProvider, Log); Log.Debug("Connection multiplexer provider: {0}", config.ConnectionMultiplexerProvider); - config.DatabaseProvider = GetInstanceOfType(RedisEnvironment.DatabaseProvider, properties, config.DatabaseProvider, Log); + config.DatabaseProvider = GetInstance(RedisEnvironment.DatabaseProvider, properties, config.DatabaseProvider, Log); Log.Debug("Database provider: {0}", config.DatabaseProvider); config.DefaultExpiration = GetTimeSpanFromSeconds(Cfg.Environment.CacheDefaultExpiration, properties, config.DefaultExpiration); @@ -265,10 +265,10 @@ private void BuildDefaultConfiguration(IDictionary properties) lockConfig.MinRetryDelay = GetTimeSpanFromMilliseconds(RedisEnvironment.LockMinRetryDelay, properties, lockConfig.MinRetryDelay); Log.Debug("Lock min retry delay: {0} milliseconds", lockConfig.MinRetryDelay.TotalMilliseconds); - lockConfig.ValueProvider = GetInstanceOfType(RedisEnvironment.LockValueProvider, properties, lockConfig.ValueProvider, Log); + lockConfig.ValueProvider = GetInstance(RedisEnvironment.LockValueProvider, properties, lockConfig.ValueProvider, Log); Log.Debug("Lock value provider: {0}", lockConfig.ValueProvider); - lockConfig.RetryDelayProvider = GetInstanceOfType(RedisEnvironment.LockRetryDelayProvider, properties, lockConfig.RetryDelayProvider, Log); + lockConfig.RetryDelayProvider = GetInstance(RedisEnvironment.LockRetryDelayProvider, properties, lockConfig.RetryDelayProvider, Log); Log.Debug("Lock retry delay provider: {0}", lockConfig.RetryDelayProvider); lockConfig.KeySuffix = GetString(RedisEnvironment.LockKeySuffix, properties, lockConfig.KeySuffix); diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs index b5586574..c7d68faa 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using NHibernate.Caches.Common; namespace NHibernate.Caches.StackExRedis { @@ -71,9 +72,9 @@ public RedisCacheRegionConfiguration(string regionName) public bool AppendHashcode { get; internal set; } /// - /// The to be used. + /// The to be used. /// - public IRedisSerializer Serializer { get; internal set; } + public CacheSerializerBase Serializer { get; internal set; } /// /// The instance. From 644dedd538e074dae07ae6be254c1b8a2b89ba8e Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 8 Dec 2018 15:38:00 +0100 Subject: [PATCH 21/24] Updated StackExchange.Redis package to version 2 --- .../DefaultConnectionMultiplexerProvider.cs | 1 - .../NHibernate.Caches.StackExRedis.csproj | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs index b070865d..8b3e388b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs +++ b/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs @@ -13,7 +13,6 @@ public IConnectionMultiplexer Get(string configuration) { TextWriter textWriter = Log.IsDebugEnabled() ? new NHibernateTextWriter(Log) : null; var connectionMultiplexer = ConnectionMultiplexer.Connect(configuration, textWriter); - connectionMultiplexer.PreserveAsyncOrder = false; // Recommended setting return connectionMultiplexer; } } diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj index fff45f0a..0f28e2cf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj +++ b/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj @@ -1,4 +1,4 @@ - + NHibernate.Caches.StackExRedis @@ -22,7 +22,7 @@ - + From 4db42a52353349bf9ff81bd23c91e201809b8b9a Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 8 Dec 2018 15:56:24 +0100 Subject: [PATCH 22/24] Renamed project to NHibernate.Caches.StackExchangeRedis --- NHibernate.Caches.Everything.sln | 4 ++-- .../NHibernate.Caches.StackExchangeRedis.Tests}/App.config | 4 ++-- .../Async/Caches/DistributedRedisCache.cs | 2 +- .../Async/RedisCacheDefaultStrategyFixture.cs | 2 +- .../Async/RedisCacheFixture.cs | 2 +- .../Async/RedisCachePerformanceFixture.cs | 2 +- .../Caches/DistributedRedisCache.cs | 2 +- .../CustomObjectsFactory.cs | 2 +- .../DistributedRedisCacheFixture.cs | 4 ++-- .../NHibernate.Caches.StackExchangeRedis.Tests.csproj | 4 ++-- .../NHibernate.Caches.StackExchangeRedis.Tests}/Program.cs | 2 +- .../Properties/AssemblyInfo.cs | 0 .../Providers/DistributedRedisCacheProvider.cs | 4 ++-- .../RedisCacheDefaultStrategyFixture.cs | 2 +- .../RedisCacheFastStrategyFixture.cs | 2 +- .../RedisCacheFixture.cs | 2 +- .../RedisCachePerformanceFixture.cs | 2 +- .../RedisCacheProviderFixture.cs | 2 +- .../TestsContext.cs | 2 +- .../AbstractRegionStrategy.cs | 2 +- .../Async/AbstractRegionStrategy.cs | 2 +- .../Async/DefaultRegionStrategy.cs | 4 ++-- .../Async/FastRegionStrategy.cs | 2 +- .../Async/NHibernateTextWriter.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/Async/RedisCache.cs | 2 +- .../Async/RedisKeyLocker.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/CacheConfig.cs | 2 +- .../ConfigurationHelper.cs | 2 +- .../DefaultCacheLockRetryDelayProvider.cs | 2 +- .../DefaultCacheLockValueProvider.cs | 2 +- .../DefaultCacheRegionStrategyFactory.cs | 2 +- .../DefaultConnectionMultiplexerProvider.cs | 2 +- .../DefaultDatabaseProvider.cs | 2 +- .../DefaultRegionStrategy.cs | 4 ++-- .../FastRegionStrategy.cs | 2 +- .../ICacheLockRetryDelayProvider.cs | 2 +- .../ICacheLockValueProvider.cs | 2 +- .../ICacheRegionStrategyFactory.cs | 2 +- .../IConnectionMultiplexerProvider.cs | 2 +- .../IDatabaseProvider.cs | 2 +- .../Lua/DefaultRegionStrategy/CheckVersion.lua | 0 .../Lua/DefaultRegionStrategy/Get.lua | 0 .../Lua/DefaultRegionStrategy/GetMany.lua | 0 .../Lua/DefaultRegionStrategy/InitializeVersion.lua | 0 .../Lua/DefaultRegionStrategy/Lock.lua | 0 .../Lua/DefaultRegionStrategy/LockMany.lua | 0 .../Lua/DefaultRegionStrategy/Put.lua | 0 .../Lua/DefaultRegionStrategy/PutMany.lua | 0 .../Lua/DefaultRegionStrategy/Remove.lua | 0 .../Lua/DefaultRegionStrategy/RemoveMany.lua | 0 .../Lua/DefaultRegionStrategy/Unlock.lua | 0 .../Lua/DefaultRegionStrategy/UnlockMany.lua | 0 .../Lua/DefaultRegionStrategy/UpdateVersion.lua | 0 .../Lua/FastRegionStrategy/ExpirationPutMany.lua | 0 .../Lua/FastRegionStrategy/LockMany.lua | 0 .../Lua/FastRegionStrategy/SlidingGet.lua | 0 .../Lua/FastRegionStrategy/SlidingGetMany.lua | 0 .../Lua/FastRegionStrategy/Unlock.lua | 0 .../Lua/FastRegionStrategy/UnlockMany.lua | 0 .../LuaScriptProvider.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis.csproj | 4 ++-- .../NHibernateTextWriter.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/RedisCache.cs | 2 +- .../RedisCacheConfiguration.cs | 2 +- .../RedisCacheLockConfiguration.cs | 2 +- .../RedisCacheProvider.cs | 4 ++-- .../RedisCacheRegionConfiguration.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/RedisEnvironment.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/RedisKeyLocker.cs | 2 +- .../RedisSectionHandler.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/RegionConfig.cs | 2 +- .../NHibernate.Caches.StackExchangeRedis}/RetryPolicy.cs | 2 +- 72 files changed, 61 insertions(+), 61 deletions(-) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/App.config (89%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Async/Caches/DistributedRedisCache.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Async/RedisCacheDefaultStrategyFixture.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Async/RedisCacheFixture.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Async/RedisCachePerformanceFixture.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Caches/DistributedRedisCache.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/CustomObjectsFactory.cs (95%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/DistributedRedisCacheFixture.cs (88%) rename StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/NHibernate.Caches.StackExchangeRedis.Tests.csproj (86%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Program.cs (76%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Properties/AssemblyInfo.cs (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/Providers/DistributedRedisCacheProvider.cs (93%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/RedisCacheDefaultStrategyFixture.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/RedisCacheFastStrategyFixture.cs (93%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/RedisCacheFixture.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/RedisCachePerformanceFixture.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/RedisCacheProviderFixture.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis.Tests => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests}/TestsContext.cs (94%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/AbstractRegionStrategy.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/AbstractRegionStrategy.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/DefaultRegionStrategy.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/FastRegionStrategy.cs (94%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/NHibernateTextWriter.cs (95%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/RedisCache.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Async/RedisKeyLocker.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/CacheConfig.cs (93%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/ConfigurationHelper.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultCacheLockRetryDelayProvider.cs (90%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultCacheLockValueProvider.cs (81%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultCacheRegionStrategyFactory.cs (95%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultConnectionMultiplexerProvider.cs (92%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultDatabaseProvider.cs (85%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/DefaultRegionStrategy.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/FastRegionStrategy.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/ICacheLockRetryDelayProvider.cs (91%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/ICacheLockValueProvider.cs (88%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/ICacheRegionStrategyFactory.cs (94%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/IConnectionMultiplexerProvider.cs (92%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/IDatabaseProvider.cs (92%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/CheckVersion.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/Get.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/GetMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/InitializeVersion.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/Lock.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/LockMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/Put.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/PutMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/Remove.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/RemoveMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/Unlock.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/UnlockMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/DefaultRegionStrategy/UpdateVersion.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/ExpirationPutMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/LockMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/SlidingGet.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/SlidingGetMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/Unlock.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/Lua/FastRegionStrategy/UnlockMany.lua (100%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/LuaScriptProvider.cs (98%) rename StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.csproj (93%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/NHibernateTextWriter.cs (92%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisCache.cs (97%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisCacheConfiguration.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisCacheLockConfiguration.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisCacheProvider.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisCacheRegionConfiguration.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisEnvironment.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisKeyLocker.cs (99%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RedisSectionHandler.cs (98%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RegionConfig.cs (97%) rename {StackExRedis/NHibernate.Caches.StackExRedis => StackExchangeRedis/NHibernate.Caches.StackExchangeRedis}/RetryPolicy.cs (98%) diff --git a/NHibernate.Caches.Everything.sln b/NHibernate.Caches.Everything.sln index 2acec460..c419c1b6 100644 --- a/NHibernate.Caches.Everything.sln +++ b/NHibernate.Caches.Everything.sln @@ -74,9 +74,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.Json EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.Util.JsonSerializer.Tests", "Util\NHibernate.Caches.Util.JsonSerializer.Tests\NHibernate.Caches.Util.JsonSerializer.Tests.csproj", "{26EAA903-63F7-41B1-9197-5944CEBF00C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExRedis", "StackExRedis\NHibernate.Caches.StackExRedis\NHibernate.Caches.StackExRedis.csproj", "{726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExchangeRedis", "StackExchangeRedis\NHibernate.Caches.StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.csproj", "{726AA9C7-0EA4-47C0-B65D-D42CD3ECEF82}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExRedis.Tests", "StackExRedis\NHibernate.Caches.StackExRedis.Tests\NHibernate.Caches.StackExRedis.Tests.csproj", "{CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.StackExchangeRedis.Tests", "StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.Tests\NHibernate.Caches.StackExchangeRedis.Tests.csproj", "{CC2A7851-B1CD-49F7-ACD4-08604C2ACC63}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/App.config similarity index 89% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/App.config index db99c96a..6d036d1b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/App.config +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/App.config @@ -1,7 +1,7 @@ -
+
@@ -20,7 +20,7 @@ Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI ReadCommitted - NHibernate.Caches.StackExRedis.RedisCacheProvider, NHibernate.Caches.StackExRedis + NHibernate.Caches.StackExchangeRedis.RedisCacheProvider, NHibernate.Caches.StackExchangeRedis diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/Caches/DistributedRedisCache.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/Caches/DistributedRedisCache.cs index 984827fa..e487dfe1 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/Caches/DistributedRedisCache.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/Caches/DistributedRedisCache.cs @@ -13,7 +13,7 @@ using System.Linq; using NHibernate.Cache; -namespace NHibernate.Caches.StackExRedis.Tests.Caches +namespace NHibernate.Caches.StackExchangeRedis.Tests.Caches { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs index bb7017ef..3a094cca 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheDefaultStrategyFixture.cs @@ -13,7 +13,7 @@ using NHibernate.Cache; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { using System.Threading.Tasks; public partial class RedisCacheDefaultStrategyFixture : RedisCacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheFixture.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheFixture.cs index 43a26502..89dcb6a3 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCacheFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCacheFixture.cs @@ -26,7 +26,7 @@ using NSubstitute; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { using System.Threading.Tasks; public abstract partial class RedisCacheFixture : CacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCachePerformanceFixture.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCachePerformanceFixture.cs index 7b709643..ac438770 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Async/RedisCachePerformanceFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Async/RedisCachePerformanceFixture.cs @@ -17,7 +17,7 @@ using NHibernate.Caches.Common.Tests; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { using System.Threading; public partial class RedisCachePerformanceFixture : Fixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Caches/DistributedRedisCache.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Caches/DistributedRedisCache.cs index fce1bb72..e5226450 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Caches/DistributedRedisCache.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Caches/DistributedRedisCache.cs @@ -3,7 +3,7 @@ using System.Linq; using NHibernate.Cache; -namespace NHibernate.Caches.StackExRedis.Tests.Caches +namespace NHibernate.Caches.StackExchangeRedis.Tests.Caches { /// /// Operates with multiple independent Redis instances. This cache should not be used in a real environment diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/CustomObjectsFactory.cs similarity index 95% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/CustomObjectsFactory.cs index aea90b29..8b092834 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/CustomObjectsFactory.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/CustomObjectsFactory.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using NHibernate.Bytecode; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { public class CustomObjectsFactory : IObjectsFactory { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/DistributedRedisCacheFixture.cs similarity index 88% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/DistributedRedisCacheFixture.cs index ae541055..8250954a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/DistributedRedisCacheFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/DistributedRedisCacheFixture.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; -using NHibernate.Caches.StackExRedis.Tests.Providers; +using NHibernate.Caches.StackExchangeRedis.Tests.Providers; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture] public class DistributedRedisCacheFixture : CacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/NHibernate.Caches.StackExchangeRedis.Tests.csproj similarity index 86% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/NHibernate.Caches.StackExchangeRedis.Tests.csproj index 5e89828e..1ce12581 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/NHibernate.Caches.StackExRedis.Tests.csproj +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/NHibernate.Caches.StackExchangeRedis.Tests.csproj @@ -1,7 +1,7 @@  - NHibernate.Caches.StackExRedis.Tests + NHibernate.Caches.StackExchangeRedis.Tests Unit tests of cache provider NHibernate using StackExchange.Redis. net461;netcoreapp2.0 true @@ -15,7 +15,7 @@ - + diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Program.cs similarity index 76% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Program.cs index b262abad..be90c629 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Program.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Program.cs @@ -1,5 +1,5 @@ #if !NETFX -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { public class Program { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Properties/AssemblyInfo.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Properties/AssemblyInfo.cs diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Providers/DistributedRedisCacheProvider.cs similarity index 93% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Providers/DistributedRedisCacheProvider.cs index 80cddf81..a8a985b7 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/Providers/DistributedRedisCacheProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/Providers/DistributedRedisCacheProvider.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using NHibernate.Cache; -using NHibernate.Caches.StackExRedis.Tests.Caches; +using NHibernate.Caches.StackExchangeRedis.Tests.Caches; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis.Tests.Providers +namespace NHibernate.Caches.StackExchangeRedis.Tests.Providers { /// /// Provider for building a cache capable of operating with multiple independent Redis instances. This provider diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheDefaultStrategyFixture.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheDefaultStrategyFixture.cs index a6c9380e..d5595941 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheDefaultStrategyFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheDefaultStrategyFixture.cs @@ -3,7 +3,7 @@ using NHibernate.Cache; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture] public partial class RedisCacheDefaultStrategyFixture : RedisCacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFastStrategyFixture.cs similarity index 93% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFastStrategyFixture.cs index 0b7884fc..3d3869e4 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFastStrategyFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFastStrategyFixture.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture] public class RedisCacheFastStrategyFixture : RedisCacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFixture.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFixture.cs index 8d91d12a..791dfe0d 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheFixture.cs @@ -16,7 +16,7 @@ using NSubstitute; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture] public abstract partial class RedisCacheFixture : CacheFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCachePerformanceFixture.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCachePerformanceFixture.cs index df157700..ead798d2 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCachePerformanceFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCachePerformanceFixture.cs @@ -7,7 +7,7 @@ using NHibernate.Caches.Common.Tests; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture, Explicit] public partial class RedisCachePerformanceFixture : Fixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheProviderFixture.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheProviderFixture.cs index db64bb48..440d57a5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/RedisCacheProviderFixture.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/RedisCacheProviderFixture.cs @@ -6,7 +6,7 @@ using NSubstitute; using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [TestFixture] public class RedisCacheProviderFixture : CacheProviderFixture diff --git a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/TestsContext.cs similarity index 94% rename from StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/TestsContext.cs index 77e95173..3d00f4ab 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis.Tests/TestsContext.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.Tests/TestsContext.cs @@ -4,7 +4,7 @@ #endif using NUnit.Framework; -namespace NHibernate.Caches.StackExRedis.Tests +namespace NHibernate.Caches.StackExchangeRedis.Tests { [SetUpFixture] public class TestsContext diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs index d627a118..f4e6fd58 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/AbstractRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs @@ -5,7 +5,7 @@ using NHibernate.Caches.Common; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// An abstract region strategy that provides common functionalities to create a region strategy. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs index 3318ae87..779c778b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/AbstractRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs @@ -15,7 +15,7 @@ using NHibernate.Caches.Common; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs index ebf973f8..5bfe0e0c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/DefaultRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs @@ -12,9 +12,9 @@ using System.Linq; using NHibernate.Cache; using StackExchange.Redis; -using static NHibernate.Caches.StackExRedis.ConfigurationHelper; +using static NHibernate.Caches.StackExchangeRedis.ConfigurationHelper; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/FastRegionStrategy.cs similarity index 94% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/FastRegionStrategy.cs index 36bab894..0d09b5f9 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/FastRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/FastRegionStrategy.cs @@ -12,7 +12,7 @@ using System.Collections.Generic; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/NHibernateTextWriter.cs similarity index 95% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/NHibernateTextWriter.cs index 7190f8cf..3b932cd5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/NHibernateTextWriter.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/NHibernateTextWriter.cs @@ -11,7 +11,7 @@ using System.IO; using System.Text; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; internal partial class NHibernateTextWriter : TextWriter diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisCache.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisCache.cs index b117352e..50440044 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisCache.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisCache.cs @@ -10,7 +10,7 @@ using NHibernate.Cache; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisKeyLocker.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisKeyLocker.cs index 1c4e0b68..232108bf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/Async/RedisKeyLocker.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/RedisKeyLocker.cs @@ -13,7 +13,7 @@ using NHibernate.Cache; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { using System.Threading.Tasks; using System.Threading; diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/CacheConfig.cs similarity index 93% rename from StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/CacheConfig.cs index fe4f0df2..e67d01e6 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/CacheConfig.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/CacheConfig.cs @@ -1,4 +1,4 @@ -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Cache configuration properties. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs index 85e4d325..0515c2f7 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ConfigurationHelper.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs @@ -4,7 +4,7 @@ using NHibernate.Cache; using NHibernate.Util; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Various methods to easier retrieve the configuration values. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockRetryDelayProvider.cs similarity index 90% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockRetryDelayProvider.cs index fa09517b..ab6884cc 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockRetryDelayProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockRetryDelayProvider.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// public class DefaultCacheLockRetryDelayProvider : ICacheLockRetryDelayProvider diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockValueProvider.cs similarity index 81% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockValueProvider.cs index 2733ff75..d073ed88 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheLockValueProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheLockValueProvider.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// public class DefaultCacheLockValueProvider : ICacheLockValueProvider diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs similarity index 95% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs index 816d3997..ccaab2c5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultCacheRegionStrategyFactory.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs @@ -3,7 +3,7 @@ using NHibernate.Cache; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// public class DefaultCacheRegionStrategyFactory : ICacheRegionStrategyFactory diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultConnectionMultiplexerProvider.cs similarity index 92% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultConnectionMultiplexerProvider.cs index 8b3e388b..53c7ba3c 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultConnectionMultiplexerProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultConnectionMultiplexerProvider.cs @@ -1,7 +1,7 @@ using System.IO; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// public class DefaultConnectionMultiplexerProvider : IConnectionMultiplexerProvider diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultDatabaseProvider.cs similarity index 85% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultDatabaseProvider.cs index 96d30c49..d57617b0 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultDatabaseProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultDatabaseProvider.cs @@ -1,6 +1,6 @@ using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// public class DefaultDatabaseProvider : IDatabaseProvider diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs index 90d9bc74..bb3a63de 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/DefaultRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs @@ -2,9 +2,9 @@ using System.Linq; using NHibernate.Cache; using StackExchange.Redis; -using static NHibernate.Caches.StackExRedis.ConfigurationHelper; +using static NHibernate.Caches.StackExchangeRedis.ConfigurationHelper; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// The default region strategy. This strategy uses a special key that contains the region current version number which is appended diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/FastRegionStrategy.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/FastRegionStrategy.cs index 384b8a7d..072fbfbf 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/FastRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/FastRegionStrategy.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// A region strategy that have very simple read/write operations but does not support diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockRetryDelayProvider.cs similarity index 91% rename from StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockRetryDelayProvider.cs index c4219f0f..37b81ebe 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockRetryDelayProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockRetryDelayProvider.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Defines a method to return a to be waited before the next lock attempt. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockValueProvider.cs similarity index 88% rename from StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockValueProvider.cs index 6fb5c080..6931704a 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheLockValueProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheLockValueProvider.cs @@ -1,4 +1,4 @@ -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Defines a method to get a unique value that will be used as a value when locking keys in diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheRegionStrategyFactory.cs similarity index 94% rename from StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheRegionStrategyFactory.cs index aa1491f5..d2869b94 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/ICacheRegionStrategyFactory.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ICacheRegionStrategyFactory.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Defines a factory to create concrete instances. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IConnectionMultiplexerProvider.cs similarity index 92% rename from StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IConnectionMultiplexerProvider.cs index e068aebf..ff8f49dc 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IConnectionMultiplexerProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IConnectionMultiplexerProvider.cs @@ -1,6 +1,6 @@ using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Defines a method to provide an instance. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IDatabaseProvider.cs similarity index 92% rename from StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IDatabaseProvider.cs index c0524c02..76a08d76 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/IDatabaseProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/IDatabaseProvider.cs @@ -1,6 +1,6 @@ using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Defines a method to provide an instance. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/CheckVersion.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/CheckVersion.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/CheckVersion.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Get.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Get.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Get.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/GetMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/GetMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/GetMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/InitializeVersion.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Lock.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Lock.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Lock.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/LockMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/LockMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/LockMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Put.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Put.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Put.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/PutMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/PutMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/PutMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Remove.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Remove.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Remove.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/RemoveMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Unlock.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/Unlock.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/Unlock.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/UnlockMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UnlockMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/UnlockMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/UpdateVersion.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/ExpirationPutMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/LockMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/LockMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/LockMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/SlidingGet.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGet.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/SlidingGet.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/SlidingGetMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/SlidingGetMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/SlidingGetMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/Unlock.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/Unlock.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/Unlock.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/UnlockMany.lua similarity index 100% rename from StackExRedis/NHibernate.Caches.StackExRedis/Lua/FastRegionStrategy/UnlockMany.lua rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/FastRegionStrategy/UnlockMany.lua diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/LuaScriptProvider.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/LuaScriptProvider.cs index 66cd2c0b..c2727ed5 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/LuaScriptProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/LuaScriptProvider.cs @@ -4,7 +4,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Provides the lua scripts of the internal region strategies from the embedded resources. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.csproj similarity index 93% rename from StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.csproj index 0f28e2cf..d83d95d3 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernate.Caches.StackExRedis.csproj +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernate.Caches.StackExchangeRedis.csproj @@ -1,8 +1,8 @@  - NHibernate.Caches.StackExRedis - NHibernate.Caches.StackExRedis + NHibernate.Caches.StackExchangeRedis + NHibernate.Caches.StackExchangeRedis Redis cache provider for NHibernate using StackExchange.Redis. net461;netstandard2.0 diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernateTextWriter.cs similarity index 92% rename from StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernateTextWriter.cs index f6c12114..edfe8338 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/NHibernateTextWriter.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/NHibernateTextWriter.cs @@ -1,7 +1,7 @@ using System.IO; using System.Text; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { internal partial class NHibernateTextWriter : TextWriter { diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCache.cs similarity index 97% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCache.cs index a2f83e14..46b88c30 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCache.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCache.cs @@ -1,6 +1,6 @@ using NHibernate.Cache; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// A cache used to store objects into a Redis cache. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheConfiguration.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheConfiguration.cs index 0abaac3e..989e2177 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheConfiguration.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheConfiguration.cs @@ -1,7 +1,7 @@ using System; using NHibernate.Caches.Common; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Global cache configuration. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheLockConfiguration.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheLockConfiguration.cs index 34f82ecc..d270f12e 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheLockConfiguration.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheLockConfiguration.cs @@ -1,7 +1,7 @@ using System; using System.Text; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Cache configuration for locking keys. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheProvider.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheProvider.cs index 0890bfd6..391714a8 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheProvider.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheProvider.cs @@ -3,9 +3,9 @@ using System.Configuration; using NHibernate.Cache; using StackExchange.Redis; -using static NHibernate.Caches.StackExRedis.ConfigurationHelper; +using static NHibernate.Caches.StackExchangeRedis.ConfigurationHelper; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Cache provider using the classes. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheRegionConfiguration.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheRegionConfiguration.cs index c7d68faa..24b9637b 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisCacheRegionConfiguration.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisCacheRegionConfiguration.cs @@ -2,7 +2,7 @@ using System.Text; using NHibernate.Caches.Common; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Cache configuration for a region. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisEnvironment.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisEnvironment.cs index b6672698..49b073cb 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisEnvironment.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisEnvironment.cs @@ -1,6 +1,6 @@ using NHibernate.Cfg; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// An extension of NHibernate that provides configuration for the Redis cache. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisKeyLocker.cs similarity index 99% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisKeyLocker.cs index 4703efec..812ecd69 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisKeyLocker.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisKeyLocker.cs @@ -3,7 +3,7 @@ using NHibernate.Cache; using StackExchange.Redis; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Provides a mechanism for locking and unlocking one or many keys. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisSectionHandler.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisSectionHandler.cs index a8e14252..260cec96 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RedisSectionHandler.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RedisSectionHandler.cs @@ -3,7 +3,7 @@ using System.Configuration; using System.Xml; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Configuration file provider. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RegionConfig.cs similarity index 97% rename from StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RegionConfig.cs index 9d396bd4..6f57f6cb 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RegionConfig.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RegionConfig.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// Region configuration properties. diff --git a/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RetryPolicy.cs similarity index 98% rename from StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs rename to StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RetryPolicy.cs index 76c8762c..adfe0513 100644 --- a/StackExRedis/NHibernate.Caches.StackExRedis/RetryPolicy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/RetryPolicy.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NHibernate.Caches.StackExRedis +namespace NHibernate.Caches.StackExchangeRedis { /// /// A retry policy that can be applied to delegates returning a value of type . From 2fb32abbb2d0d6285a99e8e8cb09e3649396e8b8 Mon Sep 17 00:00:00 2001 From: maca88 Date: Sat, 8 Dec 2018 17:03:34 +0100 Subject: [PATCH 23/24] Replaced project name in missed files and added a build file --- AsyncGenerator.yml | 32 ++++++++++++++++---------------- StackExchangeRedis/default.build | 27 +++++++++++++++++++++++++++ appveyor.yml | 4 ++-- default.build | 1 + 4 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 StackExchangeRedis/default.build diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index efe8fa7c..cb3f67bf 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -28,7 +28,7 @@ registerPlugin: - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover assemblyName: AsyncGenerator.Core -- filePath: StackExRedis\NHibernate.Caches.StackExRedis\NHibernate.Caches.StackExRedis.csproj +- filePath: StackExchangeRedis\NHibernate.Caches.StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.csproj targetFramework: net461 concurrentRun: true applyChanges: true @@ -46,33 +46,33 @@ methodParameter: - parameter: Required requiresCancellationToken: - - containingType: NHibernate.Caches.StackExRedis.RedisCache + - containingType: NHibernate.Caches.StackExchangeRedis.RedisCache name: GetMany - - containingType: NHibernate.Caches.StackExRedis.RedisCache + - containingType: NHibernate.Caches.StackExchangeRedis.RedisCache name: PutMany - - containingType: NHibernate.Caches.StackExRedis.RedisCache + - containingType: NHibernate.Caches.StackExchangeRedis.RedisCache name: RemoveMany - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Get - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: GetMany - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Put - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: PutMany - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Remove - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: RemoveMany - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Unlock - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: UnlockMany - - containingType: NHibernate.Caches.StackExRedis.AbstractRegionStrategy + - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Clear - - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker + - containingType: NHibernate.Caches.StackExchangeRedis.RedisKeyLocker name: Unlock - - containingType: NHibernate.Caches.StackExRedis.RedisKeyLocker + - containingType: NHibernate.Caches.StackExchangeRedis.RedisKeyLocker name: UnlockMany scanMethodBody: true searchAsyncCounterpartsInInheritedTypes: true @@ -415,7 +415,7 @@ registerPlugin: - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder assemblyName: AsyncGenerator.Core -- filePath: StackExRedis\NHibernate.Caches.StackExRedis.Tests\NHibernate.Caches.StackExRedis.Tests.csproj +- filePath: StackExchangeRedis\NHibernate.Caches.StackExchangeRedis.Tests\NHibernate.Caches.StackExchangeRedis.Tests.csproj targetFramework: net461 concurrentRun: true applyChanges: true diff --git a/StackExchangeRedis/default.build b/StackExchangeRedis/default.build new file mode 100644 index 00000000..0f8c6a20 --- /dev/null +++ b/StackExchangeRedis/default.build @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index ac1700d7..e0a8afaa 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -111,7 +111,7 @@ test_script: #netFx tests If ($env:TESTS -eq 'net') { - @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache', 'CoreDistributedCache', 'StackExRedis') | ForEach-Object { + @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache', 'CoreDistributedCache', 'StackExchangeRedis') | ForEach-Object { $projects.Add($_, "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$target\NHibernate.Caches.$_.Tests.dll") } ForEach ($project in $projects.GetEnumerator()) { @@ -124,7 +124,7 @@ test_script: #core tests If ($env:TESTS -eq 'core') { - @('CoreMemoryCache', 'CoreDistributedCache', 'RtMemoryCache', 'StackExRedis') | ForEach-Object { + @('CoreMemoryCache', 'CoreDistributedCache', 'RtMemoryCache', 'StackExchangeRedis') | ForEach-Object { $projects.Add($_, "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$target\NHibernate.Caches.$_.Tests.dll") } ForEach ($project in $projects.GetEnumerator()) { diff --git a/default.build b/default.build index 637138d7..e1f1cf0c 100644 --- a/default.build +++ b/default.build @@ -19,6 +19,7 @@ + Date: Sun, 9 Dec 2018 14:25:38 +0100 Subject: [PATCH 24/24] Clean RemoveMany remnants And some additional minor clean-up --- AsyncGenerator.yml | 4 -- .../Async/CacheFixture.cs | 29 -------------- .../CacheFixture.cs | 29 -------------- .../AbstractRegionStrategy.cs | 34 ---------------- .../Async/AbstractRegionStrategy.cs | 40 ------------------- .../Async/DefaultRegionStrategy.cs | 18 --------- .../ConfigurationHelper.cs | 1 - .../DefaultCacheRegionStrategyFactory.cs | 3 +- .../DefaultRegionStrategy.cs | 21 ---------- .../Lua/DefaultRegionStrategy/RemoveMany.lua | 5 --- 10 files changed, 1 insertion(+), 183 deletions(-) delete mode 100644 StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index cb3f67bf..44c62cbd 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -50,8 +50,6 @@ name: GetMany - containingType: NHibernate.Caches.StackExchangeRedis.RedisCache name: PutMany - - containingType: NHibernate.Caches.StackExchangeRedis.RedisCache - name: RemoveMany - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Get - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy @@ -62,8 +60,6 @@ name: PutMany - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Remove - - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy - name: RemoveMany - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy name: Unlock - containingType: NHibernate.Caches.StackExchangeRedis.AbstractRegionStrategy diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index ddd5e68e..49bbe473 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -221,35 +221,6 @@ public async Task TestPutManyAsync() } } - [Test] - public async Task TestRemoveManyAsync() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestRemove{i}"; - values[i] = $"valueRemove{i}"; - } - - var cache = GetDefaultCache(); - - // add the item - await (cache.PutManyAsync(keys, values, CancellationToken.None)); - - // make sure it's there - var items = await (cache.GetManyAsync(keys, CancellationToken.None)); - Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); - - // remove it - foreach (var key in keys) - await (cache.RemoveAsync(key, CancellationToken.None)); - - // make sure it's not there - items = await (cache.GetManyAsync(keys, CancellationToken.None)); - Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); - } - [Test] public async Task TestLockUnlockManyAsync() { diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 7973bde9..f48d0ec5 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -222,35 +222,6 @@ public void TestPutMany() } } - [Test] - public void TestRemoveMany() - { - var keys = new object[10]; - var values = new object[10]; - for (var i = 0; i < keys.Length; i++) - { - keys[i] = $"keyTestRemove{i}"; - values[i] = $"valueRemove{i}"; - } - - var cache = GetDefaultCache(); - - // add the item - cache.PutMany(keys, values); - - // make sure it's there - var items = cache.GetMany(keys); - Assert.That(items, Is.EquivalentTo(values), "items just added are not there"); - - // remove it - foreach (var key in keys) - cache.Remove(key); - - // make sure it's not there - items = cache.GetMany(keys); - Assert.That(items, Is.EquivalentTo(new object[10]), "items still exists in cache after remove"); - } - [Test] public void TestLockUnlockMany() { diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs index f4e6fd58..a7f8dd2d 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/AbstractRegionStrategy.cs @@ -81,11 +81,6 @@ protected AbstractRegionStrategy(IConnectionMultiplexer connectionMultiplexer, /// protected virtual string RemoveScript => null; - /// - /// The lua script for removing many keys from the cache at once. - /// - protected virtual string RemoveManyScript => null; - /// /// The lua script for locking a key. /// @@ -346,35 +341,6 @@ public virtual bool Remove(object key) return (bool) results[0]; } - /// - /// Removes many keys from Redis at once. - /// - /// The keys to remove. - public virtual long RemoveMany(object[] keys) - { - if (keys == null) - { - throw new ArgumentNullException(nameof(keys)); - } - - Log.Debug("Removing {0} objects...", keys.Length); - var cacheKeys = new RedisKey[keys.Length]; - for (var i = 0; i < keys.Length; i++) - { - cacheKeys[i] = GetCacheKey(keys[i]); - Log.Debug("Removing object with key: '{0}'.", cacheKeys[i]); - } - - if (string.IsNullOrEmpty(RemoveManyScript)) - { - return Database.KeyDelete(cacheKeys); - } - - cacheKeys = AppendAdditionalKeys(cacheKeys); - var results = (RedisValue[]) Database.ScriptEvaluate(RemoveManyScript, cacheKeys, GetAdditionalValues()); - return (long) results[0]; - } - /// /// Locks the key. /// diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs index 779c778b..e03ddc1f 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/AbstractRegionStrategy.cs @@ -272,46 +272,6 @@ async Task InternalRemoveAsync() } } - /// - /// Removes many keys from Redis at once. - /// - /// The keys to remove. - /// A cancellation token that can be used to cancel the work - public virtual Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) - { - if (keys == null) - { - throw new ArgumentNullException(nameof(keys)); - } - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - return InternalRemoveManyAsync(); - async Task InternalRemoveManyAsync() - { - - Log.Debug("Removing {0} objects...", keys.Length); - var cacheKeys = new RedisKey[keys.Length]; - for (var i = 0; i < keys.Length; i++) - { - cacheKeys[i] = GetCacheKey(keys[i]); - Log.Debug("Removing object with key: '{0}'.", cacheKeys[i]); - } - - if (string.IsNullOrEmpty(RemoveManyScript)) - { - cancellationToken.ThrowIfCancellationRequested(); - return await (Database.KeyDeleteAsync(cacheKeys)).ConfigureAwait(false); - } - - cacheKeys = AppendAdditionalKeys(cacheKeys); - cancellationToken.ThrowIfCancellationRequested(); - var results = (RedisValue[]) await (Database.ScriptEvaluateAsync(RemoveManyScript, cacheKeys, GetAdditionalValues())).ConfigureAwait(false); - return (long) results[0]; - } - } - /// /// Locks the key. /// diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs index 5bfe0e0c..e46f0add 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Async/DefaultRegionStrategy.cs @@ -163,24 +163,6 @@ public override async Task RemoveAsync(object key, CancellationToken cance } } - /// - public override async Task RemoveManyAsync(object[] keys, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - try - { - return await (base.RemoveManyAsync(keys, cancellationToken)).ConfigureAwait(false); - } - catch (RedisServerException e) when (e.Message == InvalidVersionMessage) - { - Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); - cancellationToken.ThrowIfCancellationRequested(); - await (InitializeVersionAsync()).ConfigureAwait(false); - // There is no point removing the keys in the new version. - return 0L; - } - } - /// public override async Task UnlockAsync(object key, string lockValue, CancellationToken cancellationToken) { diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs index 0515c2f7..957f590e 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/ConfigurationHelper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using NHibernate.Bytecode; -using NHibernate.Cache; using NHibernate.Util; namespace NHibernate.Caches.StackExchangeRedis diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs index ccaab2c5..b1a28666 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultCacheRegionStrategyFactory.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using NHibernate.Cache; using StackExchange.Redis; diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs index bb3a63de..9e18fc23 100644 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs +++ b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/DefaultRegionStrategy.cs @@ -22,7 +22,6 @@ public partial class DefaultRegionStrategy : AbstractRegionStrategy private static readonly string PutLuaScript; private static readonly string PutManyLuaScript; private static readonly string RemoveLuaScript; - private static readonly string RemoveManyLuaScript; private static readonly string LockLuaScript; private static readonly string LockManyLuaScript; private static readonly string UnlockLuaScript; @@ -39,7 +38,6 @@ static DefaultRegionStrategy() PutLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Put)); PutManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(PutMany)); RemoveLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Remove)); - RemoveManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(RemoveMany)); LockLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Lock)); LockManyLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(LockMany)); UnlockLuaScript = LuaScriptProvider.GetConcatenatedScript(checkVersion, nameof(Unlock)); @@ -99,9 +97,6 @@ public DefaultRegionStrategy(IConnectionMultiplexer connectionMultiplexer, /// protected override string RemoveScript => RemoveLuaScript; - /// - protected override string RemoveManyScript => RemoveManyLuaScript; - /// protected override string LockScript => LockLuaScript; @@ -242,22 +237,6 @@ public override bool Remove(object key) } } - /// - public override long RemoveMany(object[] keys) - { - try - { - return base.RemoveMany(keys); - } - catch (RedisServerException e) when (e.Message == InvalidVersionMessage) - { - Log.Debug("Version '{0}' is not valid anymore, updating version...", CurrentVersion); - InitializeVersion(); - // There is no point removing the keys in the new version. - return 0L; - } - } - /// public override bool Unlock(object key, string lockValue) { diff --git a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua b/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua deleted file mode 100644 index 733008aa..00000000 --- a/StackExchangeRedis/NHibernate.Caches.StackExchangeRedis/Lua/DefaultRegionStrategy/RemoveMany.lua +++ /dev/null @@ -1,5 +0,0 @@ -local removedKeys = 0 -for i=1,#KEYS-1 do - removedKeys = removedKeys + redis.call('del', KEYS[i]) -end -return removedKeys