Nic*_*cht 5 c# asp.net nhibernate asp.net-mvc multithreading
我有一个生产应用程序(IIS8,MVC5,nHibernate DAL),我注意到最近的高CPU使用率.循环应用程序池修复它但在从服务器执行一些诊断和内存转储以分析问题后,我注意到多个线程的一致模式试图枚举相同的集合.最常见的一点是应用程序检查用户角色的位置.我怀疑这可能更多的是这个代码是为每个验证权限的请求运行的,所以它更可能是它被卡住的集合?
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();
}
Run Code Online (Sandbox Code Playgroud)
我的CurrentUser对象有一个简单的接口,包含从依赖项解析器注入的用户的详细信息.我已经验证了UserId存在且有效,这一切都非常简单.当我看到这两个请求被挂起的转储时,我得到一个警告,多个线程正在枚举一个集合.当我检查转储中的两个线程时,我看到几乎相同的堆栈跟踪.(我已经在堆栈跟踪中重命名了一些命名空间细节,但它没有改变).两个请求中的userId(以及结果配置文件)是相同的,因此它似乎是由于两个独立的线程试图在几乎同时从数据库加载同一个对象.
堆栈跟踪在下面,但我不知道从这里去哪里以解决这个问题.
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
Run Code Online (Sandbox Code Playgroud)
我目前正在ActionFilter中打开我的数据库事务,该事件在OnActionExecuting()发生时打开事务,然后在发生时提交/回滚事务OnActionExecuted().
我正在使用StructureMap(v2.6.4.1)进行依赖注入,我的数据持久性的相关行如下所示.
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());
Run Code Online (Sandbox Code Playgroud)
更新:我还在处理这个问题,如果这是nhibernate的问题,或者我如何配置它,我会喜欢一些提示?由于19个独立的线程试图枚举同一个集合,我的应用程序锁定到了我们今天必须重新启动到服务器的程度.
下面提到它可能是SecurityService的生命周期范围问题,我同意这是一种可能性.目前我通过Structuremap通过依赖注入提供服务(2.6的最新版本发布,尚未更新到3.x).我在下面简要介绍了我希望简洁但仍然相关的细节.
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
}
Run Code Online (Sandbox Code Playgroud)
我的依赖解析器设置....
For<ISecurityService>().Use<SecurityService>();
For(typeof (IRepository<>)).Use(typeof (NHibernateRepository<>));
//And then the ISession is commented above.
Run Code Online (Sandbox Code Playgroud)
nHibernate配置了WebSessionContext的内部上下文ISessionFactory是Singleton ISession是HybridHttpOrThreadLocalScoped ISecurityService和IRepository都是默认的瞬态
角色被缓存,如果没有找到,那么系统会调用安全服务上的GetRoles方法,我想我可能会遇到一个问题,它调用GetRoles的次数比它需要的多,但这超出了范围.我现在有多个并发枚举问题.
更新: 所以我很困惑,我今天得到同样的问题来调用GetReference.18个单独的线程枚举枚举相同的集合,但这个是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
Run Code Online (Sandbox Code Playgroud)
在调用GetReference之后还有更多,但它与我所知道的问题无关?
在我看来,您使用的 NHibernate 版本早于 4.0.0(2014 年 8 月 17 日发布)。如果您使用的是较新版本,请忽略此答案。
NHibernate 存在并发问题 - 请参阅此处:
有时我们的 IIS 进程开始使用 100% CPU。在内存转储中,我们看到许多线程位于 Dictionary FindEntry 方法中,该方法是从 ColumnNameCache.GetIndexForColumnName 调用的。
此问题已解决,但补丁仅合并到版本 4.0.0。
问题是,当底层集合被修改时,通用字典会进入无限循环,即两个线程正在尝试读取值,一个正在写入。
来自文档:
只要不修改集合,字典就可以同时支持多个读取器。即便如此,通过集合进行枚举本质上并不是线程安全的过程。在枚举与写访问发生冲突的极少数情况下,必须在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。
线程不安全版本:https://github.com/nhibernate/nhibernate-core/blob/3.4.x/src/NHibernate/AdoNet/ColumnNameCache.cs
与应用的补丁相同:https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/AdoNet/ColumnNameCache.cs
关于为什么 Dictionary 不是线程安全的以及为什么 IIS 停止服务请求的详细解释:
| 归档时间: |
|
| 查看次数: |
868 次 |
| 最近记录: |