Skip to content

Add monitor based sync only locker for ReadWrite cache #2944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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