Search code examples
scalaexceptionatomicstm

Why is an atomic block in Scala run twice when exception gets thrown?


Code:

object Blub {
  import scala.concurrent.stm._
  val last = Ref("none")

  def bla() = atomic { implicit txn =>
    last() = "outer"
    try {
      atomic { implicit txn =>
        last() = "inner"
        println(last())
        throw new RuntimeException
      }
    } catch {
      case _: RuntimeException =>
    }
  }

  def main(args: Array[String]): Unit = {
    bla()
    println("Result: "+last.single())
  }
}

Output:

inner  
inner  
Result: outer

Can anyone explain why the inner atomic block is run twice? I know that it does a rollback due to the exception, hence the final result. But i don't understand the why it runs the code for a second time.


Solution

  • The bottom of this page on the ScalaSTM documentation has this to say:

    To make nesting very cheap, ScalaSTM tries to flatten all of the nesting levels together into a single top-level transaction. If an inner transaction throws an exception then there isn’t enough information to perform the partial rollback, so ScalaSTM restarts the entire transaction in a mode that does exact nesting. This optimization is called subsumption.

    So what happens is:

    1. The entire thing is attempted as a "flattened" transaction
    2. last is set to "outer"
    3. last is set to "inner"
    4. "inner" is printed
    5. The inner atomic block throws an exception, the outer block does not
    6. ScalaSTM doesn't know how to roll back just the inner transaction since it ran "flattened" so it rolls back the whole thing (last is now back to "none") and retries it "non-flattened"
    7. last is set to "outer"
    8. last is set to "inner"
    9. "inner is printed
    10. The inner atomic block throws an exception, which is caught by the outer block
    11. This time since it's non-flattened, it can roll back the inner block only, and last is set back to "outer".