加载实体时,NHibernate的竞争条件(nhibernate race condition wh

2019-08-01 01:05发布

我有我的webapp一个NHibernate的竞争状态的问题。

我使用旧版本的log4net时(应固定在1.2.10),虽然我也遇到过很了解这种情况发生的。 正因为如此,我们已经禁用log4net的现在,由于竞争条件崩溃IIS,它是不能接受这种在生产中发生。 加载实体(见下面的堆栈跟踪),当这件事发生。 除此之外,类似的问题似乎都发生在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读取用户ID,并创建一个“WEBUSER”,与像用户id一些简单的信息的用户)。 后来,我身边有把锁,并且在完成交易,不知道这是否将是有益的用户GET请求。

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

命令会话被打开,并在需要时使用一个封闭块。

据堆栈跟踪,在StreamWriter的出现问题 - > System.Buffer,这是再次System.IO.SyncTextWriter,这应该是各地System.IO.TextWriter线程安全的包装调用。

由于这件事发生在TextWriter的,是有办法解决这个问题,使用一个线程的TextWriter?

它是安全的打开和关闭的会议上,我做它在DynamicPage的方式吗?

因为这显然是难以重现,如何做到这一点也欢迎任何想法。

[更新] NHibernate的探查告知,我们还打开和关闭会话(在使用块)在母版页,因为它需要检查一些权限,为当前用户,所以两会中每个请求打开。 我已经重构它,所以它现在不是在一个页面超打开会话,它会打开在的Application_BeginRequest在Global.asax了会议,并再次关闭它Application_EndRequest,其中会话放在HttpContext.Current.Items。

但测试没有确切方法,如果这修复它。

Answer 1:

Stamppot,谢谢你张贴这个问题StackOverflow的; 如你所知,没有在网络上可以找到有关此错误消息多少等信息。 我的团队在使用NHibernate和log4net的web应用程序遇到了类似的问题在几个月前。 (StringTemplate的可能已经介入了。)我们的“固定”通过重定向Console.Out /错误为空在Global.ascx.cs的的Application_Start()事件处理流(有效地禁止他们)的一点改进:

protected void Application_Start(object sender, EventArgs e)
{
    Console.SetOut(new System.IO.StreamWriter(System.IO.Stream.Null));
    Console.SetError(new System.IO.StreamWriter(System.IO.Stream.Null)); 
}

详细信息:在我们的例子中,“可能的竞争条件...”错误被加载相关。 在生产服务器上,此异常会偶尔出现,轰然每次都工作进程。 最后我们终于找到了如何重现它,通过运行在的短时间内多次请求淹没web应用程序的脚本。 异常堆栈迹线,当与所述的NHibernate / StringTemplate的/ log4net的源代码相关,指出了使用的Console.Out /错误方法在各种情况下记录。 似乎是这样的错误陌生的地方出现---不是这些方法被认为是线程安全的? 但是,我们应用上述解决方法后,问题立刻消失,一直没有回来。 不幸的是,其他优先事项使我们从更深的挖掘---但无论问题的根本原因,它并没有以任何其他方式表现出来。



Answer 2:

与@APW提供的解决方案的问题是,默认情况下的StreamWriter不是线程安全的。 点击此处查看: https://msdn.microsoft.com/en-us/library/system.io.streamwriter(v=vs.110).aspx

通过传递“新的StreamWriter”到Console.Set *您传递非线程安全的实例。 所以,我认为这是一次再看到类似的错误的问题。

正确的方法是用TextWriter.Synchronized方法来包装不安全Stream.Null。

using System.IO;
...
var nullStream = TextWriter.Synchronized(TextWriter.Null);
Console.SetOut(nullStream);
Console.SetError(nullStream);

UPD:请忽略这一点。 我发现,Console.SetOut包装任何流转换成TextWriter.Synchronized(...)。 证明。



文章来源: nhibernate race condition when loading entity