我的webapp中存在nhibernate竞争条件的问题。
我知道在使用旧版本的log4net时会发生这种情况(应该在1.2.10中修复),尽管我也经历过这种情况。因此我们暂时禁用了log4net,因为竞争条件导致IIS崩溃,并且在生产中发生这种情况是不可接受的。这在加载实体时发生(请参阅下面的stacktrace)。除此之外,RavenDB中似乎也出现了类似的问题,请参阅此内容 链接,这里没有NHibernate的例子 链接。
堆栈跟踪:
Server Error in '/' Application.
Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.IndexOutOfRangeException: Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader.
Source Error:
Line 105:
Line 106: if(webUser.Id > 0) { // logged in
Line 107: _user = session.Get<User>(webUser.Id);
Line 108: if(_user == null) { // session exists, but no user in DB with this id
Line 109: new SessionInit().Remove();
Source File: \App_Code\SessionInit.cs Line: 107
Stack Trace:
[IndexOutOfRangeException: Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader.]
System.Buffer.InternalBlockCopy(Array src, Int32 srcOffsetBytes, Array dst, Int32 dstOffsetBytes, Int32 byteCount) +0
System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count) +117
System.IO.TextWriter.WriteLine(String value) +204
System.IO.SyncTextWriter.WriteLine(String value) +63
NHibernate.AdoNet.AbstractBatcher.ExecuteReader(IDbCommand cmd) +71
NHibernate.Loader.Loader.GetResultSet(IDbCommand st, Boolean autoDiscoverTypes, Boolean callable, RowSelection selection, ISessionImplementor session) +580
NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +275
NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) +205
NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister) +590
[GenericADOException: could not load an entity: [app.Presentation.User#338][SQL: SELECT user0_.userID as userID24_0_, user0_.instituteID as institut2_24_0_, user0_.email as email24_0_, user0_.password as password24_0_, user0_.username as username24_0_, user0_.mod_remarks as mod6_24_0_, user0_.lastLogin as lastLogin24_0_, user0_.active as active24_0_, user0_.isAcademic as isAcademic24_0_, user0_.created as created24_0_, (select p.firstName from ej_profile p where p.userID = user0_.userID) as formula11_0_, (select p.lastName from ej_profile p where p.userID = user0_.userID) as formula12_0_, (select p.timeZone from ej_profile p where p.userID = user0_.userID) as formula13_0_ FROM ej_user user0_ WHERE user0_.userID=?]]
NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister) +960
NHibernate.Loader.Entity.AbstractEntityLoader.Load(ISessionImplementor session, Object id, Object optionalObject, Object optionalId) +76
NHibernate.Loader.Entity.AbstractEntityLoader.Load(Object id, Object optionalObject, ISessionImplementor session) +32
NHibernate.Event.Default.DefaultLoadEventListener.LoadFromDatasource(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) +173
NHibernate.Event.Default.DefaultLoadEventListener.Load(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) +181
NHibernate.Event.Default.DefaultLoadEventListener.OnLoad(LoadEvent event, LoadType loadType) +1019
NHibernate.Impl.SessionImpl.FireLoad(LoadEvent event, LoadType loadType) +403
NHibernate.Impl.SessionImpl.Get(String entityName, Object id) +469
NHibernate.Impl.SessionImpl.Get(Type entityClass, Object id) +374
NHibernate.Impl.SessionImpl.Get(Object id) +391
SessionInit.GetCurrentUser(ISession session) in j:\dev\app\app_wwwroot\App_Code\SessionInit.cs:107
DynamicPage.OnPreInit(EventArgs e) in j:\dev\app\app_wwwroot\App_Code\DynamicPage.cs:24
MemberPage.OnPreInit(EventArgs e) in j:\dev\app\app_wwwroot\App_Code\MemberPage.cs:20
members_stocks_Default.OnPreInit(EventArgs e) in j:\dev\app\app_wwwroot\members\Default.aspx.cs:28
System.Web.UI.Page.PerformPreInit() +49
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1716
用户的映射:
public class UserViewMapping : ClassMap<User>
{
public UserViewMapping() {
Table("ej_user");
Id(s => s.Id, "userID").GeneratedBy.Native();
Map(s => s.InstituteId, "instituteID");
Map(s => s.Email, "email");
Map(s => s.Password, "password");
Map(s => s.Name, "username");
Map(s => s.ModRemarks, "mod_remarks");
Map(s => s.LastLogin, "lastLogin");
Map(s => s.Active, "active");
Map(s => s.IsAcademic, "isAcademic");
Map(s => s.Created, "created");
Map(s => s.FirstName).Formula("(select p.firstName from ej_profile p where p.userID = userID)");
Map(s => s.LastName).Formula("(select p.lastName from ej_profile p where p.userID = userID)");
Map(s => s.TimeZone).Formula("(select p.timeZone from ej_profile p where p.userID = userID)");
HasMany<ProfileViewModel>(s => s.Profiles)
.Table("ej_profile")
.KeyColumn("userID")
.Cascade.All()
.Inverse();
}
一些细节:我使用两个会话进行查询和命令(以及两个会话工厂),因为我使用了类似CQRS的模式。一个用于读取对象的会话,一个用于进行更改的会话(这有助于保持我的域模型简单并且可以查看模型和映射可能与命令模型不同)。
在我的开发环境(单用户)中加载用户视图模型时发生了竞争条件,但我们确保在生产中永远不会发生这种情况,因为它崩溃了IIS 7.此外,在生产中会有多个用户,所以可能是错误可能会更频繁地发生。
此外,我们有许多遗留代码,它们使用System.Data和MySql.Data.MySqlClient.MySqlDataAdapter来读/写数据库。这会有影响吗?
我正在使用NHibernate 3.1.0(将升级到3.3.1GA,但这很难重现),并且fluentNhibernate用于我的映射。
sessionfactories在global.asax中创建:
void Application_Start(object sender, EventArgs e)
{
QuerySessionFactory.Create(connectionString);
CommandSessionManager.Initialize(connString);
}
我的页面继承自我的DynamicPage,其中查询会话打开并关闭:
public class DynamicPage : System.Web.UI.Page
{
protected override void OnPreInit(EventArgs e)
{
Session = QuerySessionFactory.Instance.OpenSession();
}
protected override void OnUnload(EventArgs e) {
base.OnUnload(e);
Session.Close();
}
}
在SessionInit中(从httpcontext.session读取userID,并创建一个'webuser',一个用户带有一些像userId这样的简单信息)。后来,我已经锁定并在事务中完成了用户获取请求,不确定它是否有用。
public IUser GetCurrentUser(ISession session) {
if(_user == null) {
var webUser = new SessionInit().Get;
if(webUser.Id > 0) { // logged in
lock(_lock) {
using(var tx = session.BeginTransaction()) {
_user = session.Get<User>(webUser.Id);
tx.Commit();
}
}
if(_user == null) { // session exists, but no user in DB with this id
new SessionInit().Remove();
}
((User)_user)._currentUser = webUser;
} else {
if(webUser is CurrentUser && webUser.Id == 0) {
if(HttpContext.Current.Session != null) {
HttpContext.Current.Response.Cookies.Remove("ASPSESSID");
HttpContext.Current.Request.Cookies.Remove("ASPSESSID");
HttpContext.Current.Session.RemoveAll();
HttpContext.Current.Session.Abandon();
}
if(HttpContext.Current.Request.Url.Host.Contains("members"))
HttpContext.Current.Response.Redirect("/login");
} else
if(webUser.Id == 0) {
var userId = webUser.Id;
var userName = webUser.UserName;
var loginUrl = webUser.LoginUrl;
var clientIp = webUser.ClientIp;
var isAdmin = webUser.IsAdmin();
return new eLab.Presentation.Visitor(userId, userName, loginUrl, clientIp, isAdmin, webUser.Theme);
}
}
if (_user == null)
return new eLab.Presentation.Visitor(webUser.Id, webUser.UserName, webUser.LoginUrl, webUser.ClientIp, false, webUser.Theme);
}
return _user;
}
在需要时,可以在using块中打开和关闭命令会话。
根据stacktrace,问题出现在StreamWriter - > System.Buffer中,System.IO.SyncTextWriter再次调用它,它应该是System.IO.TextWriter的线程安全包装器。
由于这发生在TextWriter中,有没有办法解决这个问题,使用线程安全的TextWriter?
以我在DynamicPage中的方式打开和关闭会话是否安全?
由于这显然难以复制,所以关于如何做到这一点的任何想法也是受欢迎的。
[UPDATE] NHibernate Profiler告诉我们,我们还打开并关闭了主页中的会话(在一个使用块中),因为需要检查当前用户的一些权限,因此每个请求都打开了两个会话。我已经重构了它,所以它现在不是在页面超类中打开一个会话,而是在Application_BeginRequest上的global.asax中打开会话,并在Application_EndRequest上再次关闭它,其中会话放在HttpContext.Current.Items中。
但是没有确定的方法来测试它是否能解决问题。