typelevel/cats-effect

Cancelation hangs in `Dispatcher#unsafeToFutureCancelable`

armanbilge opened this issue · 2 comments

h/t @TimWSpence for original minimization.

//> using scala "3.3.1"
//> using dep "org.typelevel::cats-effect::3.5.2"

import cats.*
import cats.syntax.all.*
import cats.effect.*
import cats.effect.std.Dispatcher
import scala.concurrent.duration.*

object Repro extends IOApp.Simple:

  // also sequential
  val run = Dispatcher.parallel[IO].use { dispatcher =>
    IO.fromFuture {
      IO {
        println("starting")
        val (_, cancel) = dispatcher.unsafeToFutureCancelable(IO.never) // broken
        // val (_, cancel) = IO.never.unsafeToFutureCancelable()(runtime) // works
        println("started, now canceling")
        cancel()
      }
    }.flatMap(_ => IO(println("canceled"))).replicateA_(1000)
  }
durban commented

There are (at least) two different problems here. #3900 seems to fix the problem for Dispatcher.parallel.

For Dispatcher.sequential, the problem seems to be that it doesn't actually fork the submitted action, so it can't really cancel it (https://github.com/typelevel/cats-effect/blob/series/3.x/std/shared/src/main/scala/cats/effect/std/Dispatcher.scala#L214). I'm not sure what to do with that.

If I remember correctly, a sequential dispatcher involves at least two fibers:

  1. a worker fiber, that actually runs tasks
  2. a supervisor fiber, that restarts the worker if the task it is running (self-)cancels

So even though the task is not actually forked, it should be possible to cancel it by simply canceling the worker fiber.