如何找出我使用NHibernate的遗留应用程序泄漏内存的地方
本文关键字:泄漏 应用程序 内存 NHibernate 何找出 | 更新日期: 2023-09-27 18:01:46
我有一个正在维护的遗留应用程序,它正在泄漏内存。
我有理由相信源代码是会话管理/依赖注入代码。它使用Simple Injector和NHibernate。
首先,这里有一些我们使用的helper类和接口:
public class SessionFactory : Dictionary<string, Func<ISession>>,Helpers.ISessionFactory, IDisposable
{
public ISession CreateNew(string name)
{
return this[name]();
}
public void Dispose()
{
foreach (var key in Keys)
{
this[key]().Close();
this[key]().SessionFactory.Close();
}
}
}
public interface ISessionFactory
{
ISession CreateNew(string name);
}
容器初始化是这样的:
private static void InitializeContainer(Container container)
{
var connectionStrings = System.Configuration.
ConfigurationManager.ConnectionStrings;
var sf1 = new Configuration().Configure().SetProperty(
"connection.connection_string",
connectionStrings["db1"].ConnectionString
).BuildSessionFactory();
var sf2 = new Configuration().Configure().SetProperty(
"connection.connection_string",
connectionStrings["db2"].ConnectionString
).BuildSessionFactory();
var sf3 = new Configuration().Configure().SetProperty(
"connection.connection_string",
connectionStrings["db3"].ConnectionString
).BuildSessionFactory();
container.Register<ISessionFactory>(() =>
new SessionFactory
{
{"db1", sf1.OpenSession},
{"db2", sf2.OpenSession},
{"db3", sf3.OpenSession}
}, Lifestyle.Scoped);
}
然后,在我们的基本控制器(其他控制器从它继承)中,这会发生:
protected BaseController(ISessionFactory factory)
{
this.factory = factory;
db1Session = factory.CreateNew("db1");
db2Session = factory.CreateNew("db2");
db3Session = factory.CreateNew("db3");
}
从那里,我们所有的方法都可以使用来自任何数据库的会话。一些请求方法使用多个数据库会话来完成它们的任务。在这一点上,该项目没有利用存储库模式——重写它将是一个昂贵的操作。这段代码中是否有任何明显的内存泄漏?
我觉得你的设计很可疑。首先,你的工厂是漏水的,因为虽然你想处理,但你唯一做到的是处理你刚刚打开的东西;这不是很有用,意味着已经创建的连接不会被关闭。其次,应用程序使用基于字符串的方法请求正确连接的设计很容易出错。您的应用程序可能正在处理多个数据库模式,其中每个连接都与某个模式相关。这意味着连接是不可互换的,因此需要为每个模式使用唯一的抽象。因此,与其有一个通用的ISessionFactory
抽象试图服务所有消费者(目前失败了),不如通过为每个独特的模式提供自己的抽象来显式地实现。例如:
public interface IDb1SessionProvider
{
ISession Session { get; }
}
public interface IDb2SessionProvider
{
ISession Session { get; }
}
public interface IDb3SessionProvider
{
ISession Session { get; }
}
由于缺乏上下文,我将接口命名为IDbXSessionProvider
,但我打赌你可以想出一个更好的名字。
这可能看起来很奇怪,因为所有接口都有相同的方法签名,但请记住它们每个都有一个非常不同的契约。Liskov替代原则描述了它们不应该共享相同的接口。
这种提供者的实现可以如下所示:
public class FuncDb1SessionProvider : IDb1SessionProvider
{
Func<ISession> provider;
public FuncDb1SessionProvider(Func<ISession> sessionProvider) {
this.sessionProvier = provider;
}
public ISession Session => provider();
}
你可以在Simple Injector中注册这样的实现:
var factory = new Configuration().Configure().SetProperty(
"connection.connection_string",
connectionStrings["db1"].ConnectionString)
.BuildSessionFactory();
var session1Producer = Lifestyle.Scoped.CreateProducer<ISession>(
factory.OpenSession, container);
container.RegisterSingleton<IDb1SessionProvider>(
new FuncDb1SessionProvider(session1Producer.GetInstance));
这段代码的作用是为db1会话创建一个作用域为InstanceProducer
的会话。范围InstanceProducer
将确保在特定范围内(通常是web请求)只创建该会话的一个实例,并且它将确保处置ISession
实现(如果它实现IDisposable
)。对InstanceProducer.GetInstance()
的调用被封装在FuncDb1SessionProvider
中。此会话提供程序将调用将会话的创建转发给包装委托。
通过这种设计,您可以让应用程序代码依赖于IDb1SessionProvider
,并且代码可以使用它而无需处理它。在同一会话中对IDb1SessionProvider.Session
的每次调用将确保您获得相同的会话,并且Simple Injector保证在请求结束时处理。
看起来您已经发明了自己的名为ISessionFactory的接口。考虑到你正在使用NHibernate,它也提供了一个以这个名字命名的接口,我认为在你自己的代码中使用相同的名字是非常不幸的。您应该为自己的接口和类选择一个不同的名称,以避免混淆。
至于问题本身,NHibernate的ISessionFactory.OpenSession()
正是这样做的。它将打开并返回一个会话。没有理由认为它会在重用或作用域方面有什么神奇的作用。
要让NHibernate协助上下文会话,你需要配置适当的"上下文提供程序",并使用ISessionFactory.GetCurrentSession()
。参见NHibernate参考中的上下文会话。
或者,你可以使用任何你喜欢的方式来管理会话,但是你必须使用这种机制来检索当前会话,而不是期望NHibernate知道它。