如何找出我使用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");
}

从那里,我们所有的方法都可以使用来自任何数据库的会话。一些请求方法使用多个数据库会话来完成它们的任务。在这一点上,该项目没有利用存储库模式——重写它将是一个昂贵的操作。这段代码中是否有任何明显的内存泄漏?

如何找出我使用NHibernate的遗留应用程序泄漏内存的地方

我觉得你的设计很可疑。首先,你的工厂是漏水的,因为虽然你想处理,但你唯一做到的是处理你刚刚打开的东西;这不是很有用,意味着已经创建的连接不会被关闭。其次,应用程序使用基于字符串的方法请求正确连接的设计很容易出错。您的应用程序可能正在处理多个数据库模式,其中每个连接都与某个模式相关。这意味着连接是不可互换的,因此需要为每个模式使用唯一的抽象。因此,与其有一个通用的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知道它。