From 4e2458c010f664bb8e19e7b51fd893701e98e074 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:23:11 -0600 Subject: [PATCH 01/10] Make SessionFactoryObjectFactory safe to use in a threaded environment. Avoid situations where the `IDictionary`'s can be messed up from readers and writers interacting with them at the same time --- src/NHibernate/Impl/SessionFactoryObjectFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs index 2936f5c2405..190bcb4b525 100644 --- a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs +++ b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -21,8 +22,8 @@ public static class SessionFactoryObjectFactory { private static readonly INHibernateLogger log; - private static readonly IDictionary Instances = new Dictionary(); - private static readonly IDictionary NamedInstances = new Dictionary(); + private static readonly IDictionary Instances = new ConcurrentDictionary(); + private static readonly IDictionary NamedInstances = new ConcurrentDictionary(); /// static SessionFactoryObjectFactory() From f27a03678b5cd7f64bbc9b3588daf312b61c2ae7 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:41:24 -0600 Subject: [PATCH 02/10] Make SessionFactoryImpl.collectionPersisters a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 8ff1e9d1599..4817ed34da6 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -110,7 +110,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object [NonSerialized] private readonly IDictionary collectionMetadata; [NonSerialized] - private readonly Dictionary collectionPersisters; + private readonly IDictionary collectionPersisters; [NonSerialized] private readonly ILookup collectionPersistersSpaces; @@ -296,7 +296,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings classMetadata = new ReadOnlyDictionary(classMeta); Dictionary> tmpEntityToCollectionRoleMap = new Dictionary>(); - collectionPersisters = new Dictionary(); + var collPersisters = new Dictionary(); foreach (Mapping.Collection model in cfg.CollectionMappings) { var cache = GetCacheConcurrencyStrategy( @@ -306,7 +306,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings model.OwnerEntityName, caches); var persister = PersisterFactory.CreateCollectionPersister(model, cache, this); - collectionPersisters[model.Role] = persister; + collPersisters[model.Role] = persister; IType indexType = persister.IndexType; if (indexType != null && indexType.IsAssociationType && !indexType.IsAnyType) { @@ -333,6 +333,8 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings } } + collectionPersisters = new ReadOnlyDictionary(collPersisters); + collectionPersistersSpaces = collectionPersisters .SelectMany(x => x.Value.CollectionSpaces.Select(y => new { QuerySpace = y, Persister = x.Value })) .ToLookup(x => x.QuerySpace, x => x.Persister); From 96218d167a8454831d791547552faaef0355500a Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:44:22 -0600 Subject: [PATCH 03/10] Make SessionFactoryImpl.entityPersisters a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 4817ed34da6..4b6bbfc7b69 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -265,7 +265,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings #region Persisters var caches = new Dictionary, ICacheConcurrencyStrategy>(); - entityPersisters = new Dictionary(); + var tmpEntityPersisters = new Dictionary(); implementorToEntityName = new Dictionary(); Dictionary classMeta = new Dictionary(); @@ -280,7 +280,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings model.EntityName, caches); var cp = PersisterFactory.CreateClassPersister(model, cache, this, mapping); - entityPersisters[model.EntityName] = cp; + tmpEntityPersisters[model.EntityName] = cp; classMeta[model.EntityName] = cp.ClassMetadata; if (model.HasPocoRepresentation) @@ -289,6 +289,8 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings } } + entityPersisters = new ReadOnlyDictionary(tmpEntityPersisters); + entityPersistersSpaces = entityPersisters .SelectMany(x => x.Value.QuerySpaces.Select(y => new { QuerySpace = y, Persister = x.Value })) .ToLookup(x => x.QuerySpace, x => x.Persister); @@ -296,7 +298,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings classMetadata = new ReadOnlyDictionary(classMeta); Dictionary> tmpEntityToCollectionRoleMap = new Dictionary>(); - var collPersisters = new Dictionary(); + var tmpCollectionPersisters = new Dictionary(); foreach (Mapping.Collection model in cfg.CollectionMappings) { var cache = GetCacheConcurrencyStrategy( @@ -306,7 +308,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings model.OwnerEntityName, caches); var persister = PersisterFactory.CreateCollectionPersister(model, cache, this); - collPersisters[model.Role] = persister; + tmpCollectionPersisters[model.Role] = persister; IType indexType = persister.IndexType; if (indexType != null && indexType.IsAssociationType && !indexType.IsAnyType) { @@ -333,7 +335,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings } } - collectionPersisters = new ReadOnlyDictionary(collPersisters); + collectionPersisters = new ReadOnlyDictionary(tmpCollectionPersisters); collectionPersistersSpaces = collectionPersisters .SelectMany(x => x.Value.CollectionSpaces.Select(y => new { QuerySpace = y, Persister = x.Value })) From 401377b76ea6bf22b8b558f847c2abc5727bc841 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:47:44 -0600 Subject: [PATCH 04/10] Make SessionFactoryImpl.implementorToEntityName a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 4b6bbfc7b69..1609f591e47 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -266,7 +266,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings var caches = new Dictionary, ICacheConcurrencyStrategy>(); var tmpEntityPersisters = new Dictionary(); - implementorToEntityName = new Dictionary(); + var tmpImplementorToEntityName = new Dictionary(); Dictionary classMeta = new Dictionary(); @@ -285,11 +285,12 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings if (model.HasPocoRepresentation) { - implementorToEntityName[model.MappedClass] = model.EntityName; + tmpImplementorToEntityName[model.MappedClass] = model.EntityName; } } entityPersisters = new ReadOnlyDictionary(tmpEntityPersisters); + implementorToEntityName = new ReadOnlyDictionary(tmpImplementorToEntityName); entityPersistersSpaces = entityPersisters .SelectMany(x => x.Value.QuerySpaces.Select(y => new { QuerySpace = y, Persister = x.Value })) From 37e8302dee6dc00a21a2ff20dafd9350acfc098d Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:49:43 -0600 Subject: [PATCH 05/10] Make SessionFactoryImpl.imports a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 1609f591e47..d04ca338b60 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -141,7 +141,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object private readonly Dictionary identifierGenerators; [NonSerialized] - private readonly Dictionary imports; + private readonly IDictionary imports; [NonSerialized] private readonly IInterceptor interceptor; @@ -357,7 +357,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings sqlResultSetMappings = new Dictionary(cfg.SqlResultSetMappings); #endregion - imports = new Dictionary(cfg.Imports); + imports = new ReadOnlyDictionary(cfg.Imports); #region after *all* persisters and named queries are registered foreach (IEntityPersister persister in entityPersisters.Values) From 37fb460894e5f70be4a0c4e7e16cc53031ff02e4 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:50:06 -0600 Subject: [PATCH 06/10] Make SessionFactoryImpl.imports a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index d04ca338b60..3dc8791f18c 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -141,7 +141,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object private readonly Dictionary identifierGenerators; [NonSerialized] - private readonly IDictionary imports; + private readonly IReadOnlyDictionary imports; [NonSerialized] private readonly IInterceptor interceptor; From 5a64a8bb289cdb3054b176b061e056c5008df1e9 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:52:10 -0600 Subject: [PATCH 07/10] Make SessionFactoryImpl.namedQueries a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 3dc8791f18c..49080e59fbd 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -147,7 +147,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object private readonly IInterceptor interceptor; private readonly string name; [NonSerialized] - private readonly Dictionary namedQueries; + private readonly IReadOnlyDictionary namedQueries; [NonSerialized] private readonly Dictionary namedSqlQueries; @@ -352,7 +352,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings #endregion #region Named Queries - namedQueries = new Dictionary(cfg.NamedQueries); + namedQueries = new ReadOnlyDictionary(cfg.NamedQueries); namedSqlQueries = new Dictionary(cfg.NamedSQLQueries); sqlResultSetMappings = new Dictionary(cfg.SqlResultSetMappings); #endregion From 718912f903d5f497ed644de396eb1d3521f6e2e4 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:52:49 -0600 Subject: [PATCH 08/10] Make SessionFactoryImpl.sqlResultSetMappings a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 49080e59fbd..03f274dafa0 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -150,7 +150,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object private readonly IReadOnlyDictionary namedQueries; [NonSerialized] - private readonly Dictionary namedSqlQueries; + private readonly IReadOnlyDictionary namedSqlQueries; [NonSerialized] private readonly IDictionary properties; @@ -168,7 +168,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object [NonSerialized] private readonly SQLFunctionRegistry sqlFunctionRegistry; [NonSerialized] - private readonly Dictionary sqlResultSetMappings; + private readonly ReadOnlyDictionary sqlResultSetMappings; [NonSerialized] private readonly UpdateTimestampsCache updateTimestampsCache; [NonSerialized] @@ -353,8 +353,8 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings #region Named Queries namedQueries = new ReadOnlyDictionary(cfg.NamedQueries); - namedSqlQueries = new Dictionary(cfg.NamedSQLQueries); - sqlResultSetMappings = new Dictionary(cfg.SqlResultSetMappings); + namedSqlQueries = new ReadOnlyDictionary(cfg.NamedSQLQueries); + sqlResultSetMappings = new ReadOnlyDictionary(cfg.SqlResultSetMappings); #endregion imports = new ReadOnlyDictionary(cfg.Imports); From 9b9c3a1db17c0f3a850d8a9b5ed201ca2424a8b6 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 12:55:48 -0600 Subject: [PATCH 09/10] Line up private property types with those that can be safely considered ReadOnlyDictionary's. --- src/NHibernate/Impl/SessionFactoryImpl.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 03f274dafa0..7bc2afcbf9c 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -105,23 +105,23 @@ public void HandleEntityNotFound(string entityName, string propertyName, object new ConcurrentDictionary(); [NonSerialized] - private readonly IDictionary classMetadata; + private readonly IReadOnlyDictionary classMetadata; [NonSerialized] - private readonly IDictionary collectionMetadata; + private readonly IReadOnlyDictionary collectionMetadata; [NonSerialized] - private readonly IDictionary collectionPersisters; + private readonly IReadOnlyDictionary collectionPersisters; [NonSerialized] private readonly ILookup collectionPersistersSpaces; [NonSerialized] - private readonly IDictionary> collectionRolesByEntityParticipant; + private readonly IReadOnlyDictionary> collectionRolesByEntityParticipant; [NonSerialized] private readonly ICurrentSessionContext currentSessionContext; [NonSerialized] private readonly IEntityNotFoundDelegate entityNotFoundDelegate; [NonSerialized] - private readonly IDictionary entityPersisters; + private readonly IReadOnlyDictionary entityPersisters; [NonSerialized] private readonly ILookup entityPersistersSpaces; @@ -130,7 +130,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object /// /// this is a shortcut. [NonSerialized] - private readonly IDictionary implementorToEntityName; + private readonly IReadOnlyDictionary implementorToEntityName; [NonSerialized] private readonly EventListeners eventListeners; @@ -138,7 +138,7 @@ public void HandleEntityNotFound(string entityName, string propertyName, object [NonSerialized] private readonly Dictionary filters; [NonSerialized] - private readonly Dictionary identifierGenerators; + private readonly IReadOnlyDictionary identifierGenerators; [NonSerialized] private readonly IReadOnlyDictionary imports; From 28205fc71fb3a98a8ce8bb75091099dd4f0502f9 Mon Sep 17 00:00:00 2001 From: Michael Kent Date: Sat, 8 Mar 2025 13:04:25 -0600 Subject: [PATCH 10/10] CLeanup and make SessionFactoryImpl.identifierGenerators a ReadOnlyDictionary to line up with how collectionMetadata is built and improve determistic behavior under accidental multithreading --- src/NHibernate/Impl/SessionFactoryImpl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/NHibernate/Impl/SessionFactoryImpl.cs b/src/NHibernate/Impl/SessionFactoryImpl.cs index 7bc2afcbf9c..1e89533b641 100644 --- a/src/NHibernate/Impl/SessionFactoryImpl.cs +++ b/src/NHibernate/Impl/SessionFactoryImpl.cs @@ -105,10 +105,10 @@ public void HandleEntityNotFound(string entityName, string propertyName, object new ConcurrentDictionary(); [NonSerialized] - private readonly IReadOnlyDictionary classMetadata; + private readonly IDictionary classMetadata; [NonSerialized] - private readonly IReadOnlyDictionary collectionMetadata; + private readonly IDictionary collectionMetadata; [NonSerialized] private readonly IReadOnlyDictionary collectionPersisters; [NonSerialized] @@ -248,7 +248,7 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings #endregion #region Generators - identifierGenerators = new Dictionary(); + var tmpIdentifierGenerators = new Dictionary(); foreach (PersistentClass model in cfg.ClassMappings) { if (!model.IsInherited) @@ -257,9 +257,10 @@ public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings model.Identifier.CreateIdentifierGenerator(settings.Dialect, settings.DefaultCatalogName, settings.DefaultSchemaName, (RootClass)model); - identifierGenerators[model.EntityName] = generator; + tmpIdentifierGenerators[model.EntityName] = generator; } } + identifierGenerators = new ReadOnlyDictionary(tmpIdentifierGenerators); #endregion #region Persisters