Skip to content

Transactor.transact catches every Throwable, including OOM and StackOverflow #148

Description

@haskiindahouse

Transactor.transact wraps the user's body in try ... catch case t =>, which matches every Throwable — including fatal ones like OutOfMemoryError, StackOverflowError, and other VirtualMachineErrors. On a fatal error the code then attempts con.rollback() on a connection whose state is, by definition, undefined. That call can hang, mask the original error, or raise a secondary error that gets only added as suppressed.

Reproducer (illustrative — same control flow without a real DB):

//> using scala 3.8.3

import scala.util.control.NonFatal

@main def run(): Unit =
  // Magnum's pattern: catches everything
  def magnumStyle[T](f: => T): T =
    try f
    catch case t =>
      // would call con.rollback() here on a corrupted connection
      println(s"caught fatal: ${t.getClass.getSimpleName}")
      throw t

  try magnumStyle(throw new StackOverflowError("simulated"))
  catch case _: StackOverflowError => ()

Source:

try
val res = f(using DbTx(con, sqlLogger))
con.commit()
res
catch
case t =>
try con.rollback()
catch { case t2 => t.addSuppressed(t2) }
throw t

try
  val res = f(using DbTx(con, sqlLogger))
  con.commit()
  res
catch
  case t =>
    try con.rollback()
    catch { case t2 => t.addSuppressed(t2) }
    throw t

The standard Scala convention is case NonFatal(t) =>, which lets VirtualMachineError, ThreadDeath, LinkageError, etc. propagate without trying to do recovery on a JVM that may already be doomed. In a transactor specifically this matters because con.rollback() on OutOfMemoryError can itself try to allocate, which then deadlocks or compounds the failure.

Suggested fix:

catch
  case NonFatal(t) =>
    try con.rollback()
    catch { case NonFatal(t2) => t.addSuppressed(t2) }
    throw t

Happy to PR.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions