Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the following code leaks a Slick database collection?

I wrote the following wrapper around play's Action that will use a function that takes both a session and a request. Here is the first version:

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

This version works well (the correct Result is returned to the browser), however each call will leak a database connection. After several calls, I started getting the following exceptions:

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

When I change it to the version below (by moving the request => right after the Action, the connection leakage goes away, and it works.

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

Why is the first version causing a connection to leak, and how the second version fixes that?

like image 952
thesamet Avatar asked Mar 22 '23 16:03

thesamet


2 Answers

The first version of the code is not supposed to work. You should not return anything that holds a reference to the Session object from a withSession scope. Here you return a closure which holds such a reference. When the closure is later called by Play, the withSession scope has already been closed and the Session object is invalid. Admittedly, leaking the Session object in a closure happens very easily (and will be caught by Slick in the future).

Here is why it seems to work at first, but leaks the Connection: Session objects acquire a connection lazily. withSession blocks return (or close) the connection at the end of the block if one has been acquired. When you leak an unused Session object from the block however and use it for the first time after the block ended, it still lazily opens the connection, but nothing automatically closes it. We recognized this as undesired behavior a while ago, but didn't fix it yet. The fix we have in mind is disallowing Session object's to acquire connections once their .close method has been called. In your case this would have lead to an exception instead of a leaking connection.

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

The correct code is indeed the second version you posted, where the returned closure's body contains the whole withSession block, not just its result.

like image 169
cvogt Avatar answered Apr 02 '23 19:04

cvogt


db.withSession receives a function that gets a Session in its first argument and executes it with some session it provides to it. The return value of db.withSession is whatever that function returns.

In the first version, the expression that is passed to withSession evaluates to a function request => f(session)(request), so db.withSession ends up: instantiating a session, instantiates a function object that is bound to that session, close the session (before the function it instantiated is called!), and returns this bound function. Now, Action got exactly what it wanted - a function that takes a Request[A] and gives a Result. However, at the time Play is going to execute this Action, the session is going to be lazily opened, but there is nothing that returns it pack to the pool.

The second version does it right, inside db.withSession we are actually calling f, rather than returning a functin that calls f. This ensures that the call to f is nested inside db.withSession and occurs while the session is acquired.

Hope this helps someone!

like image 36
thesamet Avatar answered Apr 02 '23 19:04

thesamet