Skip to content

Commit ebde9af

Browse files
Give-up sharing ICache per region
1 parent e5b030f commit ebde9af

File tree

4 files changed

+75
-55
lines changed

4 files changed

+75
-55
lines changed

src/NHibernate.Test/CacheTest/BuildCacheFixture.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected override void Configure(Configuration configuration)
2828
}
2929

3030
[Theory]
31-
public void CommonRegionHasOneUniqueCacheAndExpectedConcurrency(bool withPrefix)
31+
public void CommonRegionHasExpectedConcurrency(bool withPrefix)
3232
{
3333
const string prefix = "Prefix";
3434
const string region = "Common";
@@ -70,11 +70,6 @@ public void CommonRegionHasOneUniqueCacheAndExpectedConcurrency(bool withPrefix)
7070
Assert.That(relatedAConcurrencyCache, Is.InstanceOf<NonstrictReadWriteCache>(), "Unexpected concurrency for RelatedA");
7171
Assert.That(entityBConcurrencyCache, Is.InstanceOf<ReadOnlyCache>(), "Unexpected concurrency for EntityB");
7272
Assert.That(relatedBConcurrencyCache, Is.InstanceOf<ReadWriteCache>(), "Unexpected concurrency for RelatedB");
73-
Assert.That(entityACache, Is.EqualTo(commonRegionCache), "Unexpected cache for EntityA");
74-
Assert.That(entityBCache, Is.EqualTo(commonRegionCache), "Unexpected cache for EntityB");
75-
Assert.That(relatedACache, Is.EqualTo(commonRegionCache), "Unexpected cache for RelatedA");
76-
Assert.That(relatedBCache, Is.EqualTo(commonRegionCache), "Unexpected cache for RelatedB");
77-
Assert.That(queryCache, Is.EqualTo(commonRegionCache), "Unexpected cache for query cache");
7873
});
7974
}
8075
finally

src/NHibernate/Async/Impl/SessionFactoryImpl.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ public sealed partial class SessionFactoryImpl : ISessionFactoryImplementor, IOb
8585

8686
if (settings.IsQueryCacheEnabled)
8787
{
88-
queryCache.Destroy();
89-
9088
foreach (var cache in queryCaches.Values)
9189
{
9290
cache.Value.Destroy();

src/NHibernate/Cache/CacheFactory.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,29 @@ public static ICacheConcurrencyStrategy CreateCache(
3939
Settings settings,
4040
IDictionary<string, string> properties)
4141
{
42-
return CreateCache(
43-
usage, name, mutable, settings,
44-
r => settings.CacheProvider.BuildCache(r, properties));
42+
var cache = CreateCache(
43+
usage, name, settings,
44+
(r, u) => settings.CacheProvider.BuildCache(r, properties));
45+
46+
if (cache != null && mutable && usage == ReadOnly)
47+
Log.Warn("read-only cache configured for mutable: {0}", name);
48+
49+
return cache;
4550
}
4651

4752
/// <summary>
4853
/// Creates an <see cref="ICacheConcurrencyStrategy"/> from the parameters.
4954
/// </summary>
5055
/// <param name="usage">The name of the strategy that <see cref="ICacheProvider"/> should use for the class.</param>
51-
/// <param name="name">The name of the class the strategy is being created for.</param>
52-
/// <param name="mutable"><see langword="true" /> if the object being stored in the cache is mutable.</param>
56+
/// <param name="name">The name of the cache region the strategy is being created for.</param>
5357
/// <param name="settings">Used to retrieve the global cache region prefix.</param>
54-
/// <param name="regionCacheGetter">The delegate for obtaining the <see cref="ICache" /> to use for the region.</param>
58+
/// <param name="regionAndUsageCacheGetter">The delegate for obtaining the <see cref="ICache" /> to use for the region.</param>
5559
/// <returns>An <see cref="ICacheConcurrencyStrategy"/> to use for this object in the <see cref="ICache"/>.</returns>
5660
public static ICacheConcurrencyStrategy CreateCache(
5761
string usage,
5862
string name,
59-
bool mutable,
6063
Settings settings,
61-
Func<string, ICache> regionCacheGetter)
64+
Func<string, string, ICache> regionAndUsageCacheGetter)
6265
{
6366
if (usage == null || !settings.IsSecondLevelCacheEnabled) return null; //no cache
6467

@@ -71,10 +74,6 @@ public static ICacheConcurrencyStrategy CreateCache(
7174
switch (usage)
7275
{
7376
case ReadOnly:
74-
if (mutable)
75-
{
76-
Log.Warn("read-only cache configured for mutable: {0}", name);
77-
}
7877
ccs = new ReadOnlyCache();
7978
break;
8079
case ReadWrite:
@@ -94,7 +93,7 @@ public static ICacheConcurrencyStrategy CreateCache(
9493
ICache impl;
9594
try
9695
{
97-
impl = regionCacheGetter(name);
96+
impl = regionAndUsageCacheGetter(name, usage);
9897
}
9998
catch (CacheException e)
10099
{

src/NHibernate/Impl/SessionFactoryImpl.cs

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ public void HandleEntityNotFound(string entityName, object id)
9595
private static readonly IIdentifierGenerator UuidGenerator = new UUIDHexGenerator();
9696

9797
[NonSerialized]
98-
private readonly ConcurrentDictionary<string, ICache> allCacheRegions =
99-
new ConcurrentDictionary<string, ICache>();
98+
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ICache>> allCachePerRegionThenType =
99+
new ConcurrentDictionary<string, ConcurrentDictionary<string, ICache>>();
100100

101101
[NonSerialized]
102102
private readonly IDictionary<string, IClassMetadata> classMetadata;
@@ -148,6 +148,8 @@ public void HandleEntityNotFound(string entityName, object id)
148148
[NonSerialized]
149149
private readonly IQueryCache queryCache;
150150

151+
private const string QueryCacheType = "QueryCache";
152+
151153
[NonSerialized]
152154
private readonly ConcurrentDictionary<string, Lazy<IQueryCache>> queryCaches;
153155
[NonSerialized]
@@ -232,6 +234,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
232234

233235
#region Persisters
234236

237+
var caches = new Dictionary<Tuple<string, string>, ICacheConcurrencyStrategy>();
235238
entityPersisters = new Dictionary<string, IEntityPersister>();
236239
implementorToEntityName = new Dictionary<System.Type, string>();
237240

@@ -240,14 +243,11 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
240243
foreach (PersistentClass model in cfg.ClassMappings)
241244
{
242245
model.PrepareTemporaryTables(mapping, settings.Dialect);
243-
var cacheRegion = model.RootClazz.CacheRegionName;
244-
var strategy = model.CacheConcurrencyStrategy;
245-
var cache = CacheFactory.CreateCache(
246-
strategy,
247-
cacheRegion,
246+
var cache = GetCacheConcurrencyStrategy(
247+
model.RootClazz.CacheRegionName,
248+
model.CacheConcurrencyStrategy,
248249
model.IsMutable,
249-
settings,
250-
GetOrBuild);
250+
caches);
251251
var cp = PersisterFactory.CreateClassPersister(model, cache, this, mapping);
252252
entityPersisters[model.EntityName] = cp;
253253
classMeta[model.EntityName] = cp.ClassMetadata;
@@ -263,13 +263,11 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
263263
collectionPersisters = new Dictionary<string, ICollectionPersister>();
264264
foreach (Mapping.Collection model in cfg.CollectionMappings)
265265
{
266-
var cacheRegion = model.CacheRegionName;
267-
var cache = CacheFactory.CreateCache(
266+
var cache = GetCacheConcurrencyStrategy(
267+
model.CacheRegionName,
268268
model.CacheConcurrencyStrategy,
269-
cacheRegion,
270269
model.Owner.IsMutable,
271-
settings,
272-
GetOrBuild);
270+
caches);
273271
var persister = PersisterFactory.CreateCollectionPersister(model, cache, this);
274272
collectionPersisters[model.Role] = persister;
275273
IType indexType = persister.IndexType;
@@ -371,16 +369,17 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
371369

372370
if (settings.IsQueryCacheEnabled)
373371
{
374-
var updateTimestampsCacheName = typeof(StandardQueryCache).Name;
375-
updateTimestampsCache = new UpdateTimestampsCache(GetOrBuild(updateTimestampsCacheName));
372+
var updateTimestampsCacheName = typeof(UpdateTimestampsCache).Name;
373+
updateTimestampsCache = new UpdateTimestampsCache(BuildCache(updateTimestampsCacheName, updateTimestampsCacheName));
376374
var queryCacheName = typeof(StandardQueryCache).FullName;
377375
queryCache = settings.QueryCacheFactory.GetQueryCache(
378376
queryCacheName,
379377
updateTimestampsCache,
380378
settings,
381379
properties,
382-
GetOrBuild(queryCacheName));
380+
BuildCache(queryCacheName, QueryCacheType));
383381
queryCaches = new ConcurrentDictionary<string, Lazy<IQueryCache>>();
382+
queryCaches.TryAdd(queryCacheName, new Lazy<IQueryCache>(() => queryCache));
384383
}
385384
else
386385
{
@@ -417,6 +416,30 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings
417416
entityNotFoundDelegate = enfd;
418417
}
419418

419+
private ICacheConcurrencyStrategy GetCacheConcurrencyStrategy(
420+
string cacheRegion,
421+
string strategy,
422+
bool isMutable,
423+
Dictionary<Tuple<string, string>, ICacheConcurrencyStrategy> caches)
424+
{
425+
var cacheKey = new Tuple<string, string>(cacheRegion, strategy);
426+
if (!caches.TryGetValue(cacheKey, out var cache))
427+
{
428+
cache = CacheFactory.CreateCache(
429+
strategy,
430+
cacheRegion,
431+
settings,
432+
BuildCache);
433+
if (cache != null)
434+
caches.Add(cacheKey, cache);
435+
}
436+
437+
if (cache != null && isMutable && strategy == CacheFactory.ReadOnly)
438+
log.Warn("read-only cache configured for mutable: {0}", name);
439+
440+
return cache;
441+
}
442+
420443
public EventListeners EventListeners
421444
{
422445
get { return eventListeners; }
@@ -844,8 +867,6 @@ public void Close()
844867

845868
if (settings.IsQueryCacheEnabled)
846869
{
847-
queryCache.Destroy();
848-
849870
foreach (var cache in queryCaches.Values)
850871
{
851872
cache.Value.Destroy();
@@ -1034,34 +1055,41 @@ public UpdateTimestampsCache UpdateTimestampsCache
10341055

10351056
public IDictionary<string, ICache> GetAllSecondLevelCacheRegions()
10361057
{
1037-
// ToArray creates a moment in time snapshot
1038-
return allCacheRegions.ToArray().ToDictionary(kv => kv.Key, kv => kv.Value);
1058+
return
1059+
allCachePerRegionThenType
1060+
// ToArray creates a moment in time snapshot
1061+
.ToArray()
1062+
// Caches are not unique per region, take the first one.
1063+
.ToDictionary(kv => kv.Key, kv => kv.Value.Values.First());
10391064
}
10401065

10411066
public ICache GetSecondLevelCacheRegion(string regionName)
10421067
{
1043-
ICache result;
1044-
allCacheRegions.TryGetValue(regionName, out result);
1045-
return result;
1068+
if (!allCachePerRegionThenType.TryGetValue(regionName, out var result))
1069+
return null;
1070+
// Caches are not unique per region, take the first one.
1071+
return result.Values.First();
10461072
}
10471073

1048-
/// <summary>
1049-
/// Get an existing <see cref="ICache"/> or build a new one if not already existing.
1050-
/// </summary>
1051-
/// <param name="cacheRegion">The (unprefixed) cache region of the cache to get or build.</param>
1052-
/// <returns>A cache.</returns>
1053-
private ICache GetOrBuild(string cacheRegion)
1074+
private ICache BuildCache(string cacheRegion, string type)
10541075
{
1055-
// If run concurrently for the same region, this may built many caches for the same region.
1076+
// If run concurrently for the same region and type, this may built many caches for the same region and type.
10561077
// Currently only GetQueryCache may be run concurrently, and its implementation prevents
10571078
// concurrent creation call for the same region, so this will not happen.
10581079
// Otherwise the dictionary will have to be changed for using a lazy, see
10591080
// https://stackoverflow.com/a/31637510/1178314
10601081
var prefix = settings.CacheRegionPrefix;
10611082
if (!string.IsNullOrEmpty(prefix))
10621083
cacheRegion = prefix + '.' + cacheRegion;
1063-
return allCacheRegions.GetOrAdd(cacheRegion,
1064-
cr => settings.CacheProvider.BuildCache(cr, properties));
1084+
var cachesPerType = allCachePerRegionThenType.GetOrAdd(cacheRegion, cr => new ConcurrentDictionary<string, ICache>());
1085+
var cache = settings.CacheProvider.BuildCache(cacheRegion, properties);
1086+
if (!cachesPerType.TryAdd(type, cache))
1087+
{
1088+
cache.Destroy();
1089+
throw new InvalidOperationException($"A cache has already been built for region {cacheRegion} and type {type}.");
1090+
}
1091+
1092+
return cache;
10651093
}
10661094

10671095
/// <summary> Statistics SPI</summary>
@@ -1097,7 +1125,7 @@ public IQueryCache GetQueryCache(string cacheRegion)
10971125
updateTimestampsCache,
10981126
settings,
10991127
properties,
1100-
GetOrBuild(cr)))).Value;
1128+
BuildCache(cr, QueryCacheType)))).Value;
11011129
}
11021130

11031131
public void EvictQueries()

0 commit comments

Comments
 (0)