Skip to content

Commit b809f77

Browse files
authored
Add monitor based sync only locker for ReadWrite cache (#2944)
1 parent 00af788 commit b809f77

16 files changed

+299
-30
lines changed

doc/reference/modules/configuration.xml

+20
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,26 @@ var session = sessions.OpenSession(conn);
610610
</para>
611611
</entry>
612612
</row>
613+
<row>
614+
<entry>
615+
<literal>cache.read_write_lock_factory</literal>
616+
</entry>
617+
<entry>
618+
Specify the cache lock factory to use for read-write cache regions.
619+
Defaults to the built-in <literal>async</literal> cache lock factory.
620+
<para>
621+
<emphasis role="strong">eg.</emphasis>
622+
<literal>async</literal>, or <literal>sync</literal>, or <literal>classname.of.CacheLockFactory, assembly</literal> with custom implementation of <literal>ICacheReadWriteLockFactory</literal>
623+
</para>
624+
<para>
625+
<literal>async</literal> uses a single writer multiple readers locking mechanism supporting asynchronous operations.
626+
</para>
627+
<para>
628+
<literal>sync</literal> uses a single access locking mechanism which will throw on asynchronous
629+
operations but may have better performances than the <literal>async</literal> provider for applications using the .Net Framework (4.8 and below).
630+
</para>
631+
</entry>
632+
</row>
613633
<row>
614634
<entry>
615635
<literal>cache.region_prefix</literal>

src/AsyncGenerator.yml

+2
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@
222222
- conversion: Ignore
223223
anyBaseTypeRule: IsTestCase
224224
executionPhase: PostProviders
225+
- conversion: Ignore
226+
name: SyncOnlyCacheFixture
225227
ignoreDocuments:
226228
- filePathEndsWith: Linq/MathTests.cs
227229
- filePathEndsWith: Linq/ExpressionSessionLeakTest.cs

src/NHibernate.Test/Async/CacheTest/CacheFixture.cs

+14-10
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ namespace NHibernate.Test.CacheTest
1919
{
2020
using System.Threading.Tasks;
2121
[TestFixture]
22-
public class CacheFixtureAsync
22+
public class CacheFixtureAsync: TestCase
2323
{
2424
[Test]
2525
public async Task TestSimpleCacheAsync()
2626
{
2727
await (DoTestCacheAsync(new HashtableCacheProvider()));
2828
}
2929

30-
private CacheKey CreateCacheKey(string text)
30+
protected CacheKey CreateCacheKey(string text)
3131
{
3232
return new CacheKey(text, NHibernateUtil.String, "Foo", null, null);
3333
}
3434

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

3939
long longBefore = Timestamper.Next();
4040

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

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

47-
ICacheConcurrencyStrategy ccs = new ReadWriteCache();
48-
ccs.Cache = cache;
47+
ICacheConcurrencyStrategy ccs = CreateCache(cache);
4948

5049
// cache something
5150
CacheKey fooKey = CreateCacheKey("foo");
@@ -155,12 +154,17 @@ private CacheKey CreateCacheKey(string text)
155154
public async Task MinValueTimestampAsync()
156155
{
157156
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
158-
ICacheConcurrencyStrategy strategy = new ReadWriteCache();
159-
strategy.Cache = cache;
160157

161-
await (DoTestMinValueTimestampOnStrategyAsync(cache, new ReadWriteCache()));
162-
await (DoTestMinValueTimestampOnStrategyAsync(cache, new NonstrictReadWriteCache()));
163-
await (DoTestMinValueTimestampOnStrategyAsync(cache, new ReadOnlyCache()));
158+
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache)));
159+
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache, CacheFactory.NonstrictReadWrite)));
160+
await (DoTestMinValueTimestampOnStrategyAsync(cache, CreateCache(cache, CacheFactory.ReadOnly)));
164161
}
162+
163+
protected virtual ICacheConcurrencyStrategy CreateCache(CacheBase cache, string strategy = CacheFactory.ReadWrite)
164+
{
165+
return CacheFactory.CreateCache(strategy, cache, Sfi.Settings);
166+
}
167+
168+
protected override string[] Mappings => Array.Empty<string>();
165169
}
166170
}

src/NHibernate.Test/CacheTest/CacheFixture.cs

+14-10
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@
88
namespace NHibernate.Test.CacheTest
99
{
1010
[TestFixture]
11-
public class CacheFixture
11+
public class CacheFixture: TestCase
1212
{
1313
[Test]
1414
public void TestSimpleCache()
1515
{
1616
DoTestCache(new HashtableCacheProvider());
1717
}
1818

19-
private CacheKey CreateCacheKey(string text)
19+
protected CacheKey CreateCacheKey(string text)
2020
{
2121
return new CacheKey(text, NHibernateUtil.String, "Foo", null, null);
2222
}
2323

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

2828
long longBefore = Timestamper.Next();
2929

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

3434
Thread.Sleep(15);
3535

36-
ICacheConcurrencyStrategy ccs = new ReadWriteCache();
37-
ccs.Cache = cache;
36+
ICacheConcurrencyStrategy ccs = CreateCache(cache);
3837

3938
// cache something
4039
CacheKey fooKey = CreateCacheKey("foo");
@@ -144,12 +143,17 @@ private void DoTestMinValueTimestampOnStrategy(CacheBase cache, ICacheConcurrenc
144143
public void MinValueTimestamp()
145144
{
146145
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
147-
ICacheConcurrencyStrategy strategy = new ReadWriteCache();
148-
strategy.Cache = cache;
149146

150-
DoTestMinValueTimestampOnStrategy(cache, new ReadWriteCache());
151-
DoTestMinValueTimestampOnStrategy(cache, new NonstrictReadWriteCache());
152-
DoTestMinValueTimestampOnStrategy(cache, new ReadOnlyCache());
147+
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache));
148+
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.NonstrictReadWrite));
149+
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.ReadOnly));
153150
}
151+
152+
protected virtual ICacheConcurrencyStrategy CreateCache(CacheBase cache, string strategy = CacheFactory.ReadWrite)
153+
{
154+
return CacheFactory.CreateCache(strategy, cache, Sfi.Settings);
155+
}
156+
157+
protected override string[] Mappings => Array.Empty<string>();
154158
}
155159
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using NHibernate.Cache;
5+
using NHibernate.Cfg;
6+
using NUnit.Framework;
7+
using Environment = NHibernate.Cfg.Environment;
8+
9+
namespace NHibernate.Test.CacheTest
10+
{
11+
[TestFixture]
12+
public class SyncOnlyCacheFixture : CacheFixture
13+
{
14+
protected override void Configure(Configuration cfg)
15+
{
16+
base.Configure(cfg);
17+
cfg.SetProperty(Environment.CacheReadWriteLockFactory, "sync");
18+
}
19+
20+
[Test]
21+
public void AsyncOperationsThrow()
22+
{
23+
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());
24+
var strategy = CreateCache(cache);
25+
CacheKey key = CreateCacheKey("key");
26+
var stamp = Timestamper.Next();
27+
Assert.ThrowsAsync<InvalidOperationException>(
28+
() =>
29+
strategy.PutAsync(key, "value", stamp, 0, null, false, default(CancellationToken)));
30+
Assert.ThrowsAsync<InvalidOperationException>(() => strategy.GetAsync(key, stamp, default(CancellationToken)));
31+
}
32+
}
33+
}

src/NHibernate/Cache/CacheFactory.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using NHibernate.Cfg;
4+
using NHibernate.Util;
45

56
namespace NHibernate.Cache
67
{
@@ -43,7 +44,7 @@ public static ICacheConcurrencyStrategy CreateCache(
4344

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

46-
var ccs = CreateCache(usage, cache);
47+
var ccs = CreateCache(usage, cache, settings);
4748

4849
if (mutable && usage == ReadOnly)
4950
log.Warn("read-only cache configured for mutable: {0}", name);
@@ -57,7 +58,21 @@ public static ICacheConcurrencyStrategy CreateCache(
5758
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
5859
/// <param name="cache">The <see cref="CacheBase"/> used for this strategy.</param>
5960
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
61+
// TODO: Since v5.4
62+
//[Obsolete("Please use overload with a CacheBase and Settings parameters.")]
6063
public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cache)
64+
{
65+
return CreateCache(usage, cache, null);
66+
}
67+
68+
/// <summary>
69+
/// Creates an <see cref="ICacheConcurrencyStrategy"/> from the parameters.
70+
/// </summary>
71+
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
72+
/// <param name="cache">The <see cref="CacheBase"/> used for this strategy.</param>
73+
/// <param name="settings">NHibernate settings</param>
74+
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
75+
public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cache, Settings settings)
6176
{
6277
if (log.IsDebugEnabled())
6378
log.Debug("cache for: {0} usage strategy: {1}", cache.RegionName, usage);
@@ -69,7 +84,7 @@ public static ICacheConcurrencyStrategy CreateCache(string usage, CacheBase cach
6984
ccs = new ReadOnlyCache();
7085
break;
7186
case ReadWrite:
72-
ccs = new ReadWriteCache();
87+
ccs = new ReadWriteCache(settings == null ? new AsyncReaderWriterLock() : settings.CacheReadWriteLockFactory.Create());
7388
break;
7489
case NonstrictReadWrite:
7590
ccs = new NonstrictReadWriteCache();

src/NHibernate/Cache/ICacheLock.cs

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using NHibernate.Util;
4+
5+
namespace NHibernate.Cache
6+
{
7+
/// <summary>
8+
/// Implementors provide a locking mechanism for the cache.
9+
/// </summary>
10+
public interface ICacheLock : IDisposable
11+
{
12+
/// <summary>
13+
/// Acquire synchronously a read lock.
14+
/// </summary>
15+
/// <returns>A read lock.</returns>
16+
IDisposable ReadLock();
17+
18+
/// <summary>
19+
/// Acquire synchronously a write lock.
20+
/// </summary>
21+
/// <returns>A write lock.</returns>
22+
IDisposable WriteLock();
23+
24+
/// <summary>
25+
/// Acquire asynchronously a read lock.
26+
/// </summary>
27+
/// <returns>A read lock.</returns>
28+
Task<IDisposable> ReadLockAsync();
29+
30+
/// <summary>
31+
/// Acquire asynchronously a write lock.
32+
/// </summary>
33+
/// <returns>A write lock.</returns>
34+
Task<IDisposable> WriteLockAsync();
35+
}
36+
37+
/// <summary>
38+
/// Define a factory for cache locks.
39+
/// </summary>
40+
public interface ICacheReadWriteLockFactory
41+
{
42+
/// <summary>
43+
/// Create a cache lock provider.
44+
/// </summary>
45+
ICacheLock Create();
46+
}
47+
48+
internal class AsyncCacheReadWriteLockFactory : ICacheReadWriteLockFactory
49+
{
50+
public ICacheLock Create()
51+
{
52+
return new AsyncReaderWriterLock();
53+
}
54+
}
55+
56+
internal class SyncCacheReadWriteLockFactory : ICacheReadWriteLockFactory
57+
{
58+
public ICacheLock Create()
59+
{
60+
return new SyncCacheLock();
61+
}
62+
}
63+
}

src/NHibernate/Cache/ReadWriteCache.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ public interface ILockable
3636

3737
private CacheBase _cache;
3838
private int _nextLockId;
39-
private readonly AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock();
39+
private readonly ICacheLock _asyncReaderWriterLock;
40+
41+
public ReadWriteCache() : this(new AsyncReaderWriterLock())
42+
{
43+
}
44+
45+
public ReadWriteCache(ICacheLock locker)
46+
{
47+
_asyncReaderWriterLock = locker;
48+
}
4049

4150
/// <summary>
4251
/// Gets the cache region name.

src/NHibernate/Cache/SyncCacheLock.cs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace NHibernate.Cache
6+
{
7+
class SyncCacheLock : ICacheLock
8+
{
9+
class MonitorLock : IDisposable
10+
{
11+
private readonly object _lockObj;
12+
13+
public MonitorLock(object lockObj)
14+
{
15+
Monitor.Enter(lockObj);
16+
_lockObj = lockObj;
17+
}
18+
19+
public void Dispose()
20+
{
21+
Monitor.Exit(_lockObj);
22+
}
23+
}
24+
25+
public void Dispose()
26+
{
27+
}
28+
29+
public IDisposable ReadLock()
30+
{
31+
return new MonitorLock(this);
32+
}
33+
34+
public IDisposable WriteLock()
35+
{
36+
return new MonitorLock(this);
37+
}
38+
39+
public Task<IDisposable> ReadLockAsync()
40+
{
41+
throw AsyncNotSupporteException();
42+
}
43+
44+
public Task<IDisposable> WriteLockAsync()
45+
{
46+
throw AsyncNotSupporteException();
47+
}
48+
49+
private static InvalidOperationException AsyncNotSupporteException()
50+
{
51+
return new InvalidOperationException("This locker supports only sync operations. Change 'cache.read_write_lock_factory' setting to `async` to support async operations.");
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)