title: nhibernate race condition when loading entity
body: <p>I have a problem with a nhibernate race condition in my webapp.</p>
  
  <p>I am aware of this happening when using older versions of log4net (should be fixed in 1.2.10), although I have also experienced this. Because of this we have disabled log4net for now, since the race condition crashes IIS and it's unacceptable for this to happen in production. This happened when loading an entity (see stacktrace below). Besides this, a similar problem seems to have occurred in RavenDB, see this <a href="https://groups.google.com/forum/?fromgroups=#!topic/ravendb/lxb_hn74ewg">link</a>, and an example without NHibernate here <a href="http://www.syncfusion.com/support/forums/grid-windows/76832">link</a>.</p>
  
  <p>Stacktrace:</p>
  
  <pre><code>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 &gt; 0) { // logged in
  Line 107:                    _user = session.Get&lt;User&gt;(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
  </code></pre>
  
  <p>The mapping for User:</p>
  
  <pre><code>public class UserViewMapping : ClassMap&lt;User&gt;
  {
      public UserViewMapping() {
          Table("ej_user");
          Id(s =&gt; s.Id, "userID").GeneratedBy.Native();
          Map(s =&gt; s.InstituteId, "instituteID");
          Map(s =&gt; s.Email, "email");
          Map(s =&gt; s.Password, "password");
          Map(s =&gt; s.Name, "username");
          Map(s =&gt; s.ModRemarks, "mod_remarks");
          Map(s =&gt; s.LastLogin, "lastLogin");
          Map(s =&gt; s.Active, "active");
          Map(s =&gt; s.IsAcademic, "isAcademic");
          Map(s =&gt; s.Created, "created");
          Map(s =&gt; s.FirstName).Formula("(select p.firstName from ej_profile p where p.userID = userID)");
          Map(s =&gt; s.LastName).Formula("(select p.lastName from ej_profile p where p.userID = userID)");
          Map(s =&gt; s.TimeZone).Formula("(select p.timeZone from ej_profile p where p.userID = userID)");
          HasMany&lt;ProfileViewModel&gt;(s =&gt; s.Profiles)
              .Table("ej_profile")
              .KeyColumn("userID")
              .Cascade.All()
              .Inverse();
  }
  </code></pre>
  
  <p>Some details: I use two sessions for queries and commands (and two session factories) since I use a somewhat CQRS-like pattern. One session for reading objects, one for making changes (this helps me keep my domain model simple and view models and mapping possibly different from the command model). </p>
  
  <p>The race condition occurred when loading the User viewmodel in my development environment (single user), but we make sure that this will never happen in production, since it crashed IIS 7. Also, in production there will be multiple users, so maybe the error will possibly occur more often.</p>
  
  <p>Also, we have a lot of legacy code which uses System.Data and MySql.Data.MySqlClient.MySqlDataAdapter to read/write to the database. Could this be of influence? </p>
  
  <p>I'm using NHibernate 3.1.0 (will upgrade to 3.3.1GA, but this is difficult to reproduce), and fluentNhibernate for my mappings.</p>
  
  <p>The sessionfactories are created in the global.asax:</p>
  
  <pre><code>void Application_Start(object sender, EventArgs e)
  {
      QuerySessionFactory.Create(connectionString);
      CommandSessionManager.Initialize(connString);
  }
  </code></pre>
  
  <p>My pages inherit from my DynamicPage where the query session is opened, and closed:</p>
  
  <pre><code>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();
      }
  }
  </code></pre>
  
  <p>In the SessionInit (reads userID from httpcontext.session, and creates a 'webuser', a user with some simple information like userId). Later, I have put the lock around and done the user get request in a transaction, not sure if it would be useful.</p>
  
  <pre><code>    public IUser GetCurrentUser(ISession session) {
          if(_user == null) { 
              var webUser = new SessionInit().Get;
  
              if(webUser.Id &gt; 0) { // logged in
                  lock(_lock) {
                      using(var tx = session.BeginTransaction()) {
                          _user = session.Get&lt;User&gt;(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 &amp;&amp; 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;
  }
  </code></pre>
  
  <p>Command sessions are opened and closed in a using block when needed.</p>
  
  <p>According to the stacktrace, the problem occurs in the StreamWriter -> System.Buffer, which is again called by System.IO.SyncTextWriter, which is supposed to be thread-safe wrapper around System.IO.TextWriter. </p>
  
  <p>Since this happened in the TextWriter, is there a way to get around this, to use a threadsafe TextWriter?</p>
  
  <p>Is it safe to open and close the session the way I do it in DynamicPage?</p>
  
  <p>Since this is obviously difficult to reproduce, any ideas on how to do that are welcome too.</p>
  
  <p>[UPDATE]
  The NHibernate Profiler told that we also opened and closed a session (in a using block) in a master page, since it was needed to check some permissions for the current user, so two sessions were opened per request. I have refactored it, so it now instead of opening a session in a page superclass, it opens the session in the global.asax on Application_BeginRequest and closes it again on Application_EndRequest, where the session is put in HttpContext.Current.Items.</p>
  
  <p>But no sure way of testing if this fixes it.</p>