0

I recently spotted a case I can't fully understand while working with Monix Task:

There are two functions (in queue msg handler):

  def handle(msg: RollbackMsg): Task[Unit] = {
    logger.info(s"Attempting to rollback transaction ${msg.lockId}")
    Task.defer(doRollback(msg)).onErrorRestart(5).foreachL { _ =>
      logger.info(s"Transaction ${msg.lockId} rolled back")
    }
  }

  private def doRollback(msg: RollbackMsg): Task[Unit] =
    (for {
      originalLock         <- findOrigLock(msg.lockId)
      existingClearanceOpt <- findExistingClearance(originalLock)
      _                    <- clearLock(originalLock, existingClearanceOpt)
    } yield ()).transact(xa)

The internals of doRollback's for-comprehension are all a set of doobie calls returning ConnectionIO[_] monad and then transact is run on it turning the composition into Monix Task.

Now, as seen in handle function I'd like entire process to retry 5 times in case of failure. The mysterious part is that this simple call:

doRollback(msg).onErrorRestart(5)

doesn't really restart the operation on exception (verified in tests). In order to get this retry behaviour I have to explicitly wrap it in Task.defer, or have it already within Task "context" in any other way.

And this is the point I don't fully get: why is it so? doRollback already gives me Task instance, so I should be able to call onErrorRestart on it, no? If it's not the case how can I be sure that a Task instance i get from "somewhere" is ok to be restarted or not?

What am I missing here?

Michal Ostruszka
  • 2,089
  • 2
  • 20
  • 23
  • 2
    Are you sure that `doRollback` is a pure function without side effects? – simpadjo Oct 27 '18 at 19:58
  • 2
    thanks for the hint @simpadjo, it turns out that `findOrigLock` function (the first one in for-comp sequence does `connection.raiseError(ex)` on lock not found, so in this case it isn'y actually in monad "context" yet. Looks like I was searching in the wrong place. – Michal Ostruszka Oct 27 '18 at 21:33

0 Answers0