Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
180 changes: 180 additions & 0 deletions src/NHibernate.Test/CacheTest/SyncOnlyCacheFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Threading;
using NHibernate.Cache;
using NHibernate.Cache.Access;
using NHibernate.Cfg;
using NUnit.Framework;
using Environment = NHibernate.Cfg.Environment;

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

[Test]
public void TestSimpleCache()
{
DoTestCache(new HashtableCacheProvider());
}

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

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

long longBefore = Timestamper.Next();

Thread.Sleep(15);

long before = Timestamper.Next();

Thread.Sleep(15);

ICacheConcurrencyStrategy ccs = CreateCache(cache);

// cache something
CacheKey fooKey = CreateCacheKey("foo");

Assert.IsTrue(ccs.Put(fooKey, "foo", before, null, null, false));

Thread.Sleep(15);

long after = Timestamper.Next();

Assert.IsNull(ccs.Get(fooKey, longBefore));
Assert.AreEqual("foo", ccs.Get(fooKey, after));
Assert.IsFalse(ccs.Put(fooKey, "foo", before, null, null, false));

// update it;

ISoftLock fooLock = ccs.Lock(fooKey, null);

Assert.IsNull(ccs.Get(fooKey, after));
Assert.IsNull(ccs.Get(fooKey, longBefore));
Assert.IsFalse(ccs.Put(fooKey, "foo", before, null, null, false));

Thread.Sleep(15);

long whileLocked = Timestamper.Next();

Assert.IsFalse(ccs.Put(fooKey, "foo", whileLocked, null, null, false));

Thread.Sleep(15);

ccs.Release(fooKey, fooLock);

Assert.IsNull(ccs.Get(fooKey, after));
Assert.IsNull(ccs.Get(fooKey, longBefore));
Assert.IsFalse(ccs.Put(fooKey, "bar", whileLocked, null, null, false));
Assert.IsFalse(ccs.Put(fooKey, "bar", after, null, null, false));

Thread.Sleep(15);

long longAfter = Timestamper.Next();

Assert.IsTrue(ccs.Put(fooKey, "baz", longAfter, null, null, false));
Assert.IsNull(ccs.Get(fooKey, after));
Assert.IsNull(ccs.Get(fooKey, whileLocked));

Thread.Sleep(15);

long longLongAfter = Timestamper.Next();

Assert.AreEqual("baz", ccs.Get(fooKey, longLongAfter));

// update it again, with multiple locks

ISoftLock fooLock1 = ccs.Lock(fooKey, null);
ISoftLock fooLock2 = ccs.Lock(fooKey, null);

Assert.IsNull(ccs.Get(fooKey, longLongAfter));

Thread.Sleep(15);

whileLocked = Timestamper.Next();

Assert.IsFalse(ccs.Put(fooKey, "foo", whileLocked, null, null, false));

Thread.Sleep(15);

ccs.Release(fooKey, fooLock2);

Thread.Sleep(15);

long betweenReleases = Timestamper.Next();

Assert.IsFalse(ccs.Put(fooKey, "bar", betweenReleases, null, null, false));
Assert.IsNull(ccs.Get(fooKey, betweenReleases));

Thread.Sleep(15);

ccs.Release(fooKey, fooLock1);

Assert.IsFalse(ccs.Put(fooKey, "bar", whileLocked, null, null, false));

Thread.Sleep(15);

longAfter = Timestamper.Next();

Assert.IsTrue(ccs.Put(fooKey, "baz", longAfter, null, null, false));
Assert.IsNull(ccs.Get(fooKey, whileLocked));

Thread.Sleep(15);

longLongAfter = Timestamper.Next();

Assert.AreEqual("baz", ccs.Get(fooKey, longLongAfter));
}

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

private void DoTestMinValueTimestampOnStrategy(CacheBase cache, ICacheConcurrencyStrategy strategy)
{
CacheKey key = CreateCacheKey("key");
strategy.Cache = cache;
strategy.Put(key, "value", long.MinValue, 0, null, false);

Assert.IsNull(strategy.Get(key, long.MinValue), "{0} strategy fails the test", strategy.GetType());
Assert.IsNull(strategy.Get(key, long.MaxValue), "{0} strategy fails the test", strategy.GetType());
}

[Test]
public void MinValueTimestamp()
{
var cache = new HashtableCacheProvider().BuildCache("region", new Dictionary<string, string>());

DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache));
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.NonstrictReadWrite));
DoTestMinValueTimestampOnStrategy(cache, CreateCache(cache, CacheFactory.ReadOnly));
}

[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)));
}

protected override string[] Mappings => Array.Empty<string>();
}
}
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.CacheReadWriteReadWriteLockFactory.Create());
break;
case NonstrictReadWrite:
ccs = new NonstrictReadWriteCache();
Expand Down
35 changes: 35 additions & 0 deletions src/NHibernate/Cache/ICacheLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using NHibernate.Util;

namespace NHibernate.Cache
{
public interface ICacheLock : IDisposable
{
IDisposable ReadLock();
IDisposable WriteLock();
Task<IDisposable> ReadLockAsync();
Task<IDisposable> WriteLockAsync();
}

public interface ICacheReadWriteLockFactory
{
ICacheLock Create();
}

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

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

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

public ReadWriteCache()
{
_asyncReaderWriterLock = new AsyncReaderWriterLock();
}

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

/// <summary>
/// Gets the cache region name.
Expand Down
49 changes: 49 additions & 0 deletions src/NHibernate/Cache/SyncCacheLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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 new InvalidOperationException("This locker supports only sync operations.");
}

public Task<IDisposable> WriteLockAsync()
{
throw new InvalidOperationException("This locker supports only sync operations.");
}
}
}
16 changes: 13 additions & 3 deletions src/NHibernate/Cache/UpdateTimestampsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public partial class UpdateTimestampsCache
{
private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(UpdateTimestampsCache));
private readonly CacheBase _updateTimestamps;
private readonly AsyncReaderWriterLock _asyncReaderWriterLock = new AsyncReaderWriterLock();
private readonly ICacheLock _asyncReaderWriterLock;

public virtual void Clear()
{
Expand All @@ -40,11 +40,21 @@ public UpdateTimestampsCache(Settings settings, IDictionary<string, string> prop
/// <summary>
/// Build the update timestamps cache.
/// </summary>x
/// <param name="cache">The <see cref="ICache" /> to use.</param>
public UpdateTimestampsCache(CacheBase cache)
/// <param name="cache">The <see cref="CacheBase" /> to use.</param>
public UpdateTimestampsCache(CacheBase cache) : this(cache, new AsyncReaderWriterLock())
{
}

/// <summary>
/// Build the update timestamps cache.
/// </summary>
/// <param name="cache">The <see cref="CacheBase" /> to use.</param>
/// <param name="locker">Locker to use.</param>
public UpdateTimestampsCache(CacheBase cache, ICacheLock locker)
{
log.Info("starting update timestamps cache at region: {0}", cache.RegionName);
_updateTimestamps = cache;
_asyncReaderWriterLock = locker;
}

//Since v5.1
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Cfg/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ public static string Version
public const string CacheProvider = "cache.provider_class";
public const string UseQueryCache = "cache.use_query_cache";
public const string QueryCacheFactory = "cache.query_cache_factory";
public const string CacheReadWriteLockFactory = "cache.read_write_lock_factory";
public const string UseSecondLevelCache = "cache.use_second_level_cache";
public const string CacheRegionPrefix = "cache.region_prefix";
public const string UseMinimalPuts = "cache.use_minimal_puts";
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/Cfg/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public Settings()
public ConnectionReleaseMode ConnectionReleaseMode { get; internal set; }

public ICacheProvider CacheProvider { get; internal set; }
public ICacheReadWriteLockFactory CacheReadWriteReadWriteLockFactory { get; internal set; }

public IQueryCacheFactory QueryCacheFactory { get; internal set; }

Expand Down
Loading