Is cancelation required when Fiber fails? - scala

What is the proper behavior to avoid resource/memory leak? Is it required/correct to cancel failed Fiber?
val someEffect: F[Unit] = //...
def someFiber[F[_]: MonadError[?[_], Throwable]](fa: F[Fiber[F, Unit]]): F[Unit] =
for {
fiber <- fa
_ <- fiber.join handleErrorWith { _ =>
//Is this cancel required?
fiber.cancel *> someEffect
}
} yield ()
My concern is about cancel when handling Fiber.join error? Is it required?

Related

How to write an asynchronous code that Awaited concisely?

I'm a beginner in Scala.
Please let me know if there is a more concise part in the code below.
To supplement, I'd like to call each Future method synchronously.
◆getUser method:
def getUser: Option[User] = {
Await.ready(
twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF)),
Duration.Inf)
.value
.flatMap(x => Option(x.getOrElse(null)))
}
◆ process method:
def process : Unit =
for {
user <- getUser
} yield {
Await.ready(
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)),
Duration.Inf)
.value
.foreach {
case Success(tweets) => tweets.foreach(tweet => println(s"Delete Successfully!!. $tweet"))
case Failure(exception) => println(s"Failed Delete.... Exception:[$exception]")
}
}
I made some assumptions on user and tweet data types but I would rewrite that to:
def maybeDeleteUser(userName: String, maybeUser: Option[User]): Future[String] =
maybeUser match {
case Some(user) =>
twitterService.delete(user.id, configService.getString(TWITTER_SEARCH_KEYWORD)).map {
case Failure(exception) => s"Failed Delete.... Exception:[${exception.getMessage}]"
case Success(tweets) => tweets.map(tweet => s"Delete Successfully!!. $tweet").mkString(System.lineSeparator())
}
case _ => Future.successful(s"Failed to find user $userName")
}
def getStatusLogMessage: Future[String] = {
val userName = configService.getString(TWITTER_USERNAME_CONF)
for {
maybeUser <- twitterService.getUser(configService.getString(TWITTER_USERNAME_CONF))
statusLogMessage <- maybeDeleteUser(userName, maybeUser)
} yield statusLogMessage
}
def process: Unit = {
val message = Await.result(getStatusLogMessage, Duration.Inf)
println(message)
}
That way your side effect, i.e. println is isolated and other methods can be unit tested. If you need to block the execution, do it only at the end and use map and flatMap to chain Futures if you need to order the execution of those. Also be careful with Duration.Inf, if you really need to block, then you'd want to have some defined timeout.

Joining on a cancelled fiber

I expected that canceling Fiber makes it joinable. Here is an example of what I mean:
object TestFiber extends App {
implicit val contextShift: ContextShift[IO] =
IO.contextShift(ExecutionContext.global)
implicit val timer: Timer[IO] = IO.timer(ExecutionContext.global)
val test = for {
fiber <- IO.never.attempt.start
_ <- fiber.cancel
_ <- fiber.join
_ <- IO(println("Finished"))
} yield ()
test.unsafeRunSync() //blocks instead of printing "Finished" and exit
}
I expected that after cancel, join would immediately return and Finished would be printed.
But the actual behavior is that the program hangs up. How to make a Fiber finished so it can be joinable?
This seems to be the intended, documented behaviour of cats-effect. The ZIO equivalent to cancel is interrupt, and it returns an Exit type that will allow you to handle all possible cases (success, error, interruption) very easily, so if possible, I'd encourage you to just switch to ZIO.
But if you need to use cats-effect, the best that I could come up with (and it's not very good) is to use a Deferred:
def catchCancel[F[_]: Concurrent, A](io: F[A]): F[(F[Unit], F[Either[Option[Throwable], A]])] =
for {
deferred <- Deferred[F, Either[Option[Throwable], A]]
fiber <- (for {
a <- Concurrent[F].unit.bracketCase(_ => io) {
case (_, ExitCase.Completed) => Concurrent[F].unit
case (_, ExitCase.Error(e)) => deferred.complete(Left(Some(e))).as(())
case (_, ExitCase.Canceled) => deferred.complete(Left(None))
}
_ <- deferred.complete(Right(a))
} yield ()).start
} yield (fiber.cancel, deferred.get)
And you can use it like so:
val test = for {
x <- catchCancel[IO, Nothing](IO.never)
(cancel, getResult) = x
_ <- cancel
result <- getResult
_ <- IO(println(s"Finished $result"))
} yield ()
But seriously, why would you inflict this onto yourself? Just switch to ZIO, it fixes this problem and a boatload of others.

How execute spring repository method in for comprehension with implicit

I want to save data from telegram api in for comprehension type with implicit, but have an error
Error:(61, 9) type mismatch;
found : cats.effect.IO[Unit]
required: scala.concurrent.Future[?]
_ <- IO(userRepository.save(User(msg.from.get.id, msg.from.get.username.get)))
The code in TelegramBot example, which use info.mukel.telegrambot4s 3.0.9 library.
onCommand("/hello") { implicit msg =>
for {
_ <- reply(s"Hello ${msg.from.get.firstName}")
_ <- IO(userRepository.save(User(msg.from.get.id, msg.from.get.username.get)))
} yield ()
}
I tried to delete reply and add, this code compiled, but saving (inside IO) didn`t execute
onCommand("/hello") { implicit msg =>
for {
res <- IO(userRepository.save(User(msg.from.get.id, msg.from.get.username.get)))
} yield res
}
Is it possible to resolve this problem?
Try
onCommand("/hello") { implicit msg =>
for {
_ <- IO.fromFuture(IO.pure(reply(s"Hello ${msg.from.get.firstName}")))
_ <- IO(userRepository.save(User(msg.from.get.id, isBot = true, msg.from.get.username.get)))
} yield ()
}

Scala: Chaining futures, returning the first

I have a scenario where I have a Future[Something]: a which when successful needs to trigger a second Future[Unit]: b. I want to chain a and b together but I only care that a succeeds. if b fails I can just log an error and leave it at that:
So far I have:
def updateSomething(something: Something): Future[Something] = {
val eventual: Future[Something] = repository.update(something)
eventual.onSuccess({
case updated =>
repository.audit(updated.id, "Update successful")
.onFailure({
case throwable: Throwable => Logger.error("Audit failed", throwable)
})
Logger.info("Update Complete")
})
eventual
}
But this does not link the lifecyles of the update and the audit together. e.g when I Await.result(service.updateSomething(...), duration) there is no guarantee the repository.audit future has completed.
flatMap is your friend. You can just use a for-comprehension + recover:
for {
a <- executeA()
_ <- executeB(b).recover{case e => println(e); Future.unit } // awaits B to complete
} yield a
Also you can use more friendly form:
execudeA().flatMap(a =>
executeB().recover { case e => println(e); () }.map(_ => a)
)
also, you can just use a val
val = a <- executeA()
a.andThen{ case _ => executeB(b).recover{case e => println(e)} }
a //and return a

Cancellation with Future and Promise in Scala

This is a followup to my previous question.
Suppose I have a task, which executes an interruptible blocking call. I would like to run it as a Future and cancel it with failure method of Promise.
I would like the cancel to work as follows:
If one cancels the task before it finished I would like the task to finish "immediately", interrupting the blocking call if it has already started and I would like the Future to invoke onFailure.
If one cancels the task after the task finished I would like to get a status saying that the cancel failed since the task already finished.
Does it make sense? Is it possible to implement in Scala? Are there any examples of such implementations?
scala.concurrent.Future is read-only, so one reader cannot mess things up for the other readers.
It seems like you should be able to implement what you want as follows:
def cancellableFuture[T](fun: Future[T] => T)(implicit ex: ExecutionContext): (Future[T], () => Boolean) = {
val p = Promise[T]()
val f = p.future
p tryCompleteWith Future(fun(f))
(f, () => p.tryFailure(new CancellationException))
}
val (f, cancel) = cancellableFuture( future => {
while(!future.isCompleted) continueCalculation // isCompleted acts as our interrupted-flag
result // when we're done, return some result
})
val wasCancelled = cancel() // cancels the Future (sets its result to be a CancellationException conditionally)
Here is the interruptable version of Victor's code per his comments (Victor, please correct me if I misinterpreted).
object CancellableFuture extends App {
def interruptableFuture[T](fun: () => T)(implicit ex: ExecutionContext): (Future[T], () => Boolean) = {
val p = Promise[T]()
val f = p.future
val aref = new AtomicReference[Thread](null)
p tryCompleteWith Future {
val thread = Thread.currentThread
aref.synchronized { aref.set(thread) }
try fun() finally {
val wasInterrupted = (aref.synchronized { aref getAndSet null }) ne thread
//Deal with interrupted flag of this thread in desired
}
}
(f, () => {
aref.synchronized { Option(aref getAndSet null) foreach { _.interrupt() } }
p.tryFailure(new CancellationException)
})
}
val (f, cancel) = interruptableFuture[Int] { () =>
val latch = new CountDownLatch(1)
latch.await(5, TimeUnit.SECONDS) // Blocks for 5 sec, is interruptable
println("latch timed out")
42 // Completed
}
f.onFailure { case ex => println(ex.getClass) }
f.onSuccess { case i => println(i) }
Thread.sleep(6000) // Set to less than 5000 to cancel
val wasCancelled = cancel()
println("wasCancelled: " + wasCancelled)
}
With Thread.sleep(6000) the output is:
latch timed out
42
wasCancelled: false
With Thread.sleep(1000) the output is:
wasCancelled: true
class java.util.concurrent.CancellationException
Twitter's futures implement cancellation. Have a look here:
https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala
Line 563 shows the abstract method responsible for this. Scala's futures currently do not support cancellation.
You can use Monix library instead of Future
https://monix.io