Skip to content

Commit

Permalink
Add monitor based sync only locker for ReadWrite cache (#2944)
Browse files Browse the repository at this point in the history
  • Loading branch information
bahusoid authored Feb 14, 2022
1 parent 00af788 commit b809f77
Show file tree
Hide file tree
Showing 16 changed files with 299 additions and 30 deletions.
20 changes: 20 additions & 0 deletions doc/reference/modules/configuration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,26 @@ var session = sessions.OpenSession(conn);
</para>
</entry>
</row>
<row>
<entry>
<literal>cache.read_write_lock_factory</literal>
</entry>
<entry>
Specify the cache lock factory to use for read-write cache regions.
Defaults to the built-in <literal>async</literal> cache lock factory.
<para>
<emphasis role="strong">eg.</emphasis>
<literal>async</literal>, or <literal>sync</literal>, or <literal>classname.of.CacheLockFactory, assembly</literal> with custom implementation of <literal>ICacheReadWriteLockFactory</literal>
</para>
<para>
<literal>async</literal> uses a single writer multiple readers locking mechanism supporting asynchronous operations.
</para>
<para>
<literal>sync</literal> uses a single access locking mechanism which will throw on asynchronous
operations but may have better performances than the <literal>async</literal> provider for applications using the .Net Framework (4.8 and below).
</para>
</entry>
</row>
<row>
<entry>
<literal>cache.region_prefix</literal>
Expand Down
2 changes: 2 additions & 0 deletions src/AsyncGenerator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@
- conversion: Ignore
anyBaseTypeRule: IsTestCase
executionPhase: PostProviders
- conversion: Ignore
name: SyncOnlyCacheFixture
ignoreDocuments:
- filePathEndsWith: Linq/MathTests.cs
- filePathEndsWith: Linq/ExpressionSessionLeakTest.cs
Expand Down
24 changes: 14 additions & 10 deletions src/NHibernate.Test/Async/CacheTest/CacheFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ namespace NHibernate.Test.CacheTest
{
using System.Threading.Tasks;
[TestFixture]
public class CacheFixtureAsync
public class CacheFixtureAsync: TestCase
{
[Test]
public async Task TestSimpleCacheAsync()
{
await (DoTestCacheAsync(new HashtableCacheProvider()));
}

private CacheKey CreateCacheKey(string text)
protected CacheKey CreateCacheKey(string text)
{
return new CacheKey(text, NHibernateUtil.String, "Foo", null, null);
}

public async Task DoTestCacheAsync(ICacheProvider cacheProvider, CancellationToken cancellationToken = default(CancellationToken))
{
var cache = cacheProvider.BuildCache(typeof(String).FullName, new Dictionary<string, string>());
var cache = (CacheBase) cacheProvider.BuildCache(typeof(String).FullName, new Dictionary<string, string>());

long longBefore = Timestamper.Next();

Expand All @@ -44,8 +44,7 @@ private CacheKey CreateCacheKey(string text)

await (Task.Delay(15, cancellationToken));

ICacheConcurrencyStrategy ccs = new ReadWriteCache();
ccs.Cache = cache;
ICacheConcurrencyStrategy ccs = CreateCache(cache);

// cache something
CacheKey fooKey = CreateCacheKey("foo");
Expand Down Expand Up @@ -155,12 +154,17 @@ private CacheKey CreateCacheKey(string text)
public async Task MinValueTimestampAsync()
{
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
ICacheConcurrencyStrategy strategy = new ReadWriteCache();
strategy.Cache = cache;

await (DoTestMinValueTimestampOnStrategyAsync(cache, new ReadWriteCache()));
await (DoTestMinValueTimestampOnStrategyAsync(cache, new NonstrictReadWriteCache()));
await (DoTestMinValueTimestampOnStrategyAsync(cache, new ReadOnlyCache()));
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache)));
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache, CacheFactory.NonstrictReadWrite)));
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache, CacheFactory.ReadOnly)));
}

protected virtual ICacheConcurrencyStrategy CreateCache(CacheBase cache, string strategy = CacheFactory.ReadWrite)
{
return CacheFactory.CreateCache(strategy, cache, Sfi.Settings);
}

protected override string[] Mappings => Array.Empty<string>();
}
}
24 changes: 14 additions & 10 deletions src/NHibernate.Test/CacheTest/CacheFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@
namespace NHibernate.Test.CacheTest
{
[TestFixture]
public class CacheFixture
public class CacheFixture: TestCase
{
[Test]
public void TestSimpleCache()
{
DoTestCache(new HashtableCacheProvider());
}

private CacheKey CreateCacheKey(string text)
protected CacheKey CreateCacheKey(string text)
{
return new CacheKey(text, NHibernateUtil.String, "Foo", null, null);
}

public void DoTestCache(ICacheProvider cacheProvider)
{
var cache = cacheProvider.BuildCache(typeof(String).FullName, new Dictionary<string, string>());
var cache = (CacheBase) cacheProvider.BuildCache(typeof(String).FullName, new Dictionary<string, string>());

long longBefore = Timestamper.Next();

Expand All @@ -33,8 +33,7 @@ public void DoTestCache(ICacheProvider cacheProvider)

Thread.Sleep(15);

ICacheConcurrencyStrategy ccs = new ReadWriteCache();
ccs.Cache = cache;
ICacheConcurrencyStrategy ccs = CreateCache(cache);

// cache something
CacheKey fooKey = CreateCacheKey("foo");
Expand Down Expand Up @@ -144,12 +143,17 @@ private void DoTestMinValueTimestampOnStrategy(CacheBase cache, ICacheConcurrenc
public void MinValueTimestamp()
{
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
ICacheConcurrencyStrategy strategy = new ReadWriteCache();
strategy.Cache = cache;

DoTestMinValueTimestampOnStrategy(cache, new ReadWriteCache());
DoTestMinValueTimestampOnStrategy(cache, new NonstrictReadWriteCache());
DoTestMinValueTimestampOnStrategy(cache, new ReadOnlyCache());
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache));
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.NonstrictReadWrite));
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.ReadOnly));
}

protected virtual ICacheConcurrencyStrategy CreateCache(CacheBase cache, string strategy = CacheFactory.ReadWrite)
{
return CacheFactory.CreateCache(strategy, cache, Sfi.Settings);
}

protected override string[] Mappings => Array.Empty<string>();
}
}
33 changes: 33 additions & 0 deletions src/NHibernate.Test/CacheTest/SyncOnlyCacheFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Threading;
using NHibernate.Cache;
using NHibernate.Cfg;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;

namespace NHibernate.Test.CacheTest
{
[TestFixture]
public class SyncOnlyCacheFixture : CacheFixture
{
protected override void Configure(Configuration cfg)
{
base.Configure(cfg);
cfg.SetProperty(Environment.CacheReadWriteLockFactory, "sync");
}

[Test]
public void AsyncOperationsThrow()
{
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
var strategy = CreateCache(cache);
CacheKey key = CreateCacheKey("key");
var stamp = Timestamper.Next();
Assert.ThrowsAsync<InvalidOperationException>(
() =>
strategy.PutAsync(key, "value", stamp, 0, null, false, default(CancellationToken)));
Assert.ThrowsAsync<InvalidOperationException>(() => strategy.GetAsync(key, stamp, default(CancellationToken)));
}
}
}
19 changes: 17 additions & 2 deletions src/NHibernate/Cache/CacheFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using NHibernate.Cfg;
using NHibernate.Util;

namespace NHibernate.Cache
{
Expand Down Expand Up @@ -43,7 +44,7 @@ public static ICacheConcurrencyStrategy CreateCache(

var cache = BuildCacheBase(name, settings, properties);

var ccs = CreateCache(usage, cache);
var ccs = CreateCache(usage, cache, settings);

if (mutable && usage == ReadOnly)
log.Warn("read-only cache configured for mutable: {0}", name);
Expand All @@ -57,7 +58,21 @@ public static ICacheConcurrencyStrategy CreateCache(
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
/// <param name="cache">The <see cref="CacheBase"/> used for this strategy.</param>
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
// TODO: Since v5.4
//[Obsolete("Please use overload with a CacheBase and Settings parameters.")]
public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cache)
{
return CreateCache(usage, cache, null);
}

/// <summary>
/// Creates an <see cref="ICacheConcurrencyStrategy"/> from the parameters.
/// </summary>
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
/// <param name="cache">The <see cref="CacheBase"/> used for this strategy.</param>
/// <param name="settings">NHibernate settings</param>
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cache, Settings settings)
{
if (log.IsDebugEnabled())
log.Debug("cache for: {0} usage strategy: {1}", cache.RegionName, usage);
Expand All @@ -69,7 +84,7 @@ public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cach
ccs = new ReadOnlyCache();
break;
case ReadWrite:
ccs = new ReadWriteCache();
ccs = new ReadWriteCache(settings == null ? new AsyncReaderWriterLock() : settings.CacheReadWriteLockFactory.Create());
break;
case NonstrictReadWrite:
ccs = new NonstrictReadWriteCache();
Expand Down
63 changes: 63 additions & 0 deletions src/NHibernate/Cache/ICacheLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Threading.Tasks;
using NHibernate.Util;

namespace NHibernate.Cache
{
/// <summary>
/// Implementors provide a locking mechanism for the cache.
/// </summary>
public interface ICacheLock : IDisposable
{
/// <summary>
/// Acquire synchronously a read lock.
/// </summary>
/// <returns>A read lock.</returns>
IDisposable ReadLock();

/// <summary>
/// Acquire synchronously a write lock.
/// </summary>
/// <returns>A write lock.</returns>
IDisposable WriteLock();

/// <summary>
/// Acquire asynchronously a read lock.
/// </summary>
/// <returns>A read lock.</returns>
Task<IDisposable> ReadLockAsync();

/// <summary>
/// Acquire asynchronously a write lock.
/// </summary>
/// <returns>A write lock.</returns>
Task<IDisposable> WriteLockAsync();
}

/// <summary>
/// Define a factory for cache locks.
/// </summary>
public interface ICacheReadWriteLockFactory
{
/// <summary>
/// Create a cache lock provider.
/// </summary>
ICacheLock Create();
}

internal class AsyncCacheReadWriteLockFactory : ICacheReadWriteLockFactory
{
public ICacheLock Create()
{
return new AsyncReaderWriterLock();
}
}

internal class SyncCacheReadWriteLockFactory : ICacheReadWriteLockFactory
{
public ICacheLock Create()
{
return new SyncCacheLock();
}
}
}
11 changes: 10 additions & 1 deletion src/NHibernate/Cache/ReadWriteCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,16 @@ public interface ILockable

private CacheBase _cache;
private int _nextLockId;
private readonly AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock();
private readonly ICacheLock _asyncReaderWriterLock;

public ReadWriteCache() : this(new AsyncReaderWriterLock())
{
}

public ReadWriteCache(ICacheLock locker)
{
_asyncReaderWriterLock = locker;
}

/// <summary>
/// Gets the cache region name.
Expand Down
54 changes: 54 additions & 0 deletions src/NHibernate/Cache/SyncCacheLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace NHibernate.Cache
{
class SyncCacheLock : ICacheLock
{
class MonitorLock : IDisposable
{
private readonly object _lockObj;

public MonitorLock(object lockObj)
{
Monitor.Enter(lockObj);
_lockObj = lockObj;
}

public void Dispose()
{
Monitor.Exit(_lockObj);
}
}

public void Dispose()
{
}

public IDisposable ReadLock()
{
return new MonitorLock(this);
}

public IDisposable WriteLock()
{
return new MonitorLock(this);
}

public Task<IDisposable> ReadLockAsync()
{
throw AsyncNotSupporteException();
}

public Task<IDisposable> WriteLockAsync()
{
throw AsyncNotSupporteException();
}

private static InvalidOperationException AsyncNotSupporteException()
{
return new InvalidOperationException("This locker supports only sync operations. Change 'cache.read_write_lock_factory' setting to `async` to support async operations.");
}
}
}
Loading

0 comments on commit b809f77

Please sign in to comment.