4

我围绕 play 的 Action 编写了以下包装器,它将使用一个同时接受会话和请求的函数。这是第一个版本:

def ActionWithSession[A](bp: BodyParser[A])(f: Session => Request[A] => Result): Action[A] =
  Action(bp) {
    db.withSession { 
      session: DbSession =>
        request => f(session)(request)
    }
  }

这个版本运行良好(正确的结果返回给浏览器),但是每次调用都会泄漏一个数据库连接。几次通话后,我开始收到以下异常:

java.sql.SQLException: Timed out waiting for a free available connection.

当我将其更改为以下版本时(通过request =>在操作后向右移动,连接泄漏消失了,并且可以正常工作。

def ActionWithSession[A](bp: BodyParser[A])(f: Session => Request[A] => Result): Action[A] =
  Action(bp) { request =>
    db.withSession { 
      session: DbSession =>
        f(session)(request)
    }
  }

为什么第一个版本会导致连接泄漏,以及第二个版本如何解决这个问题?

4

2 回答 2

4

代码的第一个版本不应该工作。您不应该从 withSession 范围返回包含对 Session 对象的引用的任何内容。在这里,您返回一个包含此类引用的闭包。当稍后 Play 调用闭包时,withSession 范围已经关闭,Session 对象无效。诚然,在闭包中泄漏 Session 对象很容易发生(并且将来会被 Slick 捕获)。

这就是为什么它一开始似乎可以工作,但会泄漏连接:会话对象懒惰地获取连接。withSession 块返回(或关闭)块末尾的连接(如果已获取)。但是,当您从块中泄漏一个未使用的 Session 对象并在块结束后第一次使用它时,它仍然会延迟打开连接,但不会自动关闭它。我们不久前就认识到这是不受欢迎的行为,但还没有解决它。我们想到的解决方法是在调用 .close 方法后禁止 Session 对象获取连接。在您的情况下,这将导致异常而不是泄漏连接。

https://github.com/slick/slick/pull/107

正确的代码确实是您发布的第二个版本,其中返回的闭包主体包含整个 withSession 块,而不仅仅是其结果。

于 2013-08-22T12:14:35.577 回答
3

db.withSession 接收一个函数,该函数在其第一个参数中获取一个 Session 并使用它提供给它的一些会话来执行它。db.withSession 的返回值是该函数返回的值。

在第一个版本中,传递给 withSession 的表达式计算为一个函数 request => f(session)(request),因此 db.withSession 结束:实例化一个会话,实例化一个绑定到该会话的函数对象,关闭会话(在它实例化的函数被调用之前!),并返回此绑定函数。现在,Action得到了它想要的——一个接受 aRequest[A]并给出 a的函数Result。但是,在 Play 将执行此操作时,会话将被延迟打开,但没有任何内容将其返回到池中。

第二个版本做得对,在 db.withSession 中我们实际上是在调用f,而不是返回调用 的函数f。这确保调用f嵌套在 db.withSession 中,并在获取会话时发生。

希望这对某人有帮助!

于 2013-08-22T05:12:00.200 回答