cats-effect icon indicating copy to clipboard operation
cats-effect copied to clipboard

Resource#race: late looser release

Open satorg opened this issue 2 years ago • 3 comments

Consider a snippet:

import cats.syntax.all._
import cats.effect._
import cats.effect.std._
import scala.concurrent.duration._

object ResourceAllocRace extends IOApp.Simple {
  private val con = Console[IO]

  private def mkResource(name: String, random: Random[IO]): Resource[IO, String] =
    Resource.make {
      con.println(s"... [$name]: connecting") >>
        random.nextIntBounded(3000).flatMap(ms => IO.sleep(ms.millis)) >>
        con.println(s">>> [$name]: CONNECTED")
    }(_ => con.println(s"<<< [$name]: CLOSED"))
      .as(name)

  override def run: IO[Unit] =
    Random.scalaUtilRandom[IO].flatMap { random =>
      (
        ((
          mkResource("AAA", random) race mkResource("BBB", random)
        ) race (
          mkResource("CCC", random) race mkResource("DDD", random)
        )) race ((
          mkResource("EEE", random) race mkResource("FFF", random)
        ) race (
          mkResource("GGG", random) race mkResource("HHH", random)
        ))
      ).map(_.merge.merge.merge).flatMap { winner =>
        Resource.eval(con.println(s"!!! [$winner]: WON"))
      }.use { _ =>
        con.println("... working") >>
          IO.sleep(10.seconds) >> // can be increased to any value
          con.println("... DONE")
      }
    }
}

When I run it, I may get the following output:

[info] running (fork) ResourceAllocRace 
[info] ... [CCC]: connecting
[info] ... [AAA]: connecting
[info] ... [EEE]: connecting
[info] ... [BBB]: connecting
[info] ... [DDD]: connecting
[info] ... [FFF]: connecting
[info] ... [GGG]: connecting
[info] ... [HHH]: connecting
[info] >>> [HHH]: CONNECTED
[info] >>> [EEE]: CONNECTED
[info] >>> [AAA]: CONNECTED
[info] >>> [FFF]: CONNECTED
[info] <<< [FFF]: CLOSED
[info] >>> [CCC]: CONNECTED
[info] >>> [DDD]: CONNECTED
[info] <<< [DDD]: CLOSED
[info] >>> [BBB]: CONNECTED
[info] <<< [BBB]: CLOSED
[info] >>> [GGG]: CONNECTED
[info] <<< [GGG]: CLOSED
[info] !!! [CCC]: WON
[info] ... working
[info] ... DONE
[info] <<< [HHH]: CLOSED
[info] <<< [EEE]: CLOSED
[info] <<< [CCC]: CLOSED
[info] <<< [AAA]: CLOSED
  • Expected behavior: only a winner resource is kept until the end of the use block.
  • Actual behavior: some of looser resources may be kept (not every time though but quite often) until the end of the use block along with the winner one.
  • Build info:
    • Scala: 2.13.8
    • Cats Effect: 3.3.14
  • System props:
    os.arch: x86_64
    os.name: Mac OS X
    os.version: 12.4
    java.vm.name: OpenJDK 64-Bit Server VM
    java.vm.vendor: Homebrew
    java.vm.version: 11.0.15+0
    

satorg avatar Jul 19 '22 23:07 satorg

This surprises me immensely. I have the same expectation as you do.

djspiewak avatar Jul 20 '22 16:07 djspiewak

I think this is related to this comment on Resource#start:

   * 4. If the fiber succeeds and .cancel lost the race or wasn't called,
   *    finalize naturally when the containing scope ends, `join` returns
   *    the value

It seems that .cancel can indeed lose the race (and it does for H, E and A in the example), and so they are not finalized eagerly. Unless I'm misunderstanding something, the "finalize naturally when the containing scope ends" part means exactly what is observed in the example. (I have no idea what is the reason for this behavior, but it's documented, so there must be a reason...)

durban avatar Jul 20 '22 22:07 durban

That comment seems to have been added/updated in:

  • https://github.com/typelevel/cats-effect/pull/1733

armanbilge avatar Aug 26 '22 14:08 armanbilge

After staring at this a bit I have a feeling it's like https://github.com/typelevel/cats-effect/issues/1620#issuecomment-1177947475. I don't think there's anything fundamentally wrong going on here per se, this is just another example of a method which has more than one lawful implementation and that we'll need to override the default implementation to get the particular semantics we want.

armanbilge avatar Nov 06 '22 03:11 armanbilge