Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixed stale database connection release #1526

Merged
merged 2 commits into from

4 participants

@rofreytag

Mailing list: https://groups.google.com/forum/#!topic/liftweb/Hn0bdboGzAU

getAutoCommit, rollback and commit can throw exceptions on stale connections, which was preventing a correct connection release. (c.releaseFunc() was never called in that case)

Regards
Robert

Robert Freytag Fixed stale database connection release
 - getAutoCommit, rollback and commit can throw exceptions
   on stale connections, which was preventing
   a correct connection release
2e830f1
@fmpwizard fmpwizard added this to the 2.6-M3 milestone
persistence/db/src/main/scala/net/liftweb/db/DB.scala
@@ -306,9 +306,15 @@ trait DB extends Loggable {
(info.get(name): @unchecked) match {
case Some(ConnectionHolder(c, 1, post, manualRollback)) => {
- if (! (c.getAutoCommit() || manualRollback)) {
- if (rollback) tryo{c.rollback}
- else c.commit
+ // stale and unexpectedly closed connections may throw here
+ try {
+ if (! (c.getAutoCommit() || manualRollback)) {
+ if (rollback) c.rollback
+ else c.commit
+ }
+ } catch {
+ case e: Exception =>
+ logger.debug("Swallowed exception during connection release. ", e)
@farmdawgnation Collaborator

I'm not a fan of the catch-all exception. Is there a particular class of Exception that gets thrown when a connection is stale? Could we just catch that one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rofreytag

That part of the code uses the Helpers.tryo() a lot, which is also a catch-all directive. Thats why I was following the already implemented pattern. In this case it would be possible to narrow the exception to java.util.SQLException. If we do that, however, the connection would not be released correctly, in the event of another Exception. As I mentioned in the mailing-list, the non-released connection will mess up the whole application.

We could also put the call to release in finally clause, so a non-SQLException will get thrown to the caller.

So I leave it to you guys to make a call on that. And I can implement either change, if requested.

@farmdawgnation
Collaborator

That part of the code uses the Helpers.tryo() a lot, which is also a catch-all directive. Thats why I was following the already implemented pattern.

I think you misunderstood the pattern a bit. Or perhaps the pattern is just misused here. tryo has very well defined behavior. Specifically, it attempts to execute code, returning a Full of the result if it was successful or a Failure containing the exception if it threw an exception.

This code will eat all exceptions, only logging them at the debug level.

It may be true that tryo is being used in such a way that things aren't getting logged or bubbled up in this code block. I'd say that's an antipattern and design flaw in this code, and we probably shouldn't let any more code through that follows that pattern.

In this case it would be possible to narrow the exception to java.util.SQLException. If we do that, however, the connection would not be released correctly, in the event of another Exception. [...] We could also put the call to release in finally clause, so a non-SQLException will get thrown to the caller.

Let's do that. (Put the release in finally.) I think that's the best compromise we have right now with the way this code is structured.

Thanks!

@Shadowfiend
Owner

Yes, generally the correct pattern for resource management is that releases go in a finally block.

Robert Freytag narrowed catch clause in connection-release,
 changed loglevel to error
e375dae
@rofreytag

I think you misunderstood the pattern a bit. Or perhaps the pattern is just misused here. tryo has very well defined behavior.

Yes it is misused here as a swallow-all. The method releaseConnectionNamed is called in two cases: trough use and trough the LoanWrapper. In both cases it is executed in a finally clause, so throwing an exception in there would overwrite an already thrown exception. As I see it there are two cases:

  1. The connection was already stale/closed when it was used to do things in the user code. In this case an exception is already thrown to the user by the driver, and the exception in the release-code should be swallowed, as it would override the already created exception.
  2. The connection became stale/closed just before executing the release-code. In this case it would be ok to throw the exception up to the user. But that behavior would break clearThread, as it relies on releaseConnectionNamed to not throw.

Therefore, I narrowed the exception to SQL and introduced the finally clause as we discussed before, and changed the log-level to error, so the user is informed either way that the connection-release was not going well. As you said, that may be a good compromise, without having to change too much in this class for that particular case.

@farmdawgnation
Collaborator

Rock on. Thanks for the contribution. :+1:

@farmdawgnation farmdawgnation merged commit 3387cdd into lift:master
@rofreytag

Wohoo nice!

@rofreytag rofreytag deleted the rofreytag:db_realeasecon_fix branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 3, 2014
  1. Fixed stale database connection release

    Robert Freytag authored
     - getAutoCommit, rollback and commit can throw exceptions
       on stale connections, which was preventing
       a correct connection release
Commits on Apr 7, 2014
  1. narrowed catch clause in connection-release,

    Robert Freytag authored
     changed loglevel to error
This page is out of date. Refresh to see the latest.
View
6 contributors.md
@@ -203,3 +203,9 @@ Vasya Novikov
### Email: ###
n1dr+cm3053lift@yaaandex.com (replace "aaa" with "a")
+
+### Name: ###
+Robert Freytag
+
+### Email: ###
+robertfreytag+lift at gmail .. com
View
27 persistence/db/src/main/scala/net/liftweb/db/DB.scala
@@ -24,7 +24,7 @@ import Helpers._
import net.liftweb.http.S
import javax.sql.{DataSource}
-import java.sql.ResultSetMetaData
+import java.sql.{ResultSetMetaData, SQLException}
import java.sql.{Statement, ResultSet, Types, PreparedStatement, Connection, DriverManager}
import scala.collection.mutable.{HashMap, ListBuffer}
import javax.naming.{Context, InitialContext}
@@ -306,16 +306,23 @@ trait DB extends Loggable {
(info.get(name): @unchecked) match {
case Some(ConnectionHolder(c, 1, post, manualRollback)) => {
- if (! (c.getAutoCommit() || manualRollback)) {
- if (rollback) tryo{c.rollback}
- else c.commit
+ // stale and unexpectedly closed connections may throw here
+ try {
+ if (! (c.getAutoCommit() || manualRollback)) {
+ if (rollback) c.rollback
+ else c.commit
+ }
+ } catch {
+ case e: SQLException =>
+ logger.error("Swallowed exception during connection release. ", e)
+ } finally {
+ tryo(c.releaseFunc())
+ info -= name
+ val rolledback = rollback | manualRollback
+ logger.trace("Invoking %d postTransaction functions. rollback=%s".format(post.size, rolledback))
+ post.reverse.foreach(f => tryo(f(!rolledback)))
+ logger.trace("Released %s on thread %s".format(name,Thread.currentThread))
}
- tryo(c.releaseFunc())
- info -= name
- val rolledback = rollback | manualRollback
- logger.trace("Invoking %d postTransaction functions. rollback=%s".format(post.size, rolledback))
- post.reverse.foreach(f => tryo(f(!rolledback)))
- logger.trace("Released %s on thread %s".format(name,Thread.currentThread))
}
case Some(ConnectionHolder(c, n, post, rb)) =>
logger.trace("Did not release " + name + " on thread " + Thread.currentThread + " count " + (n - 1))
Something went wrong with that request. Please try again.