I have a production application (IIS8, MVC5, nHibernate DAL) and I'm noticing high CPU usage as of late. Cycling the app pool fixes it but after doing some diagnostics and memory dumps from the server to analyze the issue, I noticed a consistent pattern of multiple threads trying to enumerate the same collection. The most common point is where the app checks the roles of the user. I suspect this might be more do to the fact that this code is ran for every request to verify permissions, so it's more likely to be the collection it gets stuck on?
public IList<Role> GetRoles(string username)
{
var login = GetLoginForUser(username);
return !login.Groups.Any() ? new List<Role>() : login.Groups.SelectMany(x => x.Roles).OrderBy(x => x.DisplayName).ToList();
}
My CurrentUser object there is a simple interface containing the details of the user, injected from a dependency resolver. I have verified that the UserId is present and valid, it's all pretty straight forward. When I took a look at the dumps of when these two requests were hung, I got a warning that multiple threads were enumerating a collection. When I checked the two threads in the dump, I saw practically identical stack traces. (I've renamed some of the namespace details in the stack trace but it's otherwise unaltered). The userId (and resulting profile) in both requests are the same, so it appears it's due to two separate threads trying to load the same object from the database at practically the same time.
The stack trace is below, but I'm not sure where to go from here in order to fix this.
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].FindEntry(System.__Canon)+129
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].TryGetValue(System.__Canon, System.Nullable`1<Int32> ByRef)+12
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String)+29
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16
NHibernate.Persister.Collection.AbstractCollectionPersister.ReadKey(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor)+14
NHibernate.Persister.Collection.AbstractCollectionPersister.ReadKey(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor)+14
NHibernate.Loader.Loader.ReadCollectionElement(System.Object, System.Object, NHibernate.Persister.Collection.ICollectionPersister, NHibernate.Loader.ICollectionAliases, System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+34
NHibernate.Loader.Loader.ReadCollectionElement(System.Object, System.Object, NHibernate.Persister.Collection.ICollectionPersister, NHibernate.Loader.ICollectionAliases, System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+34
NHibernate.Loader.Loader.ReadCollectionElements(System.Object[], System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+d2
NHibernate.Loader.Loader.ReadCollectionElements(System.Object[], System.Data.IDataReader, NHibernate.Engine.ISessionImplementor)+d2
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+ab
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+ab
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f
NHibernate.Loader.Loader.LoadCollection(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType)+de
NHibernate.Loader.Loader.LoadCollection(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType)+de
NHibernate.Loader.Collection.CollectionLoader.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1c
NHibernate.Loader.Collection.CollectionLoader.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1c
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1e
NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(System.Object, NHibernate.Engine.ISessionImplementor)+1e
NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(NHibernate.Event.InitializeCollectionEvent)+16d
NHibernate.Impl.SessionImpl.InitializeCollection(NHibernate.Collection.IPersistentCollection, Boolean)+1fa
NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean)+2f
NHibernate.Collection.AbstractPersistentCollection.Read()+d
NHibernate.Collection.Generic.PersistentGenericBag`1[[System.__Canon, mscorlib]].System.Collections.Generic.IEnumerable<T>.GetEnumerator()+11
System_Core_ni!System.Linq.Enumerable+<SelectManyIterator>d__14`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].MoveNext()+10c
System_Core_ni!System.Linq.Buffer`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>)+d9
System_Core_ni!System.Linq.OrderedEnumerable`1+<GetEnumerator>d__0[[System.__Canon, mscorlib]].MoveNext()+6f
System_Core_ni!System.Linq.OrderedEnumerable`1+<GetEnumerator>d__0[[System.__Canon, mscorlib]].MoveNext()+6f
mscorlib_ni!System.Collections.Generic.List`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>)+17e
System_Core_ni!System.Linq.Enumerable.ToList[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>)+3b
Company.ApplicationServices.SecurityService.GetRoles(System.String)+ef
I'm currently opening my database transaction in an ActionFilter that opens the transaction when OnActionExecuting()
happens, and then commit/rollback the transaction when OnActionExecuted()
happens.
I'm using StructureMap (v2.6.4.1) for my dependency injection, and the relevant lines for my data persistence is as follows.
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("DatabaseConnectionString"))
.CurrentSessionContext<WebSessionContext>()
// ... etc etc....
.Cache(c => c.ProviderClass<NHibernate.Caches.SysCache2.SysCacheProvider>()
.UseQueryCache()
.UseSecondLevelCache()
.UseMinimalPuts());
For<NHibernate.Cfg.Configuration>().Singleton().Use(cfg);
For<NHibernate.ISessionFactory>().Singleton()
.Use(ctx =>
{
try
{
var config = ctx.GetInstance<NHibernate.Cfg.Configuration>();
return config.BuildSessionFactory();
}
catch (SqlException ex)
{
ctx.GetInstance<IExceptionLogger>().Error(ex);
throw;
}
});
For<NHibernate.ISession>().HybridHttpOrThreadLocalScoped()
.Use(ctx => ctx.GetInstance<NHibernate.ISessionFactory>().OpenSession());
UPDATE: I'm still dealing with this and would love some tips on if this is a problem with nhibernate, or how I have it configured? I had the app lockup to the point where we had to reboot to server today because of 19 separate threads trying to enumerate the same collection.
It is mentioned below that it's likely a problem with lifetime scoping of the SecurityService, which I agree is a possibility. Currently I have the services being provided through dependency injection via Structuremap (last version of 2.6 released, haven't updated to 3.x yet). The details of which I've details briefly below for what I hope is succinct but still relevant.
public class SecurityService : ISecurityService
{
private readonly IRepository<Login> loginRepository;
public IList<Role> GetCurrentUserRoles()
{
var login = GetLoginForCurrentUser();
return GetRoles(login.Name);
}
public Login GetLoginForCurrentUser()
{
//Some logic to derive the current UserId {guid} via some resources injected into this service class.
return loginRepository.GetReference(loginId);
}
}
public class NHibernateRepository<T> : IRepository<T> where T : class
{
protected ISession Session { get; set; }
public NHibernateRepository(ISession session)
{
Session = session;
}
public T GetReference(object id)
{
return Session.Get<T>(id);
}
// Other methods typical of a repository class, nothing special
}
My dependency resolver setup....
For<ISecurityService>().Use<SecurityService>();
For(typeof (IRepository<>)).Use(typeof (NHibernateRepository<>));
//And then the ISession is commented above.
nHibernate is configured with an internal context of WebSessionContext ISessionFactory is Singleton ISession is HybridHttpOrThreadLocalScoped ISecurityService and the IRepository are both both left t the default of Transient
The roles are cached and if are not found then the system makes the call to the GetRoles method on the security service, I think I might have an issue with it calling GetRoles more often than it needs to, but that's outside of the scope of the multiple concurrent enumeration problem I'm having now.
UPDATE: So I'm baffled, I got the same issue today for a call to GetReference. 18 separate threads stuck enumerating the same collection, but this one was internal to nHibernate.
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].FindEntry(System.__Canon)+129
System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Nullable`1[[System.Int32, mscorlib]], mscorlib]].TryGetValue(System.__Canon, System.Nullable`1 ByRef)+12
NHibernate.AdoNet.ColumnNameCache.GetIndexForColumnName(System.String, NHibernate.AdoNet.ResultSetWrapper)+25
NHibernate.AdoNet.ResultSetWrapper.GetOrdinal(System.String)+e
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String)+29
NHibernate.Type.NullableType.NullSafeGet(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+16
NHibernate.Type.AbstractType.Hydrate(System.Data.IDataReader, System.String[], NHibernate.Engine.ISessionImplementor, System.Object)+14
NHibernate.Persister.Entity.AbstractEntityPersister.Hydrate(System.Data.IDataReader, System.Object, System.Object, NHibernate.Persister.Entity.ILoadable, System.String[][], Boolean, NHibernate.Engine.ISessionImplementor)+3ce
NHibernate.Loader.Loader.LoadFromResultSet(System.Data.IDataReader, Int32, System.Object, System.String, NHibernate.Engine.EntityKey, System.String, NHibernate.LockMode, NHibernate.Persister.Entity.ILoadable, NHibernate.Engine.ISessionImplementor)+118
NHibernate.Loader.Loader.InstanceNotYetLoaded(System.Data.IDataReader, Int32, NHibernate.Persister.Entity.ILoadable, NHibernate.Engine.EntityKey, NHibernate.LockMode, System.String, NHibernate.Engine.EntityKey, System.Object, System.Collections.IList, NHi+8c
NHibernate.Loader.Loader.GetRow(System.Data.IDataReader, NHibernate.Persister.Entity.ILoadable[], NHibernate.Engine.EntityKey[], System.Object, NHibernate.Engine.EntityKey, NHibernate.LockMode[], System.Collections.IList, NHibernate.Engine.ISessionImpleme+129
NHibernate.Loader.Loader.GetRowFromResultSet(System.Data.IDataReader, NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, NHibernate.LockMode[], NHibernate.Engine.EntityKey, System.Collections.IList, NHibernate.Engine.EntityKey[], Bo+97
NHibernate.Loader.Loader.DoQuery(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+1e7
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(NHibernate.Engine.ISessionImplementor, NHibernate.Engine.QueryParameters, Boolean)+7f
NHibernate.Loader.Loader.LoadEntity(NHibernate.Engine.ISessionImplementor, System.Object, NHibernate.Type.IType, System.Object, System.String, System.Object, NHibernate.Persister.Entity.IEntityPersister)+f3
NHibernate.Loader.Entity.AbstractEntityLoader.Load(NHibernate.Engine.ISessionImplementor, System.Object, System.Object, System.Object)+22
NHibernate.Loader.Entity.AbstractEntityLoader.Load(System.Object, System.Object, NHibernate.Engine.ISessionImplementor)+12
NHibernate.Persister.Entity.AbstractEntityPersister.Load(System.Object, System.Object, NHibernate.LockMode, NHibernate.Engine.ISessionImplementor)+69
NHibernate.Event.Default.DefaultLoadEventListener.LoadFromDatasource(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+84
NHibernate.Event.Default.DefaultLoadEventListener.DoLoad(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+1d7
NHibernate.Event.Default.DefaultLoadEventListener.Load(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+5e
NHibernate.Event.Default.DefaultLoadEventListener.ReturnNarrowedProxy(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType, NHibernate.Engine.IPersistenceContext, System.Object)+73
NHibernate.Event.Default.DefaultLoadEventListener.ProxyOrLoad(NHibernate.Event.LoadEvent, NHibernate.Persister.Entity.IEntityPersister, NHibernate.Engine.EntityKey, NHibernate.Event.LoadType)+cb
NHibernate.Event.Default.DefaultLoadEventListener.OnLoad(NHibernate.Event.LoadEvent, NHibernate.Event.LoadType)+120
NHibernate.Impl.SessionImpl.FireLoad(NHibernate.Event.LoadEvent, NHibernate.Event.LoadType)+140
NHibernate.Impl.SessionImpl.Get(System.String, System.Object)+148
NHibernate.Impl.SessionImpl.Get(System.Type, System.Object)+121
NHibernate.Impl.SessionImpl.Get[[System.__Canon, mscorlib]](System.Object)+143
Intellitive.Data.Repositories.NHibernateRepository`1[[System.__Canon, mscorlib]].GetReference(System.Object)+38
There was more after the call to GetReference but it's not really related to the problem from what I can tell?