-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 16 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: ASP.NET Serious memory leak
PostPosted: Wed Sep 14, 2005 2:25 am 
Newbie

Joined: Wed Sep 14, 2005 12:01 am
Posts: 3
Location: Latvia
We built some very simple ASP.NET 1.1 application using NHibernate 0.9.0, npgsql 0.7.0 and postgresql 8.0.3.

Running test on a page where is just one session.CreateCreteria.List() statement for a 5min leads to asp.net process using ~500MB of memory comparing to ~40MB at the test start.

We used some profiler, so I`ll try to describe the situation in more details as possible, hoping some one could tell as whether it is a bug in NHibernate or our misuse.

Hnibernate configuration section in web.config looks like this:
Code:
<nhibernate>
    <add
      key="hibernate.connection.provider"         
      value="NHibernate.Connection.DriverConnectionProvider"
    />
    <add
      key="hibernate.dialect"                     
      value="NHibernate.Dialect.PostgreSQLDialect"
    />
    <add
      key="hibernate.connection.driver_class"         
      value="NHibernate.Driver.NpgsqlDriver"
    />
    <add
      key="hibernate.connection.connection_string"
      value="Server=localhost;Port=5432;User id=postgres;Password=postgres;Database=NUSA-dev;ENCODING=UNICODE"
    />
  </nhibernate>

Nothing unusual...

A common mapping file looks like this:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
   <class name="nusa.Data.Classes.Service, nusa" where="is_active='true'" table="service">      
       <id name="ID" column="id" type="Int32" unsaved-value="0">
         <generator class="sequence" >
            <param name="sequence">service_id_seq</param>
         </generator>
      </id>
      <property name="GUID" column="guid"  />
      <many-to-one name="Table1" column="table1_id" />
      <many-to-one name="Table2" column="table2_id" />
      <many-to-one name="Person" column="person_id" />
      <property name="Number" column="number" type="Int32" />
      <property name="Timestamp1" column="timestemp_1" />
      <many-to-one name="Statuss" column="statuss_id" />
      <property name="Created" column="created" access="nosetter.camelcase" update="false" insert="false" />
      <property name="Modified" access="nosetter.camelcase" insert="false" column="modified" />
   </class>
</hibernate-mapping>


We use session per request model:
Code:
public class NHibernateHttpModule : IHttpModule
      {
         private static readonly ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
         // this is only used if not running in HttpModule mode
         private static ISession m_session;

         private static readonly string KEY = "NHibernateSession";

         public void Init(HttpApplication context)
         {
            context.EndRequest += new EventHandler(context_EndRequest);
         }

         public void Dispose()
         {
         }

         private void context_BeginRequest(object sender, EventArgs e)
         {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;

            context.Items[KEY] = BaseDataAccess.OpenSession();
         }

         private void context_EndRequest(object sender, EventArgs e)
         {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;

            ISession session = context.Items[KEY] as ISession;
            if (session != null)
            {
               try
               {
                  session.Close();
               }
               catch {
                  log.Error("Neizdevas nobeigt NHibernate sesiju");
               }
            }

            context.Items[KEY] = null;
         }

         public static ISession CurrentSession
         {
            get
            {

               if (HttpContext.Current==null)
               {
                  // running without an HttpContext (non-web mode)
                  // the nhibernate session is a singleton in the app domain
                  if (m_session!=null)
                  {
                     return m_session;
                  }
                  else
                  {
                     m_session = BaseDataAccess.OpenSession();

                     return m_session;
                  }
               }
               else
               {
                  // running inside of an HttpContext (web mode)
                  // the nhibernate session is a singleton to the http request
                  HttpContext currentContext = HttpContext.Current;
            
                  ISession session = currentContext.Items[KEY] as ISession;

                  if (session == null)
                  {
                     session = BaseDataAccess.OpenSession();
                     currentContext.Items[KEY] = session;
                  }

                  return session;
               }
            }
         }

      }

BaseDataAccess.OpenSession:
Code:
public static ISession OpenSession()
      {
         Configuration config;
         ISessionFactory factory;
         ISession session;
   
         config = new Configuration();

         if (config==null)
         {
            throw new InvalidOperationException("NHibernate configuration is null.");
         }

         config.AddAssembly("nusa");
         
         factory = config.BuildSessionFactory();
         
         if (factory==null)
         {
            throw new InvalidOperationException("Call to Configuration.BuildSessionFactory() returned null.");
         }

         session = factory.OpenSession();

         factory.Close();

         if (session==null)
         {
            throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
         }

         return session;
      }

Page that was tested contains:
Code:
IList operatoriList = NHibernateHttpModule.CurrentSession.CreateCriteria(typeof(SomeType))   
                  .AddOrder(Order.Asc("SomeProperty")).List();

As i told after 5 minutes of testing with Application Test Center using 10 simulated users we experianced huge memory leak.

You might find interesting what did profiler shows us:

Class(only anomalies =>1000 instances) Number of instances
    System.String 224428
    System.String[] 161803
    System.Object[] 61893
    System.Data.DataRow 48680
    NHibernate.SqlCommand.Parameter 43316
    System.Collections.Hashtable.bucket[] 37777
    System.Collections.Hashtable 37742
    System.Collections.ArrayList 35513
    System.String[][] 32767
    NHibernate.SqlCommand.SqlString 26026
    NHibernate.Property.BasicGetter 14560
    System.Data.DataColumnPropertyDescriptor 13505
    System.Data.DataColumn 13505
    NHibernate.Property.BasicSetter 13286
    System.Int32[] 10602
    System.Boolean[] 9468
    System.Data.Common.StringStorage 8360
    System.Data.DataRelationCollection.DataTableRelationCollection 7300
    NHibernate.Persister.ILoadable[] 7099
    System.String[][][] 7098
    NHibernate.LockMode[] 7098
    System.Int32 5806
    NHibernate.Type.ManyToOneType 5101
    System.Type[] 4757
    NHibernate.Type.IType[] 4735
    NHibernate.Loader SimpleEntityLoader 4732
    System.Data.DataColumn[] 4377
    System.Data.DbType 4026
    System.Collections.CaseInsensitiveHashCodeProvider 3708
    System.Threading.ReaderWriterLock 3693
    System.ComponentModel.PropertyDescriptorCollection 3674
    System.ComponentModel.PropertyDescriptor[] 3674
    System.Data.RecordManager 3650
    System.Data.DataTable 3650
    System.Data.DataRowCollection 3650
    System.Data.DataRowBuilder 3650
    System.Data.DataColumnCollection 3650
    System.Data.ConstraintCollection 3650
    System.Data.ColumnQueue 3650
    System.ComponentModel.CollectionChangeEventHandler 2880
    NHibernate.Util.SequencedHashMap.Entry 2605
    System.Web.Configuration.CapabilitiesPattern 2510
    NHibernate.Property.ISetter[] 2366
    NHibernate.Property.IGetter[] 2366
    NHibernate.Persister.EntityPersister 2366
    NHibernate.Loader.OuterJoinFetchStrategy[] 2366
    NHibernate.Loader.EntityLoader 2366
    NHibernate.Id.SequenceGenerator 2366
    NHibernate.Engine.Cascades.IdentifierValue 2366
    NHibernate.Engine.Cascades.CascadeStyle[] 2366
    System.Data.DataRow[] 2362
    System.Web.Configuration.CapabilitiesAssignment 2170
    System.Data.Common.Int32Storage 1808
    System.Collections.BitArray 1808
    System.WeakReference 1291
    NHibernate.Property.FieldSetter 1274
    NHibernate.Util.SequencedHashMap 1104


Look like we or NHibernate do not close/dispose queries/instances or some thing else...

Any comments or suggestions?


Top
 Profile  
 
 Post subject: Solution
PostPosted: Wed Sep 14, 2005 7:33 am 
Newbie

Joined: Wed Sep 14, 2005 12:01 am
Posts: 3
Location: Latvia
After hours of browsing internet, coding and testing...

We still don`t know what was wrong, but the following changes resolved memory leak. After about ten test each of them taking ~500MB of memory before, now do just a ~150MB memory peak running in a row.

BaseDataAccess do not contains OpenSession any more, and NHibernateHttpModule now look like this:
Code:
using NHibernate.Cfg;
   using System;
   using System.Web;   
   using NHibernate;
   using log4net;
      
      /// <summary>
      /// Summary description for NHibernateHttpModule.
      /// </summary>
      public class NHibernateHttpModule : IHttpModule
      {
         private static readonly ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
         // this is only used if not running in HttpModule mode
         private static ISession m_session;

         private static ISessionFactory factory;
         private static Configuration config;

         private static readonly string KEY = "NHibernateSession";

         public void Init(HttpApplication context)
         {
            context.EndRequest += new EventHandler(context_EndRequest);
         }

         private static ISession OpenSession()
         {
            
            ISession session;
   
            if (config == null)
               config = new Configuration();

            if (config==null)
            {
               throw new InvalidOperationException("NHibernate configuration is null.");
            }

            config.AddAssembly("nusa");
         
            if (factory == null)
               factory = config.BuildSessionFactory();
         
            if (factory==null)
            {
               throw new InvalidOperationException("Call to Configuration.BuildSessionFactory() returned null.");
            }

            lock (typeof(NHibernateHttpModule))
            {
               session = factory.OpenSession();
            }

            if (session==null)
            {
               throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
            }

            return session;
         }

         public void Dispose()
         {
            if (factory != null)
               factory.Close();
         }

         private void context_BeginRequest(object sender, EventArgs e)
         {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;

            context.Items[KEY] = OpenSession();
         }

         private void context_EndRequest(object sender, EventArgs e)
         {
            HttpApplication application = (HttpApplication)sender;
            HttpContext context = application.Context;

            ISession session = context.Items[KEY] as ISession;
            if (session != null)
            {
               try
               {
                  session.Close();
               }
               catch {
                  log.Error("Neizdevas nobeigt NHibernate sesiju");
               }
            }

            context.Items.Remove(KEY);
         }

         public static ISession CurrentSession
         {
            get
            {

               if (HttpContext.Current==null)
               {
                  // running without an HttpContext (non-web mode)
                  // the nhibernate session is a singleton in the app domain
                  if (m_session!=null)
                  {
                     return m_session;
                  }
                  else
                  {
                     m_session = OpenSession();

                     return m_session;
                  }
               }
               else
               {
                  // running inside of an HttpContext (web mode)
                  // the nhibernate session is a singleton to the http request
                  HttpContext currentContext = HttpContext.Current;
            
                  ISession session = currentContext.Items[KEY] as ISession;

                  if (session == null)
                  {
                     session = OpenSession();
                     currentContext.Items[KEY] = session;
                  }

                  return session;
               }
            }
         }

      }    


Try to find differences your self :)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 8:24 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
How did your BaseDataAccess.OpenSession look before? It almost looks as if you were creating a new ISessionFactory for each request.

By the way, you don't need a lock around factory.OpenSession, it's a thread-safe operation. And you need a lock around the code building the session factory, otherwise you might accidentally create several instances of the factory.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 11:33 am 
Newbie

Joined: Thu Aug 25, 2005 5:05 am
Posts: 6
In the first version you created a factory per request.

In the second version you reparse your domain assembly per request.

Try changing your OpenSession in the second version to:

Code:
         private static ISession OpenSession()
         {
           
            lock (typeof(NHibernateHttpModule))
            {
               if (factory == null)
               {
                  Configuration config = new Configuration();
               
                   config.AddAssembly("nusa");
               
                   factory = config.BuildSessionFactory();
               }
            }
   
            return factory.OpenSession();

         }


Edit: Oops, sergey was right (below), I put the lock in the wrong place.


Last edited by Dan on Wed Sep 14, 2005 11:42 am, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 11:38 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
You need to extend that lock to cover the "if (factory == null)", otherwise it doesn't really help anything.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 1:42 pm 
Senior
Senior

Joined: Sat Sep 10, 2005 3:46 pm
Posts: 178
It seems like a bad design to have your http module opening factories and sessions for you. You should at least move the OpenSession operation back out to the data access class.

I never liked the idea of having the BeginRequest opening sessions. What if you naviagate to a page that doesn't need a session?
I see this httpmodule design passed all around the internet and wonder why people do it.

I believe the Provider pattern works so good for handling sessions. this is the architecture.


abstract SessionProvider
HttpSessionProvider
StaticSessionProvider

SessionManager

You provide configuration for the provider that you want to use in your config file. then you can swap out strategies by editing the config file. This works great when you have an app with different UI's. I consider NUnit another UI so it works great for running test code also.

You just ask the Session manager for sessions and its smart enough to know how to use the correct provider. I've even extended this so that the SessionProvider is hooked to a ConnnectionProvider. This way the correct connection string can be determined at runtime based on the environment the app is running in: local, integration, staging productions, etc...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 7:23 pm 
Regular
Regular

Joined: Tue May 24, 2005 12:55 pm
Posts: 56
Quote:
I see this httpmodule design passed all around the internet and wonder why people do it.


I think most are using it because someone provided the code to use it and the session-per-request is a good solution. Post the code to use your implementation of the Provider pattern and you might find people using the Provider pattern more often. I personally would be interested as I don't use the HttpModule method either.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 14, 2005 7:52 pm 
Senior
Senior

Joined: Sat Sep 10, 2005 3:46 pm
Posts: 178
I will post some code shortly. There are a bunch of differenet classes in this implementation and theres to much to post here so I'll try to trim it down to the main code elements.

Just to clarify, I do use an httpmodule when implmenting my HttpSessionProviderClass, but its only to close out a session that hasn't been closed.

I'll get the code out as soon as I can.


Top
 Profile  
 
 Post subject: Doubt
PostPosted: Sun Sep 18, 2005 9:00 pm 
Newbie

Joined: Wed Sep 07, 2005 9:23 pm
Posts: 3
Dear Dan,

Your suggested modification would be:

Code:
         private static ISession OpenSession()
         {
           if (factory == null)
               lock (typeof(NHibernateHttpModule))
               {
                  Configuration config = new Configuration();
               
                   config.AddAssembly("nusa");
               
                   factory = config.BuildSessionFactory();
               }
            }
   
            return factory.OpenSession();

         }


I


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 19, 2005 3:44 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
It should be:
Code:
lock(typeof(NHibernateHttpModule))
{
    if (factory == null)
    {
    ...
    }
}


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 21, 2005 8:43 am 
Regular
Regular

Joined: Fri Jun 11, 2004 6:27 am
Posts: 81
Location: Yaroslavl, Russia
Sergey, one little correction: MS warns agains locking on the type object. It could create cross-AppDomain deadlocks. Use static locker object instead. Original article could be found on the MSDN site (i think it was published in the Dr.GUI's column).

_________________
Best,
Andrew Mayorov // BYTE-force


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 21, 2005 2:26 pm 
Regular
Regular

Joined: Tue May 24, 2005 12:55 pm
Posts: 56
When I get some time I want to start a wiki page about session management. there are various forumn posts that should be compiled together. For now there is the Using NHibernate with ASP.Net which lightly covers session handling. There is a link there about singletons that everone should read. I recently switched to using a nested singleton because a co-worker confirmed that the double-check locking mechanism does not work in .NET. This page also gives some explanation why xor is right.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Sep 25, 2005 10:19 pm 
Newbie

Joined: Mon Jun 20, 2005 8:47 pm
Posts: 11
I was reading this about using lock:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconthreadingdesignguidelines.asp

So maybe
Code:
System.Threading.Interlocked.CompareExchange(ref x, y, null);

would be a better alternative.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Sep 30, 2005 5:11 am 
Im a little bit confused after browsing the site. On the pages that cover SysCache http://wiki.nhibernate.org/display/NHC/SysCache+Usage it states that "The default ISessionFactory implementation is not threadsafe so be sure to acquire a lock on this while creating new ISession instances". To me it sound like you also have to sycnchronize the session creation part :

Code:
lock(myLock){
    return factory.OpenSession();
}


That is not right is it?

/Kalle


Top
  
 
 Post subject:
PostPosted: Fri Sep 30, 2005 5:39 am 
Contributor
Contributor

Joined: Wed May 11, 2005 4:59 pm
Posts: 1766
Location: Prague, Czech Republic
I too think it's wrong, see my comment there.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 16 posts ]  Go to page 1, 2  Next

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.