NHibernate的列举多个线程在同一集合 [英] nHibernate enumerating the same collection on multiple threads

查看:252
本文介绍了NHibernate的列举多个线程在同一集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个生产应用程序(IIS8,MVC5,NHibernate的DAL)和我注意到,CPU使用率过高为晚。骑自行车的应用程序池修复,但做一些诊断和内存后从服务器转储来分析这个问题,我注意到多个线程试图枚举同一集合的统一的模式。最常见的点是该应用检查用户的角色。我怀疑这可能是更多的做的事实,这code是跑了为每个请求核实的权限,所以它更可能是它被贴在收藏?

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

我的currentUser对象有包含用户的详细信息,从依赖解析器注入一个简单的界面。我已验证该用户ID为present和有效的,它的所有pretty直线前进。当我看了看,当这两个要求是雄的垃圾场,我得到了一个警告,多线程被列举的集合。当我在转储检查了两个线程,我看到几乎相同的堆栈跟踪。 (我已经改名为一些在堆栈跟踪名称空间的细节,但它是其它不变)。在两个请求的用户ID(和所得的轮廓)是相同的,所以会出现这是由于两个独立的线程试图在几乎相同的时间,以从数据库装载同一对象

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 

目前我打开我的数据库事务中,当 OnActionExecuting()打开发生交易的ActionFilter,然后提交/回滚事务时 OnActionExecuted()发生。

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.

我使用StructureMap(v2.6.4.1)对我的依赖注入,是相关行对我的数据持久如下。

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());

更新:我还在处理这个,他会喜欢的,如果这是与NHibernate问题的一些技巧,或者我如何配置它?我有应用程序锁定到我们不得不重新启动服务器,因为今天的19个独立的线程试图枚举同一集合点。

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.

据下面提到,这很可能与SecurityService,这我同意这样的可能性寿命范围界定问题。目前,我有经由Structuremap依赖注入所提供的服务(2.6最新版本发布,还没有更新到尚未3.x中)。其中的细节我已经下面简要为我所希望的是简洁,但仍然相关细节。

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配置有WebSessionContext的内部上下文
ISessionFactory是辛格尔顿
ISession的是HybridHttpOrThreadLocalScoped
ISecurityService和IRepository都双双离去牛逼短暂的默认

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

该角色被缓存,如果没有找到,则系统会调用上的安全服务,我想我可能有一个问题,它往往比它需要调用GetRoles,方法getRoles但这是外我现在具有多个并发计数问题的范围。

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.

更新:
所以我百思不得其解,我今天得到了同样的问题,为到GetReference打电话。 18独立的线程卡住列举相同的集合,但是这一次是内部NHibernate的。

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

有是调用GetReference经过,但它不是真正相关的问题,从我可以告诉?

There was more after the call to GetReference but it's not really related to the problem from what I can tell?

推荐答案

它看起来像你正在使用NHibernate的年纪比4.0.0(发布2014年8月17日)。如果使用的是较新的版本只是忽略这个答案。

To me it looks like you are using NHibernate older than 4.0.0 (released 17 August 2014). If you are using newer version just ignore this answer.

有与NHibernate的并发问题 - 请参见这里

There was a concurrency issue with NHibernate - see here:

有时我们的IIS进程开始使用100%的CPU。在内存转储
  我们看到很多的线程在字典FindEntry方法,
  一个从ColumnNameCache.GetIndexForColumnName称为

Sometimes our IIS process starting to use 100% CPU. In a memory dump we see that lots of threads is in the Dictionary FindEntry method, that is called from the ColumnNameCache.GetIndexForColumnName.

这已经解决了,但补丁已经只有合并4.0.0版本。

This has been resolved but patch has been merged only to version 4.0.0.

问题是,通用字典正在进入无限循环,当底层集合已被修改,即两个线程试图读取值与一个写作。

The problem was that generic Dictionary is getting into infinite loop when underlying collection has been modified, i.e. two threads are trying to read value and one is writing.

文档:

一个字典可以支持多个读者同时,
  只要集合不被修改。即使如此,列举
  通过一个集合本质上不是一个线程安全的过程。在
  其中,枚举与写争辩极少数情况下访问时,
  集合必须在整个枚举过程中被锁定。以允许
  收集由多个线程用于读取和写入访问,
  您必须实现自己的同步。

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

主题不安全的版本:<一href=\"https://github.com/nhibernate/nhibernate-core/blob/3.4.x/src/NHibernate/AdoNet/ColumnNameCache.cs\" rel=\"nofollow\">https://github.com/nhibernate/nhibernate-core/blob/3.4.x/src/NHibernate/AdoNet/ColumnNameCache.cs

与应用补丁是相同的:<一href=\"https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/AdoNet/ColumnNameCache.cs\" rel=\"nofollow\">https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/AdoNet/ColumnNameCache.cs

为什么为什么IIS停止提供服务请求字典不是线程安全的,更详细的解释:

Longer explanation of why Dictionary is not thread-safe and why IIS stops serving requests:


  1. http://blogs.msdn.com/b/tess/archive/2009/12/21/high-cpu-in-net-app-using-a-static-generic-dictionary.aspx

  2. http://improve.dk/debugging -in生产部分-2潜种族条件错误/

  3. ASP.NET杭 - 通用字典的并发问题引起GC僵局

  1. http://blogs.msdn.com/b/tess/archive/2009/12/21/high-cpu-in-net-app-using-a-static-generic-dictionary.aspx
  2. http://improve.dk/debugging-in-production-part-2-latent-race-condition-bugs/
  3. ASP.NET Hang - Generic Dictionary concurrency issues causes GC deadlock

这篇关于NHibernate的列举多个线程在同一集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆