From d2745adabeb6d2a116640e1a49f2b45f0f0e500e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 20 Mar 2018 18:12:51 +0100 Subject: [PATCH 1/8] Add a .Net Core distributed cache This implementation is not standalone: an IDistributedCache implementation has to be provided through an IDistributedCacheFactory, to be implemented by the user. Fix #28 --- AsyncGenerator.yml | 70 +++++ .../App.config | 24 ++ .../CoreDistributedCacheFixture.cs | 39 +++ .../CoreDistributedCacheProviderFixture.cs | 67 +++++ ...reDistributedCacheSectionHandlerFixture.cs | 65 +++++ .../MemoryDistributedCacheFactory.cs | 19 ++ ...e.Caches.CoreDistributedCache.Tests.csproj | 28 ++ .../Program.cs | 12 + .../Properties/AssemblyInfo.cs | 3 + .../TestsContext.cs | 43 +++ .../AssemblyInfo.cs | 5 + .../Async/CoreDistributedCache.cs | 172 +++++++++++ .../CacheConfig.cs | 51 ++++ .../CoreDistributedCache.cs | 270 ++++++++++++++++++ .../CoreDistributedCacheProvider.cs | 160 +++++++++++ .../CoreDistributedCacheSectionHandler.cs | 69 +++++ .../IDistributedCacheFactory.cs | 16 ++ ...bernate.Caches.CoreDistributedCache.csproj | 36 +++ CoreDistributedCache/default.build | 32 +++ .../TestsContext.cs | 56 +--- .../Async/CacheFixture.cs | 3 + .../CacheFixture.cs | 4 + .../TestsContextHelper.cs | 66 +++++ NHibernate.Caches.Everything.sln | 14 + appveyor.yml | 6 +- default.build | 1 + 26 files changed, 1278 insertions(+), 53 deletions(-) create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj create mode 100644 CoreDistributedCache/default.build create mode 100644 NHibernate.Caches.Common.Tests/TestsContextHelper.cs diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml index 9b858a37..9489bf4e 100644 --- a/AsyncGenerator.yml +++ b/AsyncGenerator.yml @@ -1,4 +1,33 @@ projects: +- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj + targetFramework: net461 + concurrentRun: true + applyChanges: true + analyzation: + methodConversion: + - conversion: Ignore + hasAttributeName: ObsoleteAttribute + callForwarding: true + cancellationTokens: + guards: true + methodParameter: + - anyInterfaceRule: PubliclyExposedType + parameter: Optional + - parameter: Optional + rule: PubliclyExposedType + - parameter: Required + scanMethodBody: 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: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.csproj targetFramework: net461 concurrentRun: true @@ -293,6 +322,47 @@ registerPlugin: - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder assemblyName: AsyncGenerator.Core +- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Tests\NHibernate.Caches.CoreDistributedCache.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 + scanForMissingAsyncMembers: + - all: true + registerPlugin: + - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder + assemblyName: AsyncGenerator.Core - filePath: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.Tests\NHibernate.Caches.CoreMemoryCache.Tests.csproj targetFramework: net461 concurrentRun: true diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config new file mode 100644 index 00000000..fc7be1fb --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config @@ -0,0 +1,24 @@ + + + +
+
+ + + + + + + + + NHibernate.Connection.DriverConnectionProvider + NHibernate.Dialect.MsSql2005Dialect + NHibernate.Driver.SqlClientDriver + + Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI + + NHibernate.Caches.CoreDistributedCache.CoreDistributedCacheProvider,NHibernate.Caches.CoreDistributedCache + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs new file mode 100644 index 00000000..33c750ac --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs @@ -0,0 +1,39 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + [TestFixture] + public class CoreDistributedCacheFixture : CacheFixture + { + protected override bool SupportsSlidingExpiration => true; + protected override bool SupportsClear => false; + + protected override Func ProviderBuilder => + () => new CoreDistributedCacheProvider(); + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs new file mode 100644 index 00000000..85ace198 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs @@ -0,0 +1,67 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using System.Collections.Generic; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + [TestFixture] + public class CoreDistributedCacheProviderFixture : CacheProviderFixture + { + protected override Func ProviderBuilder => + () => new CoreDistributedCacheProvider(); + + [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 CoreDistributedCache; + Assert.That(cache, Is.Not.Null, "pre-configured foo cache not found"); + Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "Unexpected expiration value for foo region"); + + cache = (CoreDistributedCache)DefaultProvider.BuildCache("noExplicitExpiration", null); + Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(300)), + "Unexpected expiration value for noExplicitExpiration region"); + Assert.That(cache.UseSlidingExpiration, Is.True, "Unexpected sliding value for noExplicitExpiration region"); + + cache = (CoreDistributedCache)DefaultProvider + .BuildCache("noExplicitExpiration", new Dictionary { { "expiration", "100" } }); + Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(100)), + "Unexpected expiration value for noExplicitExpiration region with default expiration"); + + cache = (CoreDistributedCache)DefaultProvider + .BuildCache("noExplicitExpiration", new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } }); + Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(50)), + "Unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs new file mode 100644 index 00000000..52f5e568 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs @@ -0,0 +1,65 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System.Xml; +using NUnit.Framework; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + [TestFixture] + public class CoreDistributedCacheSectionHandlerFixture + { + private static XmlNode GetConfigurationSection(string xml) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + return doc.DocumentElement; + } + + [Test] + public void TestGetConfigNullSection() + { + var handler = new CoreDistributedCacheSectionHandler(); + var section = new XmlDocument(); + var result = handler.Create(null, null, section); + Assert.That(result, Is.Not.Null); + Assert.IsTrue(result is CacheConfig[]); + var caches = result as CacheConfig[]; + Assert.That(caches.Length, Is.EqualTo(0)); + } + + [Test] + public void TestGetConfigFromFile() + { + const string xmlSimple = ""; + + var handler = new CoreDistributedCacheSectionHandler(); + var section = GetConfigurationSection(xmlSimple); + var result = handler.Create(null, null, section); + Assert.That(result, Is.Not.Null); + Assert.IsTrue(result is CacheConfig[]); + var caches = result as CacheConfig[]; + Assert.That(caches.Length, Is.EqualTo(1)); + Assert.That(caches[0].Properties, Does.ContainKey("cache.use_sliding_expiration")); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs new file mode 100644 index 00000000..0536dc12 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + public class MemoryDistributedCacheFactory : IDistributedCacheFactory + { + public IDistributedCache BuildCache() + { + return new MemoryDistributedCache(new Options()); + } + + private class Options : MemoryDistributedCacheOptions, IOptions + { + MemoryDistributedCacheOptions IOptions.Value => this; + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj new file mode 100644 index 00000000..970a4c7e --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj @@ -0,0 +1,28 @@ + + + + NHibernate.Caches.CoreDistributedCache + Unit tests of cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Abstractions). + net461;netcoreapp2.0 + true + + + NETFX;$(DefineConstants) + + + Exe + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs new file mode 100644 index 00000000..5df1b669 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs @@ -0,0 +1,12 @@ +#if !NETFX +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + public class Program + { + public static int Main(string[] args) + { + return new NUnitLite.AutoRun(typeof(Program).Assembly).Execute(args); + } + } +} +#endif diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..345f41dd --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: CLSCompliant(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs new file mode 100644 index 00000000..dc8ff7f0 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs @@ -0,0 +1,43 @@ +#if !NETFX +using log4net.Repository.Hierarchy; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + [SetUpFixture] + public class TestsContext + { + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "coredistributedcache"); + //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" + //so we need to explicitly load the configuration + if (TestsContextHelper.ExecutingWithVsTest) + { + ConfigureLog4Net(); + } + } + + [OneTimeTearDown] + public void RunAfterAnyTests() + { + TestsContextHelper.RunAfterAnyTests(); + } + + 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; + } + } +} +#endif diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs new file mode 100644 index 00000000..bd9d30e7 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: CLSCompliant(true)] +[assembly: AssemblyDelaySign(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs new file mode 100644 index 00000000..8e39df04 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs @@ -0,0 +1,172 @@ +//------------------------------------------------------------------------------ +// +// 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 NHibernate.Cache; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Extensions.Caching.Distributed; +using NHibernate.Util; + +namespace NHibernate.Caches.CoreDistributedCache +{ + using System.Threading.Tasks; + using System.Threading; + public partial class CoreDistributedCache : ICache + { + + /// + public async Task GetAsync(object key, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (key == null) + { + return null; + } + + var cacheKey = GetCacheKey(key); + Log.Debug("Fetching object '{0}' from the cache.", cacheKey); + + var cachedData = await (_cache.GetAsync(cacheKey, cancellationToken)).ConfigureAwait(false); + if (cachedData == null) + return null; + + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream(cachedData)) + { + var entry = serializer.Deserialize(stream) as Tuple; + return Equals(entry?.Item1, key) ? entry.Item2 : null; + } + } + + /// + public Task PutAsync(object key, object value, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key), "null key not allowed"); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value), "null value not allowed"); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + + byte[] cachedData; + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream()) + { + var entry = new Tuple(key, value); + serializer.Serialize(stream, entry); + cachedData = stream.ToArray(); + } + + var cacheKey = GetCacheKey(key); + var options = new DistributedCacheEntryOptions(); + if (UseSlidingExpiration) + options.SlidingExpiration = Expiration; + else + options.AbsoluteExpirationRelativeToNow = Expiration; + + Log.Debug("putting item with key: {0}", cacheKey); + return _cache.SetAsync(cacheKey, cachedData, options, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + public Task RemoveAsync(object key, CancellationToken cancellationToken) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + + var cacheKey = GetCacheKey(key); + Log.Debug("removing item with key: {0}", cacheKey); + return _cache.RemoveAsync(cacheKey, cancellationToken); + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + public Task ClearAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + Clear(); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + public Task LockAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + Lock(key); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + public Task UnlockAsync(object key, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + Unlock(key); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs new file mode 100644 index 00000000..1eabf119 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Cache config properties. + /// + public class CacheConfig + { + /// + /// Build a cache global configuration. + /// + /// The factory class name to use for getting + /// instances. + public CacheConfig(string factoryClass) + { + FactoryClass = factoryClass; + Global = true; + } + + /// + /// Build a cache region configuration. + /// + /// The configured cache region. + /// The expiration for the region. + /// Whether the expiration should be sliding or not. + public CacheConfig(string region, string expiration, string sliding) + { + Region = region; + Properties = new Dictionary(); + if (!string.IsNullOrEmpty(expiration)) + Properties["expiration"] = expiration; + if (!string.IsNullOrEmpty(sliding)) + Properties["cache.use_sliding_expiration"] = sliding; + } + + /// Whether this configuration is global or is a cache region configuration. + public bool Global { get; } + + /// The region name. + public string Region { get; } + + /// The factory class name to use for getting + /// instances. + public string FactoryClass { get; } + + /// The configuration properties. + public IDictionary Properties { get; } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs new file mode 100644 index 00000000..60ec79a5 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs @@ -0,0 +1,270 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using NHibernate.Cache; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Extensions.Caching.Distributed; +using NHibernate.Util; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Pluggable cache implementation using the Microsoft.Extensions.Caching.Memory classes. + /// + /// + /// Priority is not configurable because it is un-usable: the compaction on memory pressure feature has been + /// removed from MemoryCache, only explicit compaction or size limit compaction may use priorities. But + /// API does not have a suitable method for triggering compaction, and size of each + /// cached entry has to be user provided, which API does not support. + /// + public partial class CoreDistributedCache : ICache + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(CoreDistributedCache)); + + private readonly IDistributedCache _cache; + + private static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(300); + private const bool _defaultUseSlidingExpiration = false; + private static readonly string DefaultRegionPrefix = string.Empty; + private const string _cacheKeyPrefix = "NHibernate-Cache:"; + + private string _fullRegion; + + /// + /// Default constructor. + /// + /// The instance to use. + /// The region of the cache. + /// Cache configuration properties. + /// + /// There are three (3) configurable parameters: + ///
    + ///
  • expiration (or cache.default_expiration) = number of seconds to wait before expiring each item.
  • + ///
  • cache.use_sliding_expiration = a boolean, true for resetting a cached item expiration each time it is accessed.
  • + ///
  • regionPrefix = a string for prefixing the region name.
  • + ///
+ /// All parameters are optional. The defaults are an expiration of 300 seconds, no sliding expiration and no prefix. + ///
+ /// The "expiration" property could not be parsed. + public CoreDistributedCache(IDistributedCache cache, string region, IDictionary properties) + { + _cache = cache; + RegionName = region; + Configure(properties); + } + + /// + public string RegionName { get; } + + /// + /// The expiration delay applied to cached items. + /// + public TimeSpan Expiration { get; private set; } + + /// + /// Should the expiration delay be sliding? + /// + /// for resetting a cached item expiration each time it is accessed. + public bool UseSlidingExpiration { get; private set; } + + private void Configure(IDictionary props) + { + var regionPrefix = DefaultRegionPrefix; + if (props == null) + { + Log.Warn("Configuring cache with default values"); + Expiration = DefaultExpiration; + UseSlidingExpiration = _defaultUseSlidingExpiration; + } + else + { + Expiration = GetExpiration(props); + UseSlidingExpiration = GetUseSlidingExpiration(props); + regionPrefix = GetRegionPrefix(props); + } + + _fullRegion = regionPrefix + RegionName; + } + + private static string GetRegionPrefix(IDictionary props) + { + if (props.TryGetValue("regionPrefix", out var result)) + { + Log.Debug("new regionPrefix: {0}", result); + } + else + { + result = DefaultRegionPrefix; + Log.Debug("no regionPrefix value given, using defaults"); + } + + return result; + } + + private static TimeSpan GetExpiration(IDictionary props) + { + var result = DefaultExpiration; + if (!props.TryGetValue("expiration", out var expirationString)) + { + props.TryGetValue(Cfg.Environment.CacheDefaultExpiration, out expirationString); + } + + if (expirationString != null) + { + if (int.TryParse(expirationString, out var seconds)) + { + result = TimeSpan.FromSeconds(seconds); + Log.Debug("new expiration value: {0}", seconds); + } + else + { + Log.Error("error parsing expiration value '{0}'", expirationString); + throw new ArgumentException($"could not parse expiration '{expirationString}' as a number of seconds"); + } + } + else + { + Log.Debug("no expiration value given, using defaults"); + } + + return result; + } + + private static bool GetUseSlidingExpiration(IDictionary props) + { + var sliding = PropertiesHelper.GetBoolean("cache.use_sliding_expiration", props, _defaultUseSlidingExpiration); + Log.Debug("Use sliding expiration value: {0}", sliding); + return sliding; + } + + private string GetCacheKey(object key) + { + return string.Concat(_cacheKeyPrefix, _fullRegion, ":", key.ToString(), "@", key.GetHashCode()); + } + + /// + public object Get(object key) + { + if (key == null) + { + return null; + } + + var cacheKey = GetCacheKey(key); + Log.Debug("Fetching object '{0}' from the cache.", cacheKey); + + var cachedData = _cache.Get(cacheKey); + if (cachedData == null) + return null; + + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream(cachedData)) + { + var entry = serializer.Deserialize(stream) as Tuple; + return Equals(entry?.Item1, key) ? entry.Item2 : null; + } + } + + /// + public void Put(object key, object value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key), "null key not allowed"); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value), "null value not allowed"); + } + + byte[] cachedData; + var serializer = new BinaryFormatter(); + using (var stream = new MemoryStream()) + { + var entry = new Tuple(key, value); + serializer.Serialize(stream, entry); + cachedData = stream.ToArray(); + } + + var cacheKey = GetCacheKey(key); + var options = new DistributedCacheEntryOptions(); + if (UseSlidingExpiration) + options.SlidingExpiration = Expiration; + else + options.AbsoluteExpirationRelativeToNow = Expiration; + + Log.Debug("putting item with key: {0}", cacheKey); + _cache.Set(cacheKey, cachedData, options); + } + + /// + public void Remove(object key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var cacheKey = GetCacheKey(key); + Log.Debug("removing item with key: {0}", cacheKey); + _cache.Remove(cacheKey); + } + + /// + public void Clear() + { + // Like IMemoryCache, it does not support Clear. Unlike it, it does neither provides a dependency + // mechanism which would allow to implement it. + Log.Warn($"Clear is not supported by {nameof(IDistributedCache)}, ignoring the call."); + } + + /// + public void Destroy() + { + } + + /// + public void Lock(object key) + { + // Do nothing + } + + /// + public void Unlock(object key) + { + // Do nothing + } + + /// + public long NextTimestamp() + { + return Timestamper.Next(); + } + + /// + public int Timeout => Timestamper.OneMs * 60000; + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs new file mode 100644 index 00000000..6bf85108 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs @@ -0,0 +1,160 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Text; +using Microsoft.Extensions.Caching.Distributed; +using NHibernate.Cache; +using NHibernate.Util; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Cache provider using the System.Runtime.Caching classes + /// + public class CoreDistributedCacheProvider : ICacheProvider + { + private static readonly Dictionary> ConfiguredCachesProperties; + private static readonly INHibernateLogger Log; + + /// + /// The factory to use for getting + /// instances. By default, its value is initialized from factory-class attribute of the + /// coredistributedcache configuration section. + /// + /// + /// Changes to this property affect only caches built after the change. + /// + public static IDistributedCacheFactory CacheFactory { get; set; } + + static CoreDistributedCacheProvider() + { + Log = NHibernateLogger.For(typeof(CoreDistributedCacheProvider)); + ConfiguredCachesProperties = new Dictionary>(); + + if (!(ConfigurationManager.GetSection("coredistributedcache") is CacheConfig[] list)) + return; + + foreach (var cache in list) + { + if (cache.Global) + { + if (!string.IsNullOrEmpty(cache.FactoryClass)) + { + try + { + CacheFactory = + (IDistributedCacheFactory) Cfg.Environment.BytecodeProvider.ObjectsFactory + .CreateInstance(ReflectHelper.ClassForName(cache.FactoryClass)); + } + catch (Exception e) + { + throw new HibernateException( + $"Could not create the {nameof(IDistributedCacheFactory)} factory from '{cache.FactoryClass}'.", + e); + } + } + continue; + } + + ConfiguredCachesProperties.Add(cache.Region, cache.Properties); + } + } + + #region ICacheProvider Members + + /// + public ICache BuildCache(string regionName, IDictionary properties) + { + if (CacheFactory == null) + throw new InvalidOperationException( + $"{nameof(CacheFactory)} is null, cannot build a distributed cache without a cache factory. " + + $"Please provide coredistributedcache configuration section with a factory-class attribute or set" + + $"{nameof(CacheFactory)} before building a session factory."); + + if (regionName == null) + { + regionName = string.Empty; + } + + if (ConfiguredCachesProperties.TryGetValue(regionName, out var configuredProperties) && configuredProperties.Count > 0) + { + if (properties != null) + { + // Duplicate it for not altering the global configuration + properties = new Dictionary(properties); + foreach (var prop in configuredProperties) + { + properties[prop.Key] = prop.Value; + } + } + else + { + properties = configuredProperties; + } + } + + // create cache + if (properties == null) + { + properties = new Dictionary(1); + } + + if (Log.IsDebugEnabled()) + { + var sb = new StringBuilder(); + + foreach (var de in properties) + { + sb.Append("name="); + sb.Append(de.Key); + sb.Append("&value="); + sb.Append(de.Value); + sb.Append(";"); + } + + Log.Debug("building cache with region: {0}, properties: {1}, factory: {2}" , regionName, sb.ToString(), CacheFactory.GetType().FullName); + } + return new CoreDistributedCache(CacheFactory.BuildCache(), regionName, properties); + } + + /// + public long NextTimestamp() + { + return Timestamper.Next(); + } + + /// + public void Start(IDictionary properties) + { + } + + /// + public void Stop() + { + } + + #endregion + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs new file mode 100644 index 00000000..53947caa --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Configuration; +using System.Xml; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Configuration file provider. + /// + public class CoreDistributedCacheSectionHandler : IConfigurationSectionHandler + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(CoreDistributedCacheSectionHandler)); + + #region IConfigurationSectionHandler Members + + /// + /// Parse the config section. + /// + /// + /// + /// + /// An array of CacheConfig objects. + public object Create(object parent, object configContext, XmlNode section) + { + var caches = new List(); + + var fc = section.Attributes?["factory-class"]; + if (fc != null) + { + caches.Add(new CacheConfig(fc.Value)); + } + + var nodes = section.SelectNodes("cache"); + foreach (XmlNode node in nodes) + { + string region = null; + string expiration = null; + string sliding = null; + var r = node.Attributes["region"]; + var e = node.Attributes["expiration"]; + var s = node.Attributes["sliding"]; + if (r != null) + { + region = r.Value; + } + if (e != null) + { + expiration = e.Value; + } + if (s != null) + { + sliding = s.Value; + } + if (region != null) + { + caches.Add(new CacheConfig(region, expiration, sliding)); + } + else + { + Log.Warn("Found a cache node lacking a region name: ignored. Node: {0}", + node.OuterXml); + } + } + return caches.ToArray(); + } + + #endregion + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs new file mode 100644 index 00000000..4546d1de --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Caching.Distributed; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Interface for factories building instances. + /// + public interface IDistributedCacheFactory + { + /// + /// Build a instance. + /// + /// A instance. + IDistributedCache BuildCache(); + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj new file mode 100644 index 00000000..84ae324e --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj @@ -0,0 +1,36 @@ + + + + NHibernate.Caches.CoreDistributedCache + NHibernate.Caches.CoreDistributedCache + Cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Abstractions). + + net461;netstandard2.0 + $(NoWarn);3001;3002 + True + ..\..\NHibernate.Caches.snk + * New feature + * #28 - Add a .Net Core DistributedCache + + + NETFX;$(DefineConstants) + + + + + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + \ No newline at end of file diff --git a/CoreDistributedCache/default.build b/CoreDistributedCache/default.build new file mode 100644 index 00000000..8fa8b61e --- /dev/null +++ b/CoreDistributedCache/default.build @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs b/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs index 2ad480e8..b31c6db5 100644 --- a/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs +++ b/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs @@ -1,73 +1,29 @@ #if !NETFX -using NUnit.Framework; -using System.Configuration; -using System.IO; -using System.Reflection; using log4net.Repository.Hierarchy; -using NHibernate.Cfg; -using NHibernate.Cfg.ConfigurationSchema; -using Environment = NHibernate.Cfg.Environment; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; namespace NHibernate.Caches.CoreMemoryCache.Tests { [SetUpFixture] public class TestsContext { - private static readonly bool ExecutingWithVsTest = - Assembly.GetEntryAssembly()?.GetName().Name == "testhost"; - - private static bool _removeTesthostConfig; - [OneTimeSetUp] public void RunBeforeAnyTests() { + TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "corememorycache"); //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" //so we need to explicitly load the configuration - if (ExecutingWithVsTest) + if (TestsContextHelper.ExecutingWithVsTest) { - Environment.InitializeGlobalProperties(GetTestAssemblyHibernateConfiguration()); - ReadCoreCacheSectionFromTesthostConfig(); + ConfigureLog4Net(); } - - ConfigureLog4Net(); } [OneTimeTearDown] public void RunAfterAnyTests() { - if (_removeTesthostConfig) - { - File.Delete(GetTesthostConfigPath()); - } - } - - private static void ReadCoreCacheSectionFromTesthostConfig() - { - // For caches section, ConfigurationManager being directly used, the only workaround is to provide - // the configuration with its expected file name... - var assemblyPath = - Path.Combine(TestContext.CurrentContext.TestDirectory, Path.GetFileName(typeof(TestsContext).Assembly.Location)); - var configPath = assemblyPath + ".config"; - // If this copy fails: either testconfig has started having its own file, and this hack can no more be used, - // or a previous test run was interupted before its cleanup (RunAfterAnyTests): go clean it manually. - // Discussion about this mess: https://github.com/dotnet/corefx/issues/22101 - File.Copy(configPath, GetTesthostConfigPath()); - _removeTesthostConfig = true; - ConfigurationManager.RefreshSection("corememorycache"); - } - - private static string GetTesthostConfigPath() - { - return Assembly.GetEntryAssembly().Location + ".config"; - } - - private static IHibernateConfiguration GetTestAssemblyHibernateConfiguration() - { - var assemblyPath = - Path.Combine(TestContext.CurrentContext.TestDirectory, Path.GetFileName(typeof(TestsContext).Assembly.Location)); - var configuration = ConfigurationManager.OpenExeConfiguration(assemblyPath); - var section = configuration.GetSection(CfgXmlHelper.CfgSectionName); - return HibernateConfiguration.FromAppConfig(section.SectionInformation.GetRawXml()); + TestsContextHelper.RunAfterAnyTests(); } private static void ConfigureLog4Net() diff --git a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs index 81e5b600..2d46633d 100644 --- a/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/Async/CacheFixture.cs @@ -64,6 +64,9 @@ public async Task TestRemoveAsync() [Test] public async Task TestClearAsync() { + if (!SupportsClear) + Assert.Ignore("Test not supported by provider"); + const string key = "keyTestClear"; const string value = "valueClear"; diff --git a/NHibernate.Caches.Common.Tests/CacheFixture.cs b/NHibernate.Caches.Common.Tests/CacheFixture.cs index 6bc7085c..420da792 100644 --- a/NHibernate.Caches.Common.Tests/CacheFixture.cs +++ b/NHibernate.Caches.Common.Tests/CacheFixture.cs @@ -11,6 +11,7 @@ public abstract partial class CacheFixture : Fixture { protected virtual bool SupportsSlidingExpiration => false; protected virtual bool SupportsDistinguishingKeysWithSameStringRepresentationAndHashcode => true; + protected virtual bool SupportsClear => true; [Test] public void TestPut() @@ -56,6 +57,9 @@ public void TestRemove() [Test] public void TestClear() { + if (!SupportsClear) + Assert.Ignore("Test not supported by provider"); + const string key = "keyTestClear"; const string value = "valueClear"; diff --git a/NHibernate.Caches.Common.Tests/TestsContextHelper.cs b/NHibernate.Caches.Common.Tests/TestsContextHelper.cs new file mode 100644 index 00000000..aa964a9a --- /dev/null +++ b/NHibernate.Caches.Common.Tests/TestsContextHelper.cs @@ -0,0 +1,66 @@ +#if !NETFX +using System.Configuration; +using System.IO; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Cfg.ConfigurationSchema; +using NUnit.Framework; +using Environment = NHibernate.Cfg.Environment; + +namespace NHibernate.Caches.Common.Tests +{ + public static class TestsContextHelper + { + public static bool ExecutingWithVsTest { get; } = + Assembly.GetEntryAssembly()?.GetName().Name == "testhost"; + + private static bool _removeTesthostConfig; + + public static void RunBeforeAnyTests(Assembly testAssembly, string configSectionName) + { + //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" + //so we need to explicitly load the configuration + if (ExecutingWithVsTest) + { + var assemblyPath = + Path.Combine(TestContext.CurrentContext.TestDirectory, Path.GetFileName(testAssembly.Location)); + Environment.InitializeGlobalProperties(GetTestAssemblyHibernateConfiguration(assemblyPath)); + ReadCoreCacheSectionFromTesthostConfig(assemblyPath, configSectionName); + } + } + + public static void RunAfterAnyTests() + { + if (_removeTesthostConfig) + { + File.Delete(GetTesthostConfigPath()); + } + } + + private static void ReadCoreCacheSectionFromTesthostConfig(string assemblyPath, string configSectionName) + { + // For caches section, ConfigurationManager being directly used, the only workaround is to provide + // the configuration with its expected file name... + var configPath = assemblyPath + ".config"; + // If this copy fails: either testconfig has started having its own file, and this hack can no more be used, + // or a previous test run was interupted before its cleanup (RunAfterAnyTests): go clean it manually. + // Discussion about this mess: https://github.com/dotnet/corefx/issues/22101 + File.Copy(configPath, GetTesthostConfigPath()); + _removeTesthostConfig = true; + ConfigurationManager.RefreshSection(configSectionName); + } + + private static string GetTesthostConfigPath() + { + return Assembly.GetEntryAssembly().Location + ".config"; + } + + private static IHibernateConfiguration GetTestAssemblyHibernateConfiguration(string assemblyPath) + { + var configuration = ConfigurationManager.OpenExeConfiguration(assemblyPath); + var section = configuration.GetSection(CfgXmlHelper.CfgSectionName); + return HibernateConfiguration.FromAppConfig(section.SectionInformation.GetRawXml()); + } + } +} +#endif diff --git a/NHibernate.Caches.Everything.sln b/NHibernate.Caches.Everything.sln index b12158ae..a93f420a 100644 --- a/NHibernate.Caches.Everything.sln +++ b/NHibernate.Caches.Everything.sln @@ -56,6 +56,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreMemor 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -138,6 +142,14 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -162,5 +174,7 @@ Global {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} EndGlobalSection EndGlobal diff --git a/appveyor.yml b/appveyor.yml index 5b185a77..72b3b7e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -99,7 +99,7 @@ test_script: $TestsFailed = $FALSE #netFx tests If ($env:TESTS -eq 'net') { - @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache') | ForEach-Object { + @('EnyimMemcached', 'Prevalence', 'RtMemoryCache', 'SysCache', 'SysCache2', 'CoreMemoryCache', 'CoreDistributedCache') | ForEach-Object { nunit3-console (Join-Path $env:APPVEYOR_BUILD_FOLDER "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$env:NETTARGETFX\NHibernate.Caches.$_.Tests.dll") "--result=$_-NetTestResult.xml;format=AppVeyor" If ($LASTEXITCODE -ne 0) { $TestsFailed = $TRUE @@ -109,8 +109,8 @@ test_script: #core tests If ($env:TESTS -eq 'core') { - @('CoreMemoryCache') | ForEach-Object { - dotnet (Join-Path $env:APPVEYOR_BUILD_FOLDER "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$env:CORETARGETFX\NHibernate.Caches.$_.Tests.dll") --labels=before --nocolor "--result=$_-CoreTestResult.xml" + @('CoreMemoryCache', 'CoreDistributedCache') | ForEach-Object { + dotnet (Join-Path $env:APPVEYOR_BUILD_FOLDER "$_\NHibernate.Caches.$_.Tests\bin\$env:CONFIGURATION\$env:CORETARGETFX\NHibernate.Caches.$_.Tests.dll") --labels=before --nocolor "--result=$_-CoreTestResult.xml" If ($LASTEXITCODE -ne 0) { $TestsFailed = $TRUE } diff --git a/default.build b/default.build index 0f6761a4..c1724a00 100644 --- a/default.build +++ b/default.build @@ -16,6 +16,7 @@ + Date: Wed, 21 Mar 2018 15:33:48 +0100 Subject: [PATCH 2/8] Refine .Net Core distributed cache factory model And provide by default the MemoryDistributedCacheFactory. To be squashed. --- .../App.config | 6 +- .../CoreDistributedCacheProviderFixture.cs | 42 ++++++- ...reDistributedCacheSectionHandlerFixture.cs | 36 ++++-- .../MemoryDistributedCacheFactory.cs | 19 --- .../CacheConfig.cs | 40 +++--- .../CoreDistributedCacheProvider.cs | 40 +++--- .../CoreDistributedCacheSectionHandler.cs | 49 ++++---- .../MemoryDistributedCacheFactory.cs | 115 ++++++++++++++++++ 8 files changed, 254 insertions(+), 93 deletions(-) delete mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config index fc7be1fb..a3a4d4e2 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config @@ -6,7 +6,11 @@ type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> - + + + 00:10:00 + 1048576 + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs index 85ace198..157cdcab 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs @@ -22,6 +22,9 @@ using System; using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; using NUnit.Framework; @@ -34,6 +37,36 @@ public class CoreDistributedCacheProviderFixture : CacheProviderFixture protected override Func ProviderBuilder => () => new CoreDistributedCacheProvider(); + private static readonly FieldInfo MemoryCacheField = + typeof(MemoryDistributedCache).GetField("_memCache", BindingFlags.Instance | BindingFlags.NonPublic); + + private static readonly FieldInfo CacheOptionsField = + typeof(MemoryCache).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); + + [Test] + public void ConfiguredCacheFactory() + { + var factory = CoreDistributedCacheProvider.CacheFactory; + Assert.That(factory, Is.Not.Null, "Factory not found"); + Assert.That(factory, Is.InstanceOf(), "Unexpected factory"); + var cache1 = factory.BuildCache(); + Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); + Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); + var cache2 = factory.BuildCache(); + Assert.That(cache2, Is.EqualTo(cache1), + "The distributed cache factory is supposed to always yield the same instance"); + + var memCache = MemoryCacheField.GetValue(cache1); + Assert.That(memCache, Is.Not.Null, "Underlying memory cache not found"); + Assert.That(memCache, Is.InstanceOf(), "Unexpected memory cache"); + var options = CacheOptionsField.GetValue(memCache); + Assert.That(options, Is.Not.Null, "Memory cache options not found"); + Assert.That(options, Is.InstanceOf(), "Unexpected options type"); + var memOptions = (MemoryCacheOptions) options; + Assert.That(memOptions.ExpirationScanFrequency, Is.EqualTo(TimeSpan.FromMinutes(10))); + Assert.That(memOptions.SizeLimit, Is.EqualTo(1048576)); + } + [Test] public void TestBuildCacheFromConfig() { @@ -48,18 +81,19 @@ public void TestExpiration() Assert.That(cache, Is.Not.Null, "pre-configured foo cache not found"); Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "Unexpected expiration value for foo region"); - cache = (CoreDistributedCache)DefaultProvider.BuildCache("noExplicitExpiration", null); + cache = (CoreDistributedCache) DefaultProvider.BuildCache("noExplicitExpiration", null); Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(300)), "Unexpected expiration value for noExplicitExpiration region"); Assert.That(cache.UseSlidingExpiration, Is.True, "Unexpected sliding value for noExplicitExpiration region"); - cache = (CoreDistributedCache)DefaultProvider + cache = (CoreDistributedCache) DefaultProvider .BuildCache("noExplicitExpiration", new Dictionary { { "expiration", "100" } }); Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(100)), "Unexpected expiration value for noExplicitExpiration region with default expiration"); - cache = (CoreDistributedCache)DefaultProvider - .BuildCache("noExplicitExpiration", new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } }); + cache = (CoreDistributedCache) DefaultProvider + .BuildCache("noExplicitExpiration", + new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } }); Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(50)), "Unexpected expiration value for noExplicitExpiration region with cache.default_expiration"); } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs index 52f5e568..4694ead1 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs @@ -41,25 +41,41 @@ public void TestGetConfigNullSection() var handler = new CoreDistributedCacheSectionHandler(); var section = new XmlDocument(); var result = handler.Create(null, null, section); - Assert.That(result, Is.Not.Null); - Assert.IsTrue(result is CacheConfig[]); - var caches = result as CacheConfig[]; - Assert.That(caches.Length, Is.EqualTo(0)); + Assert.That(result, Is.Not.Null, "result"); + Assert.That(result, Is.InstanceOf()); + var config = (CacheConfig) result; + Assert.That(config.Properties, Is.Not.Null, "Properties"); + Assert.That(config.Properties.Count, Is.EqualTo(0), "Properties count"); + Assert.That(config.Regions, Is.Not.Null, "Regions"); + Assert.That(config.Regions.Length, Is.EqualTo(0)); } [Test] public void TestGetConfigFromFile() { - const string xmlSimple = ""; + const string xmlSimple = "Value1"; var handler = new CoreDistributedCacheSectionHandler(); var section = GetConfigurationSection(xmlSimple); var result = handler.Create(null, null, section); - Assert.That(result, Is.Not.Null); - Assert.IsTrue(result is CacheConfig[]); - var caches = result as CacheConfig[]; - Assert.That(caches.Length, Is.EqualTo(1)); - Assert.That(caches[0].Properties, Does.ContainKey("cache.use_sliding_expiration")); + Assert.That(result, Is.Not.Null, "result"); + Assert.That(result, Is.InstanceOf()); + var config = (CacheConfig) result; + + Assert.That(config.FactoryClass, Is.EqualTo("factory1")); + + Assert.That(config.Properties, Is.Not.Null, "Properties"); + Assert.That(config.Properties.Count, Is.EqualTo(1), "Properties count"); + Assert.That(config.Properties, Does.ContainKey("prop1")); + Assert.That(config.Properties["prop1"], Is.EqualTo("Value1")); + + Assert.That(config.Regions, Is.Not.Null, "Regions"); + Assert.That(config.Regions.Length, Is.EqualTo(1), "Regions count"); + Assert.That(config.Regions[0].Region, Is.EqualTo("foo")); + Assert.That(config.Regions[0].Properties, Does.ContainKey("cache.use_sliding_expiration")); + Assert.That(config.Regions[0].Properties["cache.use_sliding_expiration"], Is.EqualTo("true")); + Assert.That(config.Regions[0].Properties, Does.ContainKey("expiration")); + Assert.That(config.Regions[0].Properties["expiration"], Is.EqualTo("500")); } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs deleted file mode 100644 index 0536dc12..00000000 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/MemoryDistributedCacheFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; - -namespace NHibernate.Caches.CoreDistributedCache.Tests -{ - public class MemoryDistributedCacheFactory : IDistributedCacheFactory - { - public IDistributedCache BuildCache() - { - return new MemoryDistributedCache(new Options()); - } - - private class Options : MemoryDistributedCacheOptions, IOptions - { - MemoryDistributedCacheOptions IOptions.Value => this; - } - } -} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs index 1eabf119..5e3bb103 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConfig.cs @@ -4,28 +4,47 @@ namespace NHibernate.Caches.CoreDistributedCache { /// - /// Cache config properties. + /// Cache configuration properties. /// public class CacheConfig { /// - /// Build a cache global configuration. + /// Build a cache configuration. /// /// The factory class name to use for getting /// instances. - public CacheConfig(string factoryClass) + /// The cache configuration properties. + /// The configured cache regions. + public CacheConfig(string factoryClass, IDictionary properties, RegionConfig[] regions) { FactoryClass = factoryClass; - Global = true; + Regions = regions; + Properties = properties; } + /// The factory class name to use for getting + /// instances. + public string FactoryClass { get; } + + /// The configured cache regions. + public RegionConfig[] Regions { get; } + + /// The cache configuration properties. + public IDictionary Properties { get; } + } + + /// + /// Region configuration properties. + /// + public class RegionConfig + { /// /// Build a cache region configuration. /// /// The configured cache region. /// The expiration for the region. /// Whether the expiration should be sliding or not. - public CacheConfig(string region, string expiration, string sliding) + public RegionConfig(string region, string expiration, string sliding) { Region = region; Properties = new Dictionary(); @@ -35,17 +54,10 @@ public CacheConfig(string region, string expiration, string sliding) Properties["cache.use_sliding_expiration"] = sliding; } - /// Whether this configuration is global or is a cache region configuration. - public bool Global { get; } - /// The region name. public string Region { get; } - /// The factory class name to use for getting - /// instances. - public string FactoryClass { get; } - - /// The configuration properties. - public IDictionary Properties { get; } + /// The region configuration properties. + public IDictionary Properties { get; } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs index 6bf85108..99747d8a 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs @@ -37,6 +37,7 @@ public class CoreDistributedCacheProvider : ICacheProvider { private static readonly Dictionary> ConfiguredCachesProperties; private static readonly INHibernateLogger Log; + private static readonly System.Type[] CacheFactoryCtorWithPropertiesSignature = { typeof(IDictionary) }; /// /// The factory to use for getting @@ -53,31 +54,32 @@ static CoreDistributedCacheProvider() Log = NHibernateLogger.For(typeof(CoreDistributedCacheProvider)); ConfiguredCachesProperties = new Dictionary>(); - if (!(ConfigurationManager.GetSection("coredistributedcache") is CacheConfig[] list)) + if (!(ConfigurationManager.GetSection("coredistributedcache") is CacheConfig config)) return; - foreach (var cache in list) + if (!string.IsNullOrEmpty(config.FactoryClass)) { - if (cache.Global) + try { - if (!string.IsNullOrEmpty(cache.FactoryClass)) - { - try - { - CacheFactory = - (IDistributedCacheFactory) Cfg.Environment.BytecodeProvider.ObjectsFactory - .CreateInstance(ReflectHelper.ClassForName(cache.FactoryClass)); - } - catch (Exception e) - { - throw new HibernateException( - $"Could not create the {nameof(IDistributedCacheFactory)} factory from '{cache.FactoryClass}'.", - e); - } - } - continue; + var factoryClass = ReflectHelper.ClassForName(config.FactoryClass); + var ctorWithProperties = factoryClass.GetConstructor(CacheFactoryCtorWithPropertiesSignature); + + CacheFactory = (IDistributedCacheFactory) (ctorWithProperties != null ? + ctorWithProperties.Invoke(new object[] { config.Properties }): + Cfg.Environment.BytecodeProvider.ObjectsFactory.CreateInstance(factoryClass)); } + catch (Exception e) + { + throw new HibernateException( + $"Could not create the {nameof(IDistributedCacheFactory)} factory from '{config.FactoryClass}'. " + + $"(It must implement {nameof(IDistributedCacheFactory)} and have a constructor accepting a " + + $"{nameof(IDictionary)} or have a parameterless constructor.)", + e); + } + } + foreach (var cache in config.Regions) + { ConfiguredCachesProperties.Add(cache.Region, cache.Properties); } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs index 53947caa..20e32f35 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs @@ -19,49 +19,46 @@ public class CoreDistributedCacheSectionHandler : IConfigurationSectionHandler /// /// /// - /// An array of CacheConfig objects. + /// A object. public object Create(object parent, object configContext, XmlNode section) { - var caches = new List(); + var caches = new List(); - var fc = section.Attributes?["factory-class"]; - if (fc != null) - { - caches.Add(new CacheConfig(fc.Value)); - } - var nodes = section.SelectNodes("cache"); foreach (XmlNode node in nodes) { - string region = null; - string expiration = null; - string sliding = null; - var r = node.Attributes["region"]; - var e = node.Attributes["expiration"]; - var s = node.Attributes["sliding"]; - if (r != null) - { - region = r.Value; - } - if (e != null) + var region = node.Attributes["region"]?.Value; + var expiration = node.Attributes["expiration"]?.Value; + var sliding = node.Attributes["sliding"]?.Value; + if (region != null) { - expiration = e.Value; + caches.Add(new RegionConfig(region, expiration, sliding)); } - if (s != null) + else { - sliding = s.Value; + Log.Warn("Found a cache region node lacking a region name: ignored. Node: {0}", + node.OuterXml); } - if (region != null) + } + + var factoryClass = section.Attributes?["factory-class"]?.Value; + var properties = new Dictionary(); + nodes = section.SelectNodes("properties/property"); + foreach (XmlNode node in nodes) + { + var name = node.Attributes["name"]?.Value; + if (name != null) { - caches.Add(new CacheConfig(region, expiration, sliding)); + properties.Add(name, node.InnerText); } else { - Log.Warn("Found a cache node lacking a region name: ignored. Node: {0}", + Log.Warn("Found a cache property node lacking a name: ignored. Node: {0}", node.OuterXml); } } - return caches.ToArray(); + + return new CacheConfig(factoryClass, properties, caches.ToArray()); } #endregion diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs new file mode 100644 index 00000000..6fa3b3b5 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// A memory "distributed" cache factory. Use for testing purpose. Otherwise consider using CoreMemoryCache + /// instead. + /// + public class MemoryDistributedCacheFactory : IDistributedCacheFactory + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemoryDistributedCacheFactory)); + private const string _expirationScanFrequency = "expiration-scan-frequency"; + private const string _sizeLimit = "size-limit"; + + private readonly IDistributedCache _cache; + + /// + /// Default constructor. + /// + public MemoryDistributedCacheFactory() : this(null) + { + } + + /// + /// Constructor with explicit configuration properties. + /// + /// See . + /// See . + public MemoryDistributedCacheFactory(TimeSpan? expirationScanFrequency, long? sizeLimit) + { + var options = new Options(); + if (expirationScanFrequency.HasValue) + options.ExpirationScanFrequency = expirationScanFrequency.Value; + if (sizeLimit.HasValue) + options.SizeLimit = sizeLimit.Value; + + _cache = new MemoryDistributedCache(options); + } + + /// + /// Constructor with configuration properties. It supports expiration-scan-frequency and + /// size-limit properties. See and + /// . + /// + /// The configurations properties. + /// + /// + /// If expiration-scan-frequency is provided as an integer, this integer will be used as a number + /// of minutes. Otherwise the setting will be parsed as a . + /// + /// size-limit has to be an integer, expressing the size limit in bytes. + /// + public MemoryDistributedCacheFactory(IDictionary properties) + { + var options = new Options(); + + if (properties != null) + { + if (properties.TryGetValue(_expirationScanFrequency, out var esf)) + { + if (esf != null) + { + if (int.TryParse(esf, out var minutes)) + options.ExpirationScanFrequency = TimeSpan.FromMinutes(minutes); + else if (TimeSpan.TryParse(esf, out var expirationScanFrequency)) + options.ExpirationScanFrequency = expirationScanFrequency; + Log.Warn( + "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.", + esf, _expirationScanFrequency); + } + else + { + Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _expirationScanFrequency); + } + } + + if (properties.TryGetValue(_sizeLimit, out var sl)) + { + if (sl != null) + { + if (long.TryParse(sl, out var bytes)) + options.SizeLimit = bytes; + Log.Warn( + "Invalid value '{0}' for {1} setting: it is not an integer. Ignoring.", + sl, _sizeLimit); + } + else + { + Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _sizeLimit); + } + } + } + + _cache = new MemoryDistributedCache(options); + } + + /// + public IDistributedCache BuildCache() + { + // Always yields the same instance: its underlying implementation is a MemoryCache which regularly spawn + // a background task for expiring items. This avoids creating many instances, thus avoiding potentially + // spawning many such background tasks at once. + return _cache; + } + + private class Options : MemoryDistributedCacheOptions, IOptions + { + MemoryDistributedCacheOptions IOptions.Value => this; + } + } +} From 0db48db3b7b328c30dedc0b761bb238f5940f697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Wed, 21 Mar 2018 20:23:12 +0100 Subject: [PATCH 3/8] Provide distributed cache factories * Move the MemoryDistributedCacheFactory to its own package for avoiding an undue dependency. * Add a RedisCache factory. * Add a SqlServerCache factory. To be squashed. --- .../AssemblyInfo.cs | 5 + .../MemoryFactory.cs} | 28 +++-- ....Caches.CoreDistributedCache.Memory.csproj | 36 ++++++ .../AssemblyInfo.cs | 5 + ...e.Caches.CoreDistributedCache.Redis.csproj | 35 ++++++ .../RedisFactory.cs | 73 ++++++++++++ .../AssemblyInfo.cs | 5 + ...ches.CoreDistributedCache.SqlServer.csproj | 35 ++++++ .../SqlServerFactory.cs | 109 ++++++++++++++++++ .../App.config | 2 +- .../CoreDistributedCacheProviderFixture.cs | 3 +- .../DistributedCacheFactoryFixture.cs | 93 +++++++++++++++ ...e.Caches.CoreDistributedCache.Tests.csproj | 3 + ...bernate.Caches.CoreDistributedCache.csproj | 6 +- CoreDistributedCache/default.build | 24 ++++ NHibernate.Caches.Everything.sln | 21 ++++ 16 files changed, 466 insertions(+), 17 deletions(-) create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs rename CoreDistributedCache/{NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs => NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs} (82%) create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs new file mode 100644 index 00000000..bd9d30e7 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: CLSCompliant(true)] +[assembly: AssemblyDelaySign(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs similarity index 82% rename from CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs rename to CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs index 6fa3b3b5..440af305 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/MemoryDistributedCacheFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs @@ -4,15 +4,15 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; -namespace NHibernate.Caches.CoreDistributedCache +namespace NHibernate.Caches.CoreDistributedCache.Memory { /// /// A memory "distributed" cache factory. Use for testing purpose. Otherwise consider using CoreMemoryCache /// instead. /// - public class MemoryDistributedCacheFactory : IDistributedCacheFactory + public class MemoryFactory : IDistributedCacheFactory { - private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemoryDistributedCacheFactory)); + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemoryFactory)); private const string _expirationScanFrequency = "expiration-scan-frequency"; private const string _sizeLimit = "size-limit"; @@ -21,7 +21,7 @@ public class MemoryDistributedCacheFactory : IDistributedCacheFactory /// /// Default constructor. /// - public MemoryDistributedCacheFactory() : this(null) + public MemoryFactory() : this(null) { } @@ -30,7 +30,7 @@ public MemoryDistributedCacheFactory() : this(null) /// /// See . /// See . - public MemoryDistributedCacheFactory(TimeSpan? expirationScanFrequency, long? sizeLimit) + public MemoryFactory(TimeSpan? expirationScanFrequency, long? sizeLimit) { var options = new Options(); if (expirationScanFrequency.HasValue) @@ -54,7 +54,7 @@ public MemoryDistributedCacheFactory(TimeSpan? expirationScanFrequency, long? si /// /// size-limit has to be an integer, expressing the size limit in bytes. /// - public MemoryDistributedCacheFactory(IDictionary properties) + public MemoryFactory(IDictionary properties) { var options = new Options(); @@ -68,9 +68,10 @@ public MemoryDistributedCacheFactory(IDictionary properties) options.ExpirationScanFrequency = TimeSpan.FromMinutes(minutes); else if (TimeSpan.TryParse(esf, out var expirationScanFrequency)) options.ExpirationScanFrequency = expirationScanFrequency; - Log.Warn( - "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.", - esf, _expirationScanFrequency); + else + Log.Warn( + "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.", + esf, _expirationScanFrequency); } else { @@ -84,9 +85,10 @@ public MemoryDistributedCacheFactory(IDictionary properties) { if (long.TryParse(sl, out var bytes)) options.SizeLimit = bytes; - Log.Warn( - "Invalid value '{0}' for {1} setting: it is not an integer. Ignoring.", - sl, _sizeLimit); + else + Log.Warn( + "Invalid value '{0}' for {1} setting: it is not an integer. Ignoring.", + sl, _sizeLimit); } else { @@ -104,6 +106,8 @@ public IDistributedCache BuildCache() // Always yields the same instance: its underlying implementation is a MemoryCache which regularly spawn // a background task for expiring items. This avoids creating many instances, thus avoiding potentially // spawning many such background tasks at once. + // This also allows to share the cache between all session factories of a process, thus emulating a + // distributed aspect. return _cache; } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj new file mode 100644 index 00000000..332ddb1e --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj @@ -0,0 +1,36 @@ + + + + NHibernate.Caches.CoreDistributedCache.Memory + NHibernate.Caches.CoreDistributedCache.Memory + Memory cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Redis). +Meant for testing purpose, consider NHibernate.Caches.CoreMemoryCache for other usages. + + net461;netstandard2.0 + $(NoWarn);3001;3002 + True + ..\..\NHibernate.Caches.snk + * New feature + * #28 - Add a .Net Core DistributedCache + + + NETFX;$(DefineConstants) + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs new file mode 100644 index 00000000..bd9d30e7 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: CLSCompliant(true)] +[assembly: AssemblyDelaySign(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj new file mode 100644 index 00000000..e733961e --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj @@ -0,0 +1,35 @@ + + + + NHibernate.Caches.CoreDistributedCache.Redis + NHibernate.Caches.CoreDistributedCache.Redis + Redis cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Redis). + + net461;netstandard2.0 + $(NoWarn);3001;3002 + True + ..\..\NHibernate.Caches.snk + * New feature + * #28 - Add a .Net Core DistributedCache + + + NETFX;$(DefineConstants) + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs new file mode 100644 index 00000000..82078b60 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Redis; + +namespace NHibernate.Caches.CoreDistributedCache.Redis +{ + /// + /// A Redis distributed cache factory. See . + /// + public class RedisFactory : IDistributedCacheFactory + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(RedisFactory)); + private const string _configuration = "configuration"; + private const string _instanceName = "instance-name"; + + private readonly RedisCacheOptions _options; + + /// + /// Constructor with explicit configuration properties. + /// + /// See . + /// See . + public RedisFactory(string configuration, string instanceName) + { + _options = new RedisCacheOptions + { + Configuration = configuration, + InstanceName = instanceName + }; + } + + /// + /// Constructor with configuration properties. It supports configuration and + /// instance-name properties. See and + /// . + /// + /// The configurations properties. + public RedisFactory(IDictionary properties) + { + _options = new RedisCacheOptions(); + + if (properties == null) + return; + + if (properties.TryGetValue(_configuration, out var configuration)) + { + _options.Configuration = configuration; + Log.Info("Configuration set as '{0}'", configuration); + } + else + { + // Configuration is supposed to be mandatory. + Log.Warn("No {0} property provided", _configuration); + } + + if (properties.TryGetValue(_instanceName, out var instanceName)) + { + _options.InstanceName = instanceName; + Log.Info("InstanceName set as '{0}'", instanceName); + } + else + Log.Info("No {0} property provided", _instanceName); + } + + /// + public IDistributedCache BuildCache() + { + // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface + // (see its paragraph end note) there is no need for a singleton lifetime. + return new RedisCache(_options); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs new file mode 100644 index 00000000..bd9d30e7 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: CLSCompliant(true)] +[assembly: AssemblyDelaySign(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj new file mode 100644 index 00000000..0c81c9df --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj @@ -0,0 +1,35 @@ + + + + NHibernate.Caches.CoreDistributedCache.SqlServer + NHibernate.Caches.CoreDistributedCache.SqlServer + SQL Server cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.SqlServer). + + net461;netstandard2.0 + $(NoWarn);3001;3002 + True + ..\..\NHibernate.Caches.snk + * New feature + * #28 - Add a .Net Core DistributedCache + + + NETFX;$(DefineConstants) + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs new file mode 100644 index 00000000..bec4a4e1 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.SqlServer; + +namespace NHibernate.Caches.CoreDistributedCache.SqlServer +{ + /// + /// A Redis distributed cache factory. See . + /// + public class SqlServerFactory : IDistributedCacheFactory + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(SqlServerFactory)); + private const string _connectionString = "connection-string"; + private const string _schemaName = "schema-name"; + private const string _tableName = "table-name"; + private const string _expiredItemsDeletionInterval = "expired-items-deletion-interval"; + + private readonly SqlServerCacheOptions _options; + + /// + /// Constructor with explicit configuration properties. + /// + /// See . + /// See . + /// See . + /// See . + public SqlServerFactory( + string connectionString, string schemaName, string tableName, TimeSpan? expiredItemsDeletionInterval) + { + _options = new SqlServerCacheOptions + { + ConnectionString = connectionString, + SchemaName = schemaName, + TableName = tableName, + ExpiredItemsDeletionInterval = expiredItemsDeletionInterval + }; + } + + /// + /// Constructor with configuration properties. It supports connection-string, schema-name, + /// table-name and expired-items-deletion-interval properties. + /// See . + /// + /// The configurations properties. + /// + /// If expired-items-deletion-interval is provided as an integer, this integer will be used as a number + /// of minutes. Otherwise the setting will be parsed as a . + /// + public SqlServerFactory(IDictionary properties) + { + _options = new SqlServerCacheOptions(); + + if (properties == null) + return; + + if (properties.TryGetValue(_connectionString, out var connectionString)) + { + _options.ConnectionString = connectionString; + Log.Info("ConnectionString set as '{0}'", connectionString); + } + else + Log.Warn("No {0} property provided", _connectionString); + + if (properties.TryGetValue(_schemaName, out var schemaName)) + { + _options.SchemaName = schemaName; + Log.Info("SchemaName set as '{0}'", schemaName); + } + else + Log.Warn("No {0} property provided", _schemaName); + + if (properties.TryGetValue(_tableName, out var tableName)) + { + _options.TableName = tableName; + Log.Info("TableName set as '{0}'", tableName); + } + else + Log.Warn("No {0} property provided", _tableName); + + if (properties.TryGetValue(_expiredItemsDeletionInterval, out var eidi)) + { + if (eidi != null) + { + if (int.TryParse(eidi, out var minutes)) + _options.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(minutes); + else if (TimeSpan.TryParse(eidi, out var expirationScanFrequency)) + _options.ExpiredItemsDeletionInterval = expirationScanFrequency; + else + Log.Warn( + "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.", + eidi, _expiredItemsDeletionInterval); + } + else + { + Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _expiredItemsDeletionInterval); + } + } + } + + /// + public IDistributedCache BuildCache() + { + // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface + // (see its paragraph end note) there is no need for a singleton lifetime. + return new SqlServerCache(_options); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config index a3a4d4e2..cfae39fc 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config @@ -6,7 +6,7 @@ type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> - + 00:10:00 1048576 diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs index 157cdcab..6eb44138 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs @@ -27,6 +27,7 @@ using Microsoft.Extensions.Caching.Memory; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; +using NHibernate.Caches.CoreDistributedCache.Memory; using NUnit.Framework; namespace NHibernate.Caches.CoreDistributedCache.Tests @@ -48,7 +49,7 @@ public void ConfiguredCacheFactory() { var factory = CoreDistributedCacheProvider.CacheFactory; Assert.That(factory, Is.Not.Null, "Factory not found"); - Assert.That(factory, Is.InstanceOf(), "Unexpected factory"); + Assert.That(factory, Is.InstanceOf(), "Unexpected factory"); var cache1 = factory.BuildCache(); Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs new file mode 100644 index 00000000..d8e97925 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs @@ -0,0 +1,93 @@ +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.Caching.Redis; +using Microsoft.Extensions.Caching.SqlServer; +using NHibernate.Caches.CoreDistributedCache.Redis; +using NHibernate.Caches.CoreDistributedCache.SqlServer; +using NUnit.Framework; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + [TestFixture] + public class DistributedCacheFactoryFixture + { + private static readonly FieldInfo RedisCacheOptionsField = + typeof(RedisFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly FieldInfo SqlServerCacheOptionsField = + typeof(SqlServerFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); + + [Test] + public void RedisCacheFactory() + { + var factory = + new RedisFactory(new Dictionary + { + { "configuration", "config" }, + { "instance-name", "instance" } + }); + var cache1 = factory.BuildCache(); + Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); + Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); + var cache2 = factory.BuildCache(); + Assert.That(cache2, Is.Not.EqualTo(cache1), + "The Redis cache factory is supposed to always yield a new instance"); + + var options = RedisCacheOptionsField.GetValue(factory); + Assert.That(options, Is.Not.Null, "Factory cache options not found"); + Assert.That(options, Is.InstanceOf(), "Unexpected options type"); + var redisOptions = (RedisCacheOptions) options; + Assert.That(redisOptions.Configuration, Is.EqualTo("config")); + Assert.That(redisOptions.InstanceName, Is.EqualTo("instance")); + } + + [Test] + public void SqlServerCacheFactory() + { + var factory = new SqlServerFactory(new Dictionary + { + { "connection-string", "connection" }, + { "schema-name", "schema" }, + { "table-name", "table" }, + { "expired-items-deletion-interval", "5" } + }); + var cache1 = factory.BuildCache(); + Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); + Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); + var cache2 = factory.BuildCache(); + Assert.That(cache2, Is.Not.EqualTo(cache1), + "The SQL Server cache factory is supposed to always yield a new instance"); + + var options = SqlServerCacheOptionsField.GetValue(factory); + Assert.That(options, Is.Not.Null, "Factory cache options not found"); + Assert.That(options, Is.InstanceOf(), "Unexpected options type"); + var sqlServerOptions = (SqlServerCacheOptions) options; + Assert.That(sqlServerOptions.ConnectionString, Is.EqualTo("connection")); + Assert.That(sqlServerOptions.SchemaName, Is.EqualTo("schema")); + Assert.That(sqlServerOptions.TableName, Is.EqualTo("table")); + Assert.That(sqlServerOptions.ExpiredItemsDeletionInterval, Is.EqualTo(TimeSpan.FromMinutes(5))); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj index 970a4c7e..fa0ca85a 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj @@ -15,6 +15,9 @@ + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj index 84ae324e..b6baf4ef 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj @@ -3,7 +3,8 @@ NHibernate.Caches.CoreDistributedCache NHibernate.Caches.CoreDistributedCache - Cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Abstractions). + Cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Abstractions). +This provider is not bound to a specific implementation and require a cache factory yielding IDistributedCache implementations. net461;netstandard2.0 $(NoWarn);3001;3002 @@ -22,7 +23,6 @@ - @@ -33,4 +33,4 @@ ./NHibernate.Caches.license.txt - \ No newline at end of file + diff --git a/CoreDistributedCache/default.build b/CoreDistributedCache/default.build index 8fa8b61e..cf11a763 100644 --- a/CoreDistributedCache/default.build +++ b/CoreDistributedCache/default.build @@ -23,10 +23,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NHibernate.Caches.Everything.sln b/NHibernate.Caches.Everything.sln index a93f420a..7f9737cd 100644 --- a/NHibernate.Caches.Everything.sln +++ b/NHibernate.Caches.Everything.sln @@ -60,6 +60,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistr 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +156,18 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -176,5 +194,8 @@ Global {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} EndGlobalSection EndGlobal From 58e0110e696c2ed65d47eac77e3414ca56128871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Thu, 22 Mar 2018 19:57:03 +0100 Subject: [PATCH 4/8] Support cache implementations having a max key length --- .../MemoryFactory.cs | 3 + .../RedisFactory.cs | 3 + .../SqlServerFactory.cs | 3 + .../Async/CoreDistributedCacheFixture.cs | 59 +++++++++++++++++++ .../CoreDistributedCacheFixture.cs | 16 ++++- ...e.Caches.CoreDistributedCache.Tests.csproj | 1 + .../TestsContext.cs | 17 +++--- .../Async/CoreDistributedCache.cs | 2 + .../CoreDistributedCache.cs | 41 ++++++++++++- .../CoreDistributedCacheProvider.cs | 2 +- .../IDistributedCacheFactory.cs | 6 ++ .../TestsContext.cs | 17 +++--- 12 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs index 440af305..af4a6f9e 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs @@ -111,6 +111,9 @@ public IDistributedCache BuildCache() return _cache; } + /// + public int? MaxKeySize => null; + private class Options : MemoryDistributedCacheOptions, IOptions { MemoryDistributedCacheOptions IOptions.Value => this; diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs index 82078b60..539e376f 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs @@ -62,6 +62,9 @@ public RedisFactory(IDictionary properties) Log.Info("No {0} property provided", _instanceName); } + /// + public int? MaxKeySize => null; + /// public IDistributedCache BuildCache() { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs index bec4a4e1..de611b68 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs @@ -98,6 +98,9 @@ public SqlServerFactory(IDictionary properties) } } + /// + public int? MaxKeySize => 449; + /// public IDistributedCache BuildCache() { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs new file mode 100644 index 00000000..2b57998d --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.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. +// +//------------------------------------------------------------------------------ + + +#region License + +// +// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#endregion + +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; +using NHibernate.Cache; +using NHibernate.Caches.Common.Tests; +using NUnit.Framework; +using NSubstitute; + +namespace NHibernate.Caches.CoreDistributedCache.Tests +{ + using System.Threading.Tasks; + using System.Threading; + public partial class CoreDistributedCacheFixture : CacheFixture + { + + [Test] + public async Task MaxKeySizeAsync() + { + var distribCache = Substitute.For(); + const int maxLength = 20; + var cache = new CoreDistributedCache(distribCache, maxLength, "foo", new Dictionary()); + await (cache.PutAsync(new string('k', maxLength * 2), "test", CancellationToken.None)); + await (distribCache.Received().SetAsync(Arg.Is(k => k.Length <= maxLength), Arg.Any(), + Arg.Any())); + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs index 33c750ac..c54da27c 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs @@ -21,19 +21,33 @@ #endregion using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Distributed; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; using NUnit.Framework; +using NSubstitute; namespace NHibernate.Caches.CoreDistributedCache.Tests { [TestFixture] - public class CoreDistributedCacheFixture : CacheFixture + public partial class CoreDistributedCacheFixture : CacheFixture { protected override bool SupportsSlidingExpiration => true; protected override bool SupportsClear => false; protected override Func ProviderBuilder => () => new CoreDistributedCacheProvider(); + + [Test] + public void MaxKeySize() + { + var distribCache = Substitute.For(); + const int maxLength = 20; + var cache = new CoreDistributedCache(distribCache, maxLength, "foo", new Dictionary()); + cache.Put(new string('k', maxLength * 2), "test"); + distribCache.Received().Set(Arg.Is(k => k.Length <= maxLength), Arg.Any(), + Arg.Any()); + } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj index fa0ca85a..411d8c14 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj @@ -23,6 +23,7 @@ + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs index dc8ff7f0..669478e6 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs @@ -1,6 +1,7 @@ -#if !NETFX -using log4net.Repository.Hierarchy; +using log4net.Repository.Hierarchy; +#if !NETFX using NHibernate.Caches.Common.Tests; +#endif using NUnit.Framework; namespace NHibernate.Caches.CoreDistributedCache.Tests @@ -11,20 +12,19 @@ public class TestsContext [OneTimeSetUp] public void RunBeforeAnyTests() { +#if !NETFX TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "coredistributedcache"); - //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" - //so we need to explicitly load the configuration - if (TestsContextHelper.ExecutingWithVsTest) - { - ConfigureLog4Net(); - } +#endif + ConfigureLog4Net(); } +#if !NETFX [OneTimeTearDown] public void RunAfterAnyTests() { TestsContextHelper.RunAfterAnyTests(); } +#endif private static void ConfigureLog4Net() { @@ -40,4 +40,3 @@ private static void ConfigureLog4Net() } } } -#endif diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs index 8e39df04..13eb410a 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs @@ -13,6 +13,8 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; using Microsoft.Extensions.Caching.Distributed; using NHibernate.Util; diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs index 60ec79a5..433abb48 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs @@ -25,6 +25,8 @@ using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; +using System.Security.Cryptography; +using System.Text; using Microsoft.Extensions.Caching.Distributed; using NHibernate.Util; @@ -44,6 +46,7 @@ public partial class CoreDistributedCache : ICache private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(CoreDistributedCache)); private readonly IDistributedCache _cache; + private readonly int? _maxKeySize; private static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(300); private const bool _defaultUseSlidingExpiration = false; @@ -51,11 +54,13 @@ public partial class CoreDistributedCache : ICache private const string _cacheKeyPrefix = "NHibernate-Cache:"; private string _fullRegion; + private bool _hasWarnedOnHashLength; /// /// Default constructor. /// /// The instance to use. + /// If key size is limited, the maximal key size. /// The region of the cache. /// Cache configuration properties. /// @@ -68,9 +73,13 @@ public partial class CoreDistributedCache : ICache /// All parameters are optional. The defaults are an expiration of 300 seconds, no sliding expiration and no prefix. /// /// The "expiration" property could not be parsed. - public CoreDistributedCache(IDistributedCache cache, string region, IDictionary properties) + public CoreDistributedCache( + IDistributedCache cache, int? maxKeySize, string region, IDictionary properties) { + if (maxKeySize.HasValue && maxKeySize <= 0) + throw new ArgumentException($"{nameof(maxKeySize)} must be null or superior to 1.", nameof(maxKeySize)); _cache = cache; + _maxKeySize = maxKeySize; RegionName = region; Configure(properties); } @@ -161,7 +170,35 @@ private static bool GetUseSlidingExpiration(IDictionary props) private string GetCacheKey(object key) { - return string.Concat(_cacheKeyPrefix, _fullRegion, ":", key.ToString(), "@", key.GetHashCode()); + var keyAsString = string.Concat(_cacheKeyPrefix, _fullRegion, ":", key.ToString(), "@", key.GetHashCode()); + if (!_maxKeySize.HasValue || _maxKeySize >= keyAsString.Length) + return keyAsString; + + Log.Info( + "Computing a hashed key for too long key '{0}'. This may cause collisions resulting into additional cache misses.", + key); + // Hash it for respecting max key size. Collisions will be avoided by storing the actual key along + // the object and comparing it on retrieval. + using (var hasher = new SHA256Managed()) + { + var bytes = Encoding.UTF8.GetBytes(keyAsString); + var computedHash = Convert.ToBase64String(hasher.ComputeHash(bytes)); + if (computedHash.Length <= _maxKeySize) + return computedHash; + + if (!_hasWarnedOnHashLength) + { + // No lock for this field, some redundant logs will be less harm than locking. + _hasWarnedOnHashLength = true; + Log.Warn( + "Hash computed for too long keys are themselves too long. They will be truncated, further " + + "increasing the risk of collision resulting into additional cache misses. Consider using a " + + "cache supporting longer keys. Hash length: {0}; max key size: {1}", + computedHash.Length, _maxKeySize); + } + + return computedHash.Substring(0, _maxKeySize.Value); + } } /// diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs index 99747d8a..5425a57b 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs @@ -138,7 +138,7 @@ public ICache BuildCache(string regionName, IDictionary properti Log.Debug("building cache with region: {0}, properties: {1}, factory: {2}" , regionName, sb.ToString(), CacheFactory.GetType().FullName); } - return new CoreDistributedCache(CacheFactory.BuildCache(), regionName, properties); + return new CoreDistributedCache(CacheFactory.BuildCache(), CacheFactory.MaxKeySize, regionName, properties); } /// diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs index 4546d1de..902ec753 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs @@ -12,5 +12,11 @@ public interface IDistributedCacheFactory /// /// A instance. IDistributedCache BuildCache(); + + /// + /// If the underlying implementation has a limit on key size, + /// its maximal size. otherwise. + /// + int? MaxKeySize { get; } } } diff --git a/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs b/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs index b31c6db5..263aeb9a 100644 --- a/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs +++ b/CoreMemoryCache/NHibernate.Caches.CoreMemoryCache.Tests/TestsContext.cs @@ -1,6 +1,7 @@ -#if !NETFX -using log4net.Repository.Hierarchy; +using log4net.Repository.Hierarchy; +#if !NETFX using NHibernate.Caches.Common.Tests; +#endif using NUnit.Framework; namespace NHibernate.Caches.CoreMemoryCache.Tests @@ -11,20 +12,19 @@ public class TestsContext [OneTimeSetUp] public void RunBeforeAnyTests() { +#if !NETFX TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "corememorycache"); - //When .NET Core App 2.0 tests run from VS/VSTest the entry assembly is "testhost.dll" - //so we need to explicitly load the configuration - if (TestsContextHelper.ExecutingWithVsTest) - { - ConfigureLog4Net(); - } +#endif + ConfigureLog4Net(); } +#if !NETFX [OneTimeTearDown] public void RunAfterAnyTests() { TestsContextHelper.RunAfterAnyTests(); } +#endif private static void ConfigureLog4Net() { @@ -40,4 +40,3 @@ private static void ConfigureLog4Net() } } } -#endif From 1a6ece40529a40951e198dd436051c5d6e53409c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Fri, 23 Mar 2018 18:07:27 +0100 Subject: [PATCH 5/8] Add a Memcached distributed cache factory --- .../AssemblyInfo.cs | 5 + .../MemcachedFactory.cs | 156 ++++++++++++++++++ ...ches.CoreDistributedCache.Memcached.csproj | 34 ++++ .../MemoryFactory.cs | 2 +- .../RedisFactory.cs | 2 +- .../SqlServerFactory.cs | 2 +- .../App.config | 19 +++ .../Async/CoreDistributedCacheFixture.cs | 15 +- .../CoreDistributedCacheFixture.cs | 15 +- .../CoreDistributedCacheProviderFixture.cs | 34 ---- .../DistributedCacheFactoryFixture.cs | 82 ++++++++- ...e.Caches.CoreDistributedCache.Tests.csproj | 1 + .../CacheConstraints.cs | 34 ++++ .../CoreDistributedCache.cs | 66 ++++---- .../CoreDistributedCacheProvider.cs | 2 +- .../IDistributedCacheFactory.cs | 6 +- ...bernate.Caches.CoreDistributedCache.csproj | 2 +- CoreDistributedCache/default.build | 8 + NHibernate.Caches.Everything.sln | 7 + 19 files changed, 416 insertions(+), 76 deletions(-) create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj create mode 100644 CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConstraints.cs diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs new file mode 100644 index 00000000..bd9d30e7 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System; +using System.Reflection; + +[assembly: CLSCompliant(true)] +[assembly: AssemblyDelaySign(false)] \ No newline at end of file diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs new file mode 100644 index 00000000..a8f0e78b --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using Enyim.Caching; +using Enyim.Caching.Configuration; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace NHibernate.Caches.CoreDistributedCache.Memcached +{ + /// + /// A Memcached distributed cache factory. + /// + public class MemcachedFactory : IDistributedCacheFactory + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemcachedFactory)); + private const string _configuration = "configuration"; + + private readonly IDistributedCache _cache; + + /// + /// Constructor with configuration properties. It supports configuration, which has to be a JSON string + /// structured like the value part of the "enyimMemcached" property in an appsettings.json file. + /// + /// The configurations properties. + public MemcachedFactory(IDictionary properties) : this() + { + MemcachedClientOptions options; + if (properties != null && properties.TryGetValue(_configuration, out var configuration) && !string.IsNullOrWhiteSpace(configuration)) + { + options = JsonConvert.DeserializeObject(configuration); + } + else + { + Log.Warn("No {0} property provided", _configuration); + options = new MemcachedClientOptions(); + } + + var loggerFactory = new LoggerFactory(); + + _cache = new MemcachedClient(loggerFactory, new MemcachedClientConfiguration(loggerFactory, options)); + } + + private MemcachedFactory() + { + Constraints = new CacheConstraints + { + MaxKeySize = 250, + KeySanitizer = SanitizeKey + }; + } + + // According to https://groups.google.com/forum/#!topic/memcached/Tz1RE0FUbNA, + // memcached key can't contain space, newline, return, tab, vertical tab or form feed. + // Since keys contains entity identifiers which may be anything, purging them all. + private static readonly char[] ForbiddenChar = new [] { ' ', '\n', '\r', '\t', '\v', '\f' }; + + private static string SanitizeKey(string key) + { + foreach (var forbidden in ForbiddenChar) + { + key = key.Replace(forbidden, '-'); + } + return key; + } + + /// + public CacheConstraints Constraints { get; } + + /// + public IDistributedCache BuildCache() + { + return _cache; + } + + private class LoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory + { + public void Dispose() + { + } + + public ILogger CreateLogger(string categoryName) + { + return new LoggerWrapper(NHibernateLogger.For(categoryName)); + } + + public void AddProvider(ILoggerProvider provider) + { + } + } + + private class LoggerWrapper : ILogger + { + private readonly INHibernateLogger _logger; + + public LoggerWrapper(INHibernateLogger logger) + { + _logger = logger; + } + + void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + { + if (!IsEnabled(logLevel)) + return; + + if (formatter == null) + throw new ArgumentNullException(nameof(formatter)); + + _logger.Log( + TranslateLevel(logLevel), + new NHibernateLogValues("EventId {0}: {1}", new object[] { eventId, formatter(state, exception) }), + // Avoid double logging of exception by not providing it to the logger, but only to the formatter. + null); + } + + public bool IsEnabled(LogLevel logLevel) + => _logger.IsEnabled(TranslateLevel(logLevel)); + + public IDisposable BeginScope(TState state) + => NoopScope.Instance; + + private NHibernateLogLevel TranslateLevel(LogLevel level) + { + switch (level) + { + case LogLevel.None: + return NHibernateLogLevel.None; + case LogLevel.Trace: + return NHibernateLogLevel.Trace; + case LogLevel.Debug: + return NHibernateLogLevel.Debug; + case LogLevel.Information: + return NHibernateLogLevel.Info; + case LogLevel.Warning: + return NHibernateLogLevel.Warn; + case LogLevel.Error: + return NHibernateLogLevel.Error; + case LogLevel.Critical: + return NHibernateLogLevel.Fatal; + } + + return NHibernateLogLevel.Trace; + } + + private class NoopScope : IDisposable + { + public static readonly NoopScope Instance = new NoopScope(); + + public void Dispose() + { + } + } + } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj new file mode 100644 index 00000000..259ab8b0 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj @@ -0,0 +1,34 @@ + + + + NHibernate.Caches.CoreDistributedCache.Memcached + NHibernate.Caches.CoreDistributedCache.Memcached + Memcached cache provider for NHibernate using .Net Core IDistributedCache (EnyimMemcachedCore). + + net461;netstandard2.0 + $(NoWarn);3001;3002 + False + * New feature + * #28 - Add a .Net Core DistributedCache + + + NETFX;$(DefineConstants) + + + + + + + + + + ./NHibernate.Caches.readme.md + + + ./NHibernate.Caches.license.txt + + + + + + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs index af4a6f9e..7d400c46 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs @@ -112,7 +112,7 @@ public IDistributedCache BuildCache() } /// - public int? MaxKeySize => null; + public CacheConstraints Constraints => null; private class Options : MemoryDistributedCacheOptions, IOptions { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs index 539e376f..69f08d20 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs @@ -63,7 +63,7 @@ public RedisFactory(IDictionary properties) } /// - public int? MaxKeySize => null; + public CacheConstraints Constraints => null; /// public IDistributedCache BuildCache() diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs index de611b68..08fc3e6b 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs @@ -99,7 +99,7 @@ public SqlServerFactory(IDictionary properties) } /// - public int? MaxKeySize => 449; + public CacheConstraints Constraints { get; } = new CacheConstraints { MaxKeySize = 449 }; /// public IDistributedCache BuildCache() diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config index cfae39fc..4a04862b 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config @@ -6,6 +6,7 @@ type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> + 00:10:00 @@ -14,6 +15,24 @@ + + + NHibernate.Connection.DriverConnectionProvider diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs index 2b57998d..0da5ea55 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs @@ -50,10 +50,23 @@ public async Task MaxKeySizeAsync() { var distribCache = Substitute.For(); const int maxLength = 20; - var cache = new CoreDistributedCache(distribCache, maxLength, "foo", new Dictionary()); + var cache = new CoreDistributedCache(distribCache, new CacheConstraints { MaxKeySize = maxLength }, "foo", + new Dictionary()); await (cache.PutAsync(new string('k', maxLength * 2), "test", CancellationToken.None)); await (distribCache.Received().SetAsync(Arg.Is(k => k.Length <= maxLength), Arg.Any(), Arg.Any())); } + + [Test] + public async Task KeySanitizerAsync() + { + var distribCache = Substitute.For(); + Func keySanitizer = s => s.Replace('a', 'b'); + var cache = new CoreDistributedCache(distribCache, new CacheConstraints { KeySanitizer = keySanitizer }, "foo", + new Dictionary()); + await (cache.PutAsync("-abc-", "test", CancellationToken.None)); + await (distribCache.Received().SetAsync(Arg.Is(k => k.Contains(keySanitizer("-abc-"))), Arg.Any(), + Arg.Any())); + } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs index c54da27c..0ba6ec7e 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs @@ -44,10 +44,23 @@ public void MaxKeySize() { var distribCache = Substitute.For(); const int maxLength = 20; - var cache = new CoreDistributedCache(distribCache, maxLength, "foo", new Dictionary()); + var cache = new CoreDistributedCache(distribCache, new CacheConstraints { MaxKeySize = maxLength }, "foo", + new Dictionary()); cache.Put(new string('k', maxLength * 2), "test"); distribCache.Received().Set(Arg.Is(k => k.Length <= maxLength), Arg.Any(), Arg.Any()); } + + [Test] + public void KeySanitizer() + { + var distribCache = Substitute.For(); + Func keySanitizer = s => s.Replace('a', 'b'); + var cache = new CoreDistributedCache(distribCache, new CacheConstraints { KeySanitizer = keySanitizer }, "foo", + new Dictionary()); + cache.Put("-abc-", "test"); + distribCache.Received().Set(Arg.Is(k => k.Contains(keySanitizer("-abc-"))), Arg.Any(), + Arg.Any()); + } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs index 6eb44138..391336e7 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs @@ -22,12 +22,8 @@ using System; using System.Collections.Generic; -using System.Reflection; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Caching.Memory; using NHibernate.Cache; using NHibernate.Caches.Common.Tests; -using NHibernate.Caches.CoreDistributedCache.Memory; using NUnit.Framework; namespace NHibernate.Caches.CoreDistributedCache.Tests @@ -38,36 +34,6 @@ public class CoreDistributedCacheProviderFixture : CacheProviderFixture protected override Func ProviderBuilder => () => new CoreDistributedCacheProvider(); - private static readonly FieldInfo MemoryCacheField = - typeof(MemoryDistributedCache).GetField("_memCache", BindingFlags.Instance | BindingFlags.NonPublic); - - private static readonly FieldInfo CacheOptionsField = - typeof(MemoryCache).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); - - [Test] - public void ConfiguredCacheFactory() - { - var factory = CoreDistributedCacheProvider.CacheFactory; - Assert.That(factory, Is.Not.Null, "Factory not found"); - Assert.That(factory, Is.InstanceOf(), "Unexpected factory"); - var cache1 = factory.BuildCache(); - Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); - Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); - var cache2 = factory.BuildCache(); - Assert.That(cache2, Is.EqualTo(cache1), - "The distributed cache factory is supposed to always yield the same instance"); - - var memCache = MemoryCacheField.GetValue(cache1); - Assert.That(memCache, Is.Not.Null, "Underlying memory cache not found"); - Assert.That(memCache, Is.InstanceOf(), "Unexpected memory cache"); - var options = CacheOptionsField.GetValue(memCache); - Assert.That(options, Is.Not.Null, "Memory cache options not found"); - Assert.That(options, Is.InstanceOf(), "Unexpected options type"); - var memOptions = (MemoryCacheOptions) options; - Assert.That(memOptions.ExpirationScanFrequency, Is.EqualTo(TimeSpan.FromMinutes(10))); - Assert.That(memOptions.SizeLimit, Is.EqualTo(1048576)); - } - [Test] public void TestBuildCacheFromConfig() { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs index d8e97925..adfcecf0 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs @@ -23,8 +23,13 @@ using System; using System.Collections.Generic; using System.Reflection; +using Enyim.Caching; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Redis; using Microsoft.Extensions.Caching.SqlServer; +using NHibernate.Caches.CoreDistributedCache.Memcached; +using NHibernate.Caches.CoreDistributedCache.Memory; using NHibernate.Caches.CoreDistributedCache.Redis; using NHibernate.Caches.CoreDistributedCache.SqlServer; using NUnit.Framework; @@ -34,10 +39,80 @@ namespace NHibernate.Caches.CoreDistributedCache.Tests [TestFixture] public class DistributedCacheFactoryFixture { + [Test] + public void MemcachedCacheFactory() + { + var factory = + new MemcachedFactory(new Dictionary + { + { + "configuration", @"{ + ""Servers"": [ + { + ""Address"": ""memcached"", + ""Port"": 11211 + } + ], + ""Authentication"": { + ""Type"": ""Enyim.Caching.Memcached.PlainTextAuthenticator"", + ""Parameters"": { + ""zone"": """", + ""userName"": ""username"", + ""password"": ""password"" + } + } +}" + } + }); + var cache1 = factory.BuildCache(); + Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); + Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); + var cache2 = factory.BuildCache(); + Assert.That(cache2, Is.EqualTo(cache1), + "The Memcached cache factory is supposed to always yield the same instance"); + + var keySanitizer = factory.Constraints?.KeySanitizer; + Assert.That(keySanitizer, Is.Not.Null, "Factory lacks a key sanitizer"); + Assert.That(keySanitizer("--abc \n\r\t\v\fdef--"), Is.EqualTo("--abc------def--"), "Unexpected key sanitization"); + } + + private static readonly FieldInfo MemoryCacheField = + typeof(MemoryDistributedCache).GetField("_memCache", BindingFlags.Instance | BindingFlags.NonPublic); + + private static readonly FieldInfo MemoryCacheOptionsField = + typeof(MemoryCache).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); + + [Test] + public void MemoryCacheFactory() + { + var factory = + new MemoryFactory(new Dictionary + { + { "expiration-scan-frequency", "00:10:00" }, + { "size-limit", "1048576" } + }); + Assert.That(factory, Is.Not.Null, "Factory not found"); + Assert.That(factory, Is.InstanceOf(), "Unexpected factory"); + var cache1 = factory.BuildCache(); + Assert.That(cache1, Is.Not.Null, "Factory has yielded null"); + Assert.That(cache1, Is.InstanceOf(), "Unexpected cache"); + var cache2 = factory.BuildCache(); + Assert.That(cache2, Is.EqualTo(cache1), + "The distributed cache factory is supposed to always yield the same instance"); + + var memCache = MemoryCacheField.GetValue(cache1); + Assert.That(memCache, Is.Not.Null, "Underlying memory cache not found"); + Assert.That(memCache, Is.InstanceOf(), "Unexpected memory cache"); + var options = MemoryCacheOptionsField.GetValue(memCache); + Assert.That(options, Is.Not.Null, "Memory cache options not found"); + Assert.That(options, Is.InstanceOf(), "Unexpected options type"); + var memOptions = (MemoryCacheOptions) options; + Assert.That(memOptions.ExpirationScanFrequency, Is.EqualTo(TimeSpan.FromMinutes(10))); + Assert.That(memOptions.SizeLimit, Is.EqualTo(1048576)); + } + private static readonly FieldInfo RedisCacheOptionsField = typeof(RedisFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); - private static readonly FieldInfo SqlServerCacheOptionsField = - typeof(SqlServerFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); [Test] public void RedisCacheFactory() @@ -63,6 +138,9 @@ public void RedisCacheFactory() Assert.That(redisOptions.InstanceName, Is.EqualTo("instance")); } + private static readonly FieldInfo SqlServerCacheOptionsField = + typeof(SqlServerFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic); + [Test] public void SqlServerCacheFactory() { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj index 411d8c14..8e0cb7ce 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConstraints.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConstraints.cs new file mode 100644 index 00000000..daf68994 --- /dev/null +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CacheConstraints.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.Caching.Distributed; + +namespace NHibernate.Caches.CoreDistributedCache +{ + /// + /// Constraints of the implementation. + /// + public class CacheConstraints + { + /// + /// If the underlying implementation has a limit on key size, + /// its maximal size, otherwise. + /// + public int? MaxKeySize { get; set; } + + /// + /// If the underlying implementation has constraints on what a key may contain, + /// a function sanitizing provided key, otherwise. + /// + /// + /// + /// If the sanitization function causes two different keys to be equal after sanitization, additional cache + /// misses may occur. (But yielded cached values will not be mixed: either the expected value or + /// will be yielded.) + /// + /// + /// If is also provided, the provided key will already respect it. The yielded value + /// will not be checked again for its maximal length. + /// + /// + public Func KeySanitizer { get; set; } + } +} diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs index 433abb48..33afd3f7 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs @@ -47,6 +47,7 @@ public partial class CoreDistributedCache : ICache private readonly IDistributedCache _cache; private readonly int? _maxKeySize; + private readonly Func _keySanitizer; private static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(300); private const bool _defaultUseSlidingExpiration = false; @@ -60,11 +61,11 @@ public partial class CoreDistributedCache : ICache /// Default constructor. /// /// The instance to use. - /// If key size is limited, the maximal key size. + /// Optional constraints of . /// The region of the cache. /// Cache configuration properties. /// - /// There are three (3) configurable parameters: + /// There are three (3) configurable parameters taken in : ///
    ///
  • expiration (or cache.default_expiration) = number of seconds to wait before expiring each item.
  • ///
  • cache.use_sliding_expiration = a boolean, true for resetting a cached item expiration each time it is accessed.
  • @@ -74,12 +75,14 @@ public partial class CoreDistributedCache : ICache /// /// The "expiration" property could not be parsed. public CoreDistributedCache( - IDistributedCache cache, int? maxKeySize, string region, IDictionary properties) + IDistributedCache cache, CacheConstraints constraints, string region, IDictionary properties) { - if (maxKeySize.HasValue && maxKeySize <= 0) - throw new ArgumentException($"{nameof(maxKeySize)} must be null or superior to 1.", nameof(maxKeySize)); + if (constraints?.MaxKeySize <= 0) + throw new ArgumentException($"{nameof(CacheConstraints.MaxKeySize)} must be null or superior to 1.", + nameof(constraints)); _cache = cache; - _maxKeySize = maxKeySize; + _maxKeySize = constraints?.MaxKeySize; + _keySanitizer = constraints?.KeySanitizer; RegionName = region; Configure(properties); } @@ -171,34 +174,37 @@ private static bool GetUseSlidingExpiration(IDictionary props) private string GetCacheKey(object key) { var keyAsString = string.Concat(_cacheKeyPrefix, _fullRegion, ":", key.ToString(), "@", key.GetHashCode()); - if (!_maxKeySize.HasValue || _maxKeySize >= keyAsString.Length) - return keyAsString; - - Log.Info( - "Computing a hashed key for too long key '{0}'. This may cause collisions resulting into additional cache misses.", - key); - // Hash it for respecting max key size. Collisions will be avoided by storing the actual key along - // the object and comparing it on retrieval. - using (var hasher = new SHA256Managed()) - { - var bytes = Encoding.UTF8.GetBytes(keyAsString); - var computedHash = Convert.ToBase64String(hasher.ComputeHash(bytes)); - if (computedHash.Length <= _maxKeySize) - return computedHash; - if (!_hasWarnedOnHashLength) + if (_maxKeySize < keyAsString.Length) + { + Log.Info( + "Computing a hashed key for too long key '{0}'. This may cause collisions resulting into additional cache misses.", + key); + // Hash it for respecting max key size. Collisions will be avoided by storing the actual key along + // the object and comparing it on retrieval. + using (var hasher = new SHA256Managed()) { - // No lock for this field, some redundant logs will be less harm than locking. - _hasWarnedOnHashLength = true; - Log.Warn( - "Hash computed for too long keys are themselves too long. They will be truncated, further " + - "increasing the risk of collision resulting into additional cache misses. Consider using a " + - "cache supporting longer keys. Hash length: {0}; max key size: {1}", - computedHash.Length, _maxKeySize); + var bytes = Encoding.UTF8.GetBytes(keyAsString); + var computedHash = Convert.ToBase64String(hasher.ComputeHash(bytes)); + if (computedHash.Length <= _maxKeySize) + return computedHash; + + if (!_hasWarnedOnHashLength) + { + // No lock for this field, some redundant logs will be less harm than locking. + _hasWarnedOnHashLength = true; + Log.Warn( + "Hash computed for too long keys are themselves too long. They will be truncated, further " + + "increasing the risk of collision resulting into additional cache misses. Consider using a " + + "cache supporting longer keys. Hash length: {0}; max key size: {1}", + computedHash.Length, _maxKeySize); + } + + keyAsString = computedHash.Substring(0, _maxKeySize.Value); } - - return computedHash.Substring(0, _maxKeySize.Value); } + + return _keySanitizer != null ? _keySanitizer(keyAsString) : keyAsString; } /// diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs index 5425a57b..e2b0087c 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs @@ -138,7 +138,7 @@ public ICache BuildCache(string regionName, IDictionary properti Log.Debug("building cache with region: {0}, properties: {1}, factory: {2}" , regionName, sb.ToString(), CacheFactory.GetType().FullName); } - return new CoreDistributedCache(CacheFactory.BuildCache(), CacheFactory.MaxKeySize, regionName, properties); + return new CoreDistributedCache(CacheFactory.BuildCache(), CacheFactory.Constraints, regionName, properties); } /// diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs index 902ec753..d6608ab6 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs @@ -14,9 +14,9 @@ public interface IDistributedCacheFactory IDistributedCache BuildCache(); /// - /// If the underlying implementation has a limit on key size, - /// its maximal size. otherwise. + /// If the underlying implementation has specific constraints, + /// its constraints, otherwise. /// - int? MaxKeySize { get; } + CacheConstraints Constraints { get; } } } diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj index b6baf4ef..3250be54 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj @@ -33,4 +33,4 @@ This provider is not bound to a specific implementation and require a cache fact ./NHibernate.Caches.license.txt - + \ No newline at end of file diff --git a/CoreDistributedCache/default.build b/CoreDistributedCache/default.build index cf11a763..d33d2bca 100644 --- a/CoreDistributedCache/default.build +++ b/CoreDistributedCache/default.build @@ -23,6 +23,8 @@ + + @@ -34,11 +36,17 @@ + + + + + + diff --git a/NHibernate.Caches.Everything.sln b/NHibernate.Caches.Everything.sln index 7f9737cd..e49cda03 100644 --- a/NHibernate.Caches.Everything.sln +++ b/NHibernate.Caches.Everything.sln @@ -66,6 +66,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHibernate.Caches.CoreDistr 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,6 +170,10 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -197,5 +203,6 @@ Global {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} EndGlobalSection EndGlobal From 4f319c93e41eec0b948cc3378cb5a1346297eb7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sat, 24 Mar 2018 13:10:24 +0100 Subject: [PATCH 6/8] Add sample test config for Redis and SqlServer --- .../App.config | 24 +++++++++++++++++++ ...e.Caches.CoreDistributedCache.Tests.csproj | 5 +++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config index 4a04862b..d51086a8 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config @@ -33,6 +33,30 @@ --> + + + + NHibernate.Connection.DriverConnectionProvider diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj index 8e0cb7ce..fd88b603 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj @@ -30,4 +30,7 @@ - \ No newline at end of file + + + + From 5a88fb6a80b75c18139650f37e07df1adcbbc638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Sun, 25 Mar 2018 14:51:21 +0200 Subject: [PATCH 7/8] Generate XML comment documentation for Core distributed caches Fixes some erroneous comments by the way. Handle cls non compliant cases one by one instead of globally disabling the warning. To be squashed, like all other commits of this PR. --- .../MemcachedFactory.cs | 1 + ...ibernate.Caches.CoreDistributedCache.Memcached.csproj | 2 +- .../MemoryFactory.cs | 9 +++++---- .../NHibernate.Caches.CoreDistributedCache.Memory.csproj | 2 +- .../NHibernate.Caches.CoreDistributedCache.Redis.csproj | 2 +- .../RedisFactory.cs | 4 +++- ...ibernate.Caches.CoreDistributedCache.SqlServer.csproj | 2 +- .../SqlServerFactory.cs | 1 + .../CoreDistributedCache.cs | 9 ++------- .../CoreDistributedCacheProvider.cs | 3 ++- .../CoreDistributedCacheSectionHandler.cs | 7 +------ .../IDistributedCacheFactory.cs | 4 +++- .../NHibernate.Caches.CoreDistributedCache.csproj | 4 ++-- 13 files changed, 24 insertions(+), 26 deletions(-) diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs index a8f0e78b..0d18e98f 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs @@ -68,6 +68,7 @@ private static string SanitizeKey(string key) public CacheConstraints Constraints { get; } /// + [CLSCompliant(false)] public IDistributedCache BuildCache() { return _cache; diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj index 259ab8b0..aafefe85 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj @@ -6,8 +6,8 @@ Memcached cache provider for NHibernate using .Net Core IDistributedCache (EnyimMemcachedCore). net461;netstandard2.0 - $(NoWarn);3001;3002 False + true * New feature * #28 - Add a .Net Core DistributedCache diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs index 7d400c46..65937ce2 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs @@ -28,8 +28,8 @@ public MemoryFactory() : this(null) /// /// Constructor with explicit configuration properties. /// - /// See . - /// See . + /// See . + /// See . public MemoryFactory(TimeSpan? expirationScanFrequency, long? sizeLimit) { var options = new Options(); @@ -43,8 +43,8 @@ public MemoryFactory(TimeSpan? expirationScanFrequency, long? sizeLimit) /// /// Constructor with configuration properties. It supports expiration-scan-frequency and - /// size-limit properties. See and - /// . + /// size-limit properties. See and + /// . /// /// The configurations properties. /// @@ -101,6 +101,7 @@ public MemoryFactory(IDictionary properties) } /// + [CLSCompliant(false)] public IDistributedCache BuildCache() { // Always yields the same instance: its underlying implementation is a MemoryCache which regularly spawn diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj index 332ddb1e..c9698ecc 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj @@ -7,9 +7,9 @@ Meant for testing purpose, consider NHibernate.Caches.CoreMemoryCache for other usages. net461;netstandard2.0 - $(NoWarn);3001;3002 True ..\..\NHibernate.Caches.snk + true * New feature * #28 - Add a .Net Core DistributedCache diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj index e733961e..3d945aac 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj @@ -6,9 +6,9 @@ Redis cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Redis). net461;netstandard2.0 - $(NoWarn);3001;3002 True ..\..\NHibernate.Caches.snk + true * New feature * #28 - Add a .Net Core DistributedCache diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs index 69f08d20..38418c4c 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Redis; @@ -66,6 +67,7 @@ public RedisFactory(IDictionary properties) public CacheConstraints Constraints => null; /// + [CLSCompliant(false)] public IDistributedCache BuildCache() { // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj index 0c81c9df..4af24545 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj @@ -6,9 +6,9 @@ SQL Server cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.SqlServer). net461;netstandard2.0 - $(NoWarn);3001;3002 True ..\..\NHibernate.Caches.snk + true * New feature * #28 - Add a .Net Core DistributedCache diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs index 08fc3e6b..1d688247 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs @@ -102,6 +102,7 @@ public SqlServerFactory(IDictionary properties) public CacheConstraints Constraints { get; } = new CacheConstraints { MaxKeySize = 449 }; /// + [CLSCompliant(false)] public IDistributedCache BuildCache() { // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs index 33afd3f7..1c89fcd6 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCache.cs @@ -33,14 +33,8 @@ namespace NHibernate.Caches.CoreDistributedCache { /// - /// Pluggable cache implementation using the Microsoft.Extensions.Caching.Memory classes. + /// Pluggable cache implementation using implementations. /// - /// - /// Priority is not configurable because it is un-usable: the compaction on memory pressure feature has been - /// removed from MemoryCache, only explicit compaction or size limit compaction may use priorities. But - /// API does not have a suitable method for triggering compaction, and size of each - /// cached entry has to be user provided, which API does not support. - /// public partial class CoreDistributedCache : ICache { private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(CoreDistributedCache)); @@ -74,6 +68,7 @@ public partial class CoreDistributedCache : ICache /// All parameters are optional. The defaults are an expiration of 300 seconds, no sliding expiration and no prefix. /// /// The "expiration" property could not be parsed. + [CLSCompliant(false)] public CoreDistributedCache( IDistributedCache cache, CacheConstraints constraints, string region, IDictionary properties) { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs index e2b0087c..a840b9e9 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheProvider.cs @@ -31,7 +31,7 @@ namespace NHibernate.Caches.CoreDistributedCache { /// - /// Cache provider using the System.Runtime.Caching classes + /// Cache provider using implementations. /// public class CoreDistributedCacheProvider : ICacheProvider { @@ -47,6 +47,7 @@ public class CoreDistributedCacheProvider : ICacheProvider /// /// Changes to this property affect only caches built after the change. /// + [CLSCompliant(false)] public static IDistributedCacheFactory CacheFactory { get; set; } static CoreDistributedCacheProvider() diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs index 20e32f35..11e0193b 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/CoreDistributedCacheSectionHandler.cs @@ -13,12 +13,7 @@ public class CoreDistributedCacheSectionHandler : IConfigurationSectionHandler #region IConfigurationSectionHandler Members - /// - /// Parse the config section. - /// - /// - /// - /// + /// /// A object. public object Create(object parent, object configContext, XmlNode section) { diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs index d6608ab6..be5d522d 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/IDistributedCacheFactory.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.Caching.Distributed; +using System; +using Microsoft.Extensions.Caching.Distributed; namespace NHibernate.Caches.CoreDistributedCache { /// /// Interface for factories building instances. /// + [CLSCompliant(false)] public interface IDistributedCacheFactory { /// diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj index 3250be54..268970f9 100644 --- a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj +++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.csproj @@ -7,9 +7,9 @@ This provider is not bound to a specific implementation and require a cache factory yielding IDistributedCache implementations. net461;netstandard2.0 - $(NoWarn);3001;3002 True ..\..\NHibernate.Caches.snk + true * New feature * #28 - Add a .Net Core DistributedCache @@ -33,4 +33,4 @@ This provider is not bound to a specific implementation and require a cache fact ./NHibernate.Caches.license.txt - \ No newline at end of file + From b5979573f5db626d6526f1309e4de1fa274ec41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 27 Mar 2018 12:19:27 +0200 Subject: [PATCH 8/8] Remove code obsoleted by rebase --- CoreDistributedCache/default.build | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/CoreDistributedCache/default.build b/CoreDistributedCache/default.build index d33d2bca..7c4b32bb 100644 --- a/CoreDistributedCache/default.build +++ b/CoreDistributedCache/default.build @@ -32,33 +32,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -