Skip to content

Commit ea4b2d2

Browse files
Fix GetQueryCache building two different caches for same region.
1 parent e13b461 commit ea4b2d2

File tree

4 files changed

+32
-105
lines changed

4 files changed

+32
-105
lines changed

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

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public async Task RetrievedQueryCacheMatchesGloballyStoredOneAsync()
3737
{
3838
var region = "RetrievedQueryCacheMatchesGloballyStoredOne";
3939
LockedCache.Semaphore = new SemaphoreSlim(0);
40+
LockedCache.CreationCount = 0;
4041
try
4142
{
4243
var failures = new ConcurrentBag<Exception>();
@@ -89,44 +90,7 @@ public async Task RetrievedQueryCacheMatchesGloballyStoredOneAsync()
8990
var queryCache = Sfi.GetQueryCache(region).Cache;
9091
var globalCache = Sfi.GetSecondLevelCacheRegion(region);
9192
Assert.That(globalCache, Is.SameAs(queryCache));
93+
Assert.That(LockedCache.CreationCount, Is.EqualTo(1));
9294
}
9395
}
94-
95-
public partial class LockedCache : ICache
96-
{
97-
98-
#region Void implementation
99-
100-
public Task<object> GetAsync(object key, CancellationToken cancellationToken)
101-
{
102-
return Task.FromResult<object>(null);
103-
}
104-
105-
public Task PutAsync(object key, object value, CancellationToken cancellationToken)
106-
{
107-
return Task.CompletedTask;
108-
}
109-
110-
public Task RemoveAsync(object key, CancellationToken cancellationToken)
111-
{
112-
return Task.CompletedTask;
113-
}
114-
115-
public Task ClearAsync(CancellationToken cancellationToken)
116-
{
117-
return Task.CompletedTask;
118-
}
119-
120-
public Task LockAsync(object key, CancellationToken cancellationToken)
121-
{
122-
return Task.CompletedTask;
123-
}
124-
125-
public Task UnlockAsync(object key, CancellationToken cancellationToken)
126-
{
127-
return Task.CompletedTask;
128-
}
129-
130-
#endregion
131-
}
13296
}

src/NHibernate.Test/CacheTest/GetQueryCacheFixture.cs

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public void RetrievedQueryCacheMatchesGloballyStoredOne()
2626
{
2727
var region = "RetrievedQueryCacheMatchesGloballyStoredOne";
2828
LockedCache.Semaphore = new SemaphoreSlim(0);
29+
LockedCache.CreationCount = 0;
2930
try
3031
{
3132
var failures = new ConcurrentBag<Exception>();
@@ -78,60 +79,27 @@ public void RetrievedQueryCacheMatchesGloballyStoredOne()
7879
var queryCache = Sfi.GetQueryCache(region).Cache;
7980
var globalCache = Sfi.GetSecondLevelCacheRegion(region);
8081
Assert.That(globalCache, Is.SameAs(queryCache));
82+
Assert.That(LockedCache.CreationCount, Is.EqualTo(1));
8183
}
8284
}
8385

84-
public partial class LockedCache : ICache
86+
public class LockedCache : HashtableCache
8587
{
8688
public static SemaphoreSlim Semaphore { get; set; }
8789

88-
public LockedCache(string regionName)
89-
{
90-
RegionName = regionName;
91-
Semaphore?.Wait();
92-
}
93-
94-
#region Void implementation
95-
96-
public object Get(object key)
97-
{
98-
return null;
99-
}
100-
101-
public void Put(object key, object value)
102-
{
103-
}
104-
105-
public void Remove(object key)
106-
{
107-
}
108-
109-
public void Clear()
110-
{
111-
}
112-
113-
public void Destroy()
114-
{
115-
}
116-
117-
public void Lock(object key)
118-
{
119-
}
90+
private static int _creationCount;
12091

121-
public void Unlock(object key)
92+
public static int CreationCount
12293
{
94+
get => _creationCount;
95+
set => _creationCount = value;
12396
}
12497

125-
public long NextTimestamp()
98+
public LockedCache(string regionName) : base(regionName)
12699
{
127-
return Timestamper.Next();
100+
Semaphore?.Wait();
101+
Interlocked.Increment(ref _creationCount);
128102
}
129-
130-
public int Timeout => Timestamper.OneMs * 60000;
131-
132-
public string RegionName { get; }
133-
134-
#endregion
135103
}
136104

137105
public class LockedCacheProvider : ICacheProvider

src/NHibernate/Async/Impl/SessionFactoryImpl.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb
8787
{
8888
queryCache.Destroy();
8989

90-
foreach (IQueryCache cache in queryCaches.Values)
90+
foreach (var cache in queryCaches.Values)
9191
{
92-
cache.Destroy();
92+
cache.Value.Destroy();
9393
}
9494

9595
updateTimestampsCache.Destroy();
@@ -297,10 +297,9 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb
297297
{
298298
if (settings.IsQueryCacheEnabled)
299299
{
300-
IQueryCache currentQueryCache;
301-
if (queryCaches.TryGetValue(cacheRegion, out currentQueryCache))
300+
if (queryCaches.TryGetValue(cacheRegion, out var currentQueryCache))
302301
{
303-
return currentQueryCache.ClearAsync(cancellationToken);
302+
return currentQueryCache.Value.ClearAsync(cancellationToken);
304303
}
305304
}
306305
}

src/NHibernate/Impl/SessionFactoryImpl.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public void HandleEntityNotFound(string entityName, object id)
149149
private readonly IQueryCache queryCache;
150150

151151
[NonSerialized]
152-
private readonly ConcurrentDictionary<string, IQueryCache> queryCaches;
152+
private readonly ConcurrentDictionary<string, Lazy<IQueryCache>> queryCaches;
153153
[NonSerialized]
154154
private readonly SchemaExport schemaExport;
155155
[NonSerialized]
@@ -381,7 +381,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
381381
{
382382
updateTimestampsCache = new UpdateTimestampsCache(settings, properties);
383383
queryCache = settings.QueryCacheFactory.GetQueryCache(null, updateTimestampsCache, settings, properties);
384-
queryCaches = new ConcurrentDictionary<string, IQueryCache>();
384+
queryCaches = new ConcurrentDictionary<string, Lazy<IQueryCache>>();
385385
}
386386
else
387387
{
@@ -847,9 +847,9 @@ public void Close()
847847
{
848848
queryCache.Destroy();
849849

850-
foreach (IQueryCache cache in queryCaches.Values)
850+
foreach (var cache in queryCaches.Values)
851851
{
852-
cache.Destroy();
852+
cache.Value.Destroy();
853853
}
854854

855855
updateTimestampsCache.Destroy();
@@ -1029,21 +1029,18 @@ public IQueryCache GetQueryCache(string cacheRegion)
10291029
return null;
10301030
}
10311031

1032-
var wasAdded = false;
10331032
// The factory may be run concurrently by threads trying to get the same region.
1034-
// But the GetOrAdd will yield the same cache for all threads, so in case of add, use
1035-
// it to update allCacheRegions
1036-
var cache = queryCaches.GetOrAdd(
1033+
// But the GetOrAdd will yield the same lazy for all threads, so only one will
1034+
// initialize. https://stackoverflow.com/a/31637510/1178314
1035+
return queryCaches.GetOrAdd(
10371036
cacheRegion,
1038-
cr =>
1039-
{
1040-
var currentQueryCache = settings.QueryCacheFactory.GetQueryCache(cacheRegion, updateTimestampsCache, settings, properties);
1041-
wasAdded = true;
1042-
return currentQueryCache;
1043-
});
1044-
if (wasAdded)
1045-
allCacheRegions[cache.RegionName] = cache.Cache;
1046-
return cache;
1037+
cr => new Lazy<IQueryCache>(
1038+
() =>
1039+
{
1040+
var currentQueryCache = settings.QueryCacheFactory.GetQueryCache(cr, updateTimestampsCache, settings, properties);
1041+
allCacheRegions[currentQueryCache.RegionName] = currentQueryCache.Cache;
1042+
return currentQueryCache;
1043+
})).Value;
10471044
}
10481045

10491046
public void EvictQueries()
@@ -1069,10 +1066,9 @@ public void EvictQueries(string cacheRegion)
10691066
{
10701067
if (settings.IsQueryCacheEnabled)
10711068
{
1072-
IQueryCache currentQueryCache;
1073-
if (queryCaches.TryGetValue(cacheRegion, out currentQueryCache))
1069+
if (queryCaches.TryGetValue(cacheRegion, out var currentQueryCache))
10741070
{
1075-
currentQueryCache.Clear();
1071+
currentQueryCache.Value.Clear();
10761072
}
10771073
}
10781074
}

0 commit comments

Comments
 (0)