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?