0

In cats-effect 2, the safer alternative to this function would be IO.runCancelable.

In cats-effect 3, this API was replaced by its unsafe variant or a Dispatcher implementation, according to the following article:

https://typelevel.org/cats-effect/docs/migration-guide#io

I'm trying to figure out both replacement, the unsafe variant is fairly straightforward:

    
    import scala.concurrent.duration._
    implicit val ioRuntime: IORuntime = cats.effect.unsafe.IORuntime.builder().build()

    val base: IO[Unit] = IO {
      println("...")
    }.delayBy(1.second)

    val twenty: IO[Unit] = {

      val srcs = (1 to 20)
        .map { _ =>
          base
        }

      srcs.reduce(_ *> _)
    }

    val withCallback = twenty.guaranteeCase {
      case Outcome.Succeeded(fa) =>
        IO {
          println("SUCCESS!")
        }
      case Outcome.Errored(ee) =>
        IO {
          println(s"FAILUE: ${ee.toString}")
        }
      case _ => IO.pure() // do nothing
    }

    def timeoutIn(milliS: Int): Unit = {
      val cancelling = withCallback.unsafeRunCancelable()

      Thread.sleep(milliS)

      cancelling()
      println("finished")
    }

    timeoutIn(30000)

When I try to convert it into the Dispatcher variant:

    
    import scala.concurrent.duration._
    implicit val ioRuntime: IORuntime = cats.effect.unsafe.IORuntime.builder().build()

    val base: IO[Unit] = IO {
      println("...")
    }.delayBy(1.second)

    val twenty: IO[Unit] = {

      val srcs = (1 to 20)
        .map { _ =>
          base
        }

      srcs.reduce(_ *> _)
    }

    val withCallback = twenty.guaranteeCase {
      case Outcome.Succeeded(fa) =>
        IO {
          println("SUCCESS!")
        }
      case Outcome.Errored(ee) =>
        IO {
          println(s"FAILUE: ${ee.toString}")
        }
      case _ => IO.pure() // do nothing
    }

    def timeoutIn(milliS: Int): Unit = {
//      val cancelling = withCallback.unsafeRunCancelable()

      val cancelling = {

        val start: IO[() => Future[Unit]] = Dispatcher.sequential[IO](false).use { dispatcher =>
          def result = dispatcher.unsafeRunCancelable(
            withCallback
          )
          IO.delay(result)
        }

        start.unsafeRunSync()
      }

      Thread.sleep(milliS)

      cancelling()
      println("finished")
    }

    timeoutIn(30000)

I found that all effects defined in the chain of withCallback won't be executed, the program will just wait for 30 seconds and then terminates.

So I have 2 questions:

  • What's wrong with the second implementation and how to fix it to have the same behaviour?

  • If fixed, what are possible advantages of using the Dispatcher?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • 1
    So, just to confirm again, you want to start running a possibly infinite `IO` and after some amount of time cancels it, right? Also, you want to attach a finalizer to such `IO` that should consider whenever it managed to complete, failed with an exception, or was cancelled, right? – Luis Miguel Mejía Suárez Mar 23 '23 at 13:13
  • @LuisMiguelMejíaSuárez absolutely correct: `timeoutIn(30000)` will cause `withCallback` to terminate successfully with "SUCCESS!", `timeoutIn(3000)` will cause it to be cancelled early – tribbloid Mar 24 '23 at 21:36
  • Then what is wrong with just `io.timeout(duration).guaranteedCase(finalizer)`? And then submit that to the `Dispatcher` – Luis Miguel Mejía Suárez Mar 24 '23 at 21:43
  • 1
    BTW, don't create a `Dispatcher` for each call, rather receive it as a constructor parameter. – Luis Miguel Mejía Suárez Mar 24 '23 at 21:47

0 Answers0