2012-09-28 22 views
11

Tôi gặp sự cố với điều kiện đua nhibernate trong webapp của tôi.điều kiện đua nhibernate khi tải thực thể

Tôi biết điều này xảy ra khi sử dụng các phiên bản cũ hơn của log4net (nên được sửa trong 1.2.10), mặc dù tôi cũng đã trải nghiệm điều này. Bởi vì điều này chúng tôi đã vô hiệu hóa log4net cho bây giờ, kể từ khi tình trạng cuộc đua sụp đổ IIS và nó không thể chấp nhận cho điều này xảy ra trong sản xuất. Điều này xảy ra khi tải một thực thể (xem stacktrace bên dưới). Bên cạnh đó, một vấn đề tương tự dường như đã xảy ra trong RavenDB, xem này link, và một ví dụ mà không NHibernate ở đây link.

stacktrace:

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 

Việc lập bản đồ cho User:

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

Một số chi tiết: Tôi sử dụng hai phiên cho các truy vấn và các lệnh (và hai nhà máy phiên) kể từ khi tôi sử dụng một phần nào CQRS giống như mẫu. Một phiên để đọc các đối tượng, một để thực hiện các thay đổi (điều này giúp tôi giữ mô hình miền của mình đơn giản và xem các mô hình và ánh xạ có thể khác với mô hình lệnh).

Điều kiện chủng tộc xảy ra khi tải chế độ xem người dùng trong môi trường phát triển của tôi (người dùng đơn), nhưng chúng tôi đảm bảo rằng điều này sẽ không bao giờ xảy ra trong sản xuất, vì nó đã gặp sự cố IIS 7. Ngoài ra, trong sản xuất sẽ có nhiều người dùng, vì vậy có thể lỗi sẽ xảy ra thường xuyên hơn.

Ngoài ra, chúng tôi có nhiều mã kế thừa sử dụng System.Data và MySql.Data.MySqlClient.MySqlDataAdapter để đọc/ghi vào cơ sở dữ liệu. Điều này có thể ảnh hưởng không?

Tôi đang sử dụng NHibernate 3.1.0 (sẽ nâng cấp lên 3.3.1GA, nhưng điều này rất khó để tái tạo) và fluentNhibernate cho ánh xạ của tôi.

Các sessionfactories được tạo ra trong global.asax:

void Application_Start(object sender, EventArgs e) 
{ 
    QuerySessionFactory.Create(connectionString); 
    CommandSessionManager.Initialize(connString); 
} 

trang của tôi kế thừa từ DynamicPage của tôi, nơi các phiên truy vấn được mở ra, và đóng cửa:

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

Trong SessionInit (đọc userID từ httpcontext.session và tạo 'webuser', một người dùng có một số thông tin đơn giản như userId). Sau đó, tôi đã đặt khóa xung quanh và thực hiện người dùng nhận được yêu cầu trong một giao dịch, không chắc chắn nếu nó sẽ hữu ích.

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

Phiên lệnh được mở và đóng trong khối sử dụng khi cần.

Theo stacktrace, sự cố xảy ra trong StreamWriter -> System.Buffer, một lần nữa được gọi bởi System.IO.SyncTextWriter, được cho là trình bao bọc thread-safe xung quanh System.IO.TextWriter.

Vì điều này xảy ra trong TextWriter, có cách nào để giải quyết vấn đề này không, sử dụng TextWriter theo chủ đề không?

Có an toàn để mở và đóng phiên làm cách tôi thực hiện trong DynamicPage không?

Vì điều này rõ ràng là khó để tái tạo, bất kỳ ý tưởng nào về cách thực hiện cũng được chào đón.

[UPDATE] NHibernate Profiler nói rằng chúng tôi cũng đã mở và đóng phiên (trong khối sử dụng) trong trang chính vì cần kiểm tra một số quyền cho người dùng hiện tại, vì vậy hai phiên được mở mỗi yêu cầu. Tôi đã refactored nó, do đó, nó bây giờ thay vì mở một phiên trong một superclass trang, nó sẽ mở phiên trong global.asax trên Application_BeginRequest và đóng lại nó trên Application_EndRequest, nơi phiên được đặt trong HttpContext.Current.Items.

Nhưng không có cách nào chắc chắn để kiểm tra nếu điều này khắc phục.

+0

Tôi quan tâm đến bài viết SO này. Trong ngăn xếp dấu vết, bạn có điều này: - 'SessionInit.GetCurrentUser (phiên ISession) trong j: \ dev \ app \ app_wwwroot \ App_Code \ SessionInit.cs: 107' Có gì trong mã SessionInit.cs? –

+0

Nick, tôi đã thêm mã cho SessionInit.GetCurrentUser(). Xem bản cập nhật. – Stamppot

+0

Tôi nhận được vấn đề này tất cả bây giờ và sau đó quá trong cùng một vị trí chính xác trong NHibernate - bạn đã quản lý để xác định làm thế nào để sửa chữa? –

Trả lời

12

Stamppot, cảm ơn bạn đã đăng sự cố này lên StackOverflow; như bạn đã biết, không có nhiều thông tin khác về thông báo lỗi này được tìm thấy trên Web. Nhóm của tôi đã gặp phải một vấn đề tương tự cách đây vài tháng trong một ứng dụng web sử dụng NHibernate và log4net. (StringTemplate có thể đã tham gia quá.) Chúng tôi "cố định" các problen bằng cách chuyển hướng Console.Out/Lỗi null suối (có hiệu quả vô hiệu hóa chúng) trong() xử lý sự kiện Application_Start trong Global.ascx.cs:

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

Chi tiết: Trong trường hợp của chúng tôi, lỗi "có thể xảy ra tình trạng chủng tộc ..." có liên quan đến tải. Trên máy chủ sản xuất, ngoại lệ này sẽ phát sinh một cách không thường xuyên, làm rơi quá trình công nhân vào mỗi dịp. Cuối cùng, chúng tôi đã tìm ra cách tái tạo nó, bằng cách chạy một kịch bản tràn ngập ứng dụng web với nhiều yêu cầu trong một khoảng thời gian ngắn. Các dấu vết ngăn xếp ngoại lệ, khi tương quan với mã nguồn NHibernate/StringTemplate/log4net, chỉ ra việc sử dụng các phương thức Console.Out/Error để ghi lại trong các tình huống khác nhau. Có vẻ như một nơi kỳ lạ cho một lỗi như vậy phát sinh --- không phải là những phương pháp này considered to be thread-safe? Tuy nhiên, sau khi chúng tôi áp dụng cách giải quyết ở trên, vấn đề ngay lập tức biến mất và đã không trở lại kể từ đó. Thật không may, các ưu tiên khác giữ chúng ta đào sâu hơn --- nhưng bất kể nguyên nhân gốc rễ của vấn đề là gì, nó cũng không thể hiện bằng bất kỳ cách nào khác.

+0

Rất tiếc, cảm ơn "sự cố" này. Nếu nó giữ các lỗi đi tôi hạnh phúc. – Stamppot

+0

BTW, bạn có thể tái tạo nó mỗi lần bạn chạy tập lệnh không?Bất kỳ chi tiết nào về nó, chẳng hạn như bao nhiêu yêu cầu về thời lượng bao lâu? Bản thân kịch bản cũng sẽ tốt đẹp, nếu có thể. – Stamppot

+3

@APW: Chúng tôi đã gặp sự cố chính xác này khi chúng tôi nâng cấp log4net lên 1.2.12 gần đây. Không có ý tưởng tại sao bằng văn bản cho Console Out sẽ làm điều đó. Bản sửa lỗi của bạn sẽ vô hiệu hóa việc ghi nhật ký trên bàn điều khiển toàn cầu, điều này sẽ giải quyết dứt khoát vấn đề. Một điều khác (ít toàn cầu) bạn có thể làm là đảm bảo rằng, trong trường hợp NHibernate, tùy chọn cấu hình "show_sql" được đặt thành false. show_sql viết * trực tiếp * vào Console Out, hoàn toàn độc lập với log4net. Tôi tin rằng đó là trường hợp cụ thể gây ra những vấn đề mà OP gặp phải. –

0

Vấn đề với giải pháp mà @APW cung cấp là theo mặc định, StreamWriter là không phải là chủ đề an toàn. Kiểm tra xem tại đây: https://msdn.microsoft.com/en-us/library/system.io.streamwriter(v=vs.110).aspx

Bằng cách chuyển "StreamWriter mới" sang Console.Set * bạn đang chuyển trường hợp an toàn không phải là chủ đề. Vì vậy, tôi nghĩ rằng đó là một vấn đề thời gian để xem lỗi tương tự một lần nữa.

Cách chính xác sẽ sử dụng phương thức TextWriter.Synchronized để bao bọc Stream.Null không an toàn.

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

UPD: Vui lòng bỏ qua điều này. Tôi đã tìm thấy rằng Console.SetOut gói bất kỳ dòng vào TextWriter.Synchronized (...). Proof.

Các vấn đề liên quan