Skip to content

Resource#allocate that fails produces reportFailure under timeout #4697

Description

@FireFoxIL

I have recently discovered a strange behaviour while trying to use .timeout on the Resource of the Client.run(_) from http4s. As I later investigated, the issue has nothing to do with http4s, ember or fs2.

//> using scala 3
//> using dep org.typelevel::cats-effect:3.7.1

import cats.effect.*
import cats.effect.syntax.temporal.*
import scala.concurrent.duration.*

object ReproMakeBug extends IOApp {
  override def run(args: List[String]): IO[ExitCode] =
    Resource
      .make(IO.raiseError[Unit](new RuntimeException("boom")))(_ => IO.unit)
      .timeout(300.millis)
      .use(_ => IO.unit)
      .handleErrorWith { e =>
        IO.println(s"[handled] ${e.getClass.getName}: ${e.getMessage}")
      }
      .as(ExitCode.Success)
}

This code produces two error mesages: one defined in the code and one by IOApp.reportFailure. For me this is strange, because the error is handled and I expect that reportFailure should not be called.

Code with eval doesn't produce such message:

//> using scala 3
//> using dep org.typelevel::cats-effect:3.7.1

import cats.effect.*
import cats.effect.syntax.temporal.*
import scala.concurrent.duration.*

object ReproEvalControl extends IOApp {

  override def run(args: List[String]): IO[ExitCode] =
    Resource
      .eval(IO.raiseError[Unit](new RuntimeException("boom")))
      .timeout(300.millis)
      .use(_ => IO.unit)
      .handleErrorWith { e =>
        IO.println(s"[handled] ${e.getClass.getName}: ${e.getMessage}")
      }
      .guarantee(IO.sleep(600.millis))
      .as(ExitCode.Success)
}

Activity

  1. reardonj commented on Oct 6, 2026

    @reardonj
    Contributor

    For extra weirdness, adding a cede brings the report back:

    //> using scala 3
    //> using dep org.typelevel::cats-effect:3.7.1
    
    import cats.effect.*
    import cats.effect.syntax.temporal.*
    import scala.concurrent.duration.*
    
    object ReproEvalControl extends IOApp {
    
      override def run(args: List[String]): IO[ExitCode] =
        Resource
          .eval(IO.cede >> IO.raiseError[Unit](new RuntimeException("boom")))
          .timeout(300.millis)
          .use(_ => IO.unit)
          .handleErrorWith { e =>
            IO.println(s"[handled] ${e.getClass.getName}: ${e.getMessage}")
          }
          .guarantee(IO.sleep(600.millis))
          .as(ExitCode.Success)
    }
    java.lang.RuntimeException: boom
    	at ReproEvalControl$.run$$anonfun$1(Main.scala:12)
    	at >> @ ReproEvalControl$.run(Main.scala:12)
    [handled] java.lang.RuntimeException: boom
    
  2. durban commented on Oct 6, 2026

    @durban
    Contributor

    Is this different from #2962?

  3. reardonj commented on Oct 7, 2026

    @reardonj
    Contributor

    I guess since Resource's Concurrent doesn't override racePair to use the effect's implementation it doesn't go through IO's racePair, which should avoid this.

  4. durban commented on Oct 7, 2026

    @durban
    Contributor

    Yeah, at first glance this seems like a duplicate of #2962, just for Resource. It could probably be "solved" the same way (and is similarly unfixable in the general case).

    Also related: #3085.

  5. added theissue type on Oct 7, 2026
  6. geny200 commented on Nov 3, 2026

    @geny200
    Contributor

    Hi, I have same problem, do you have ideas/workaround?

  7. reardonj commented on Nov 3, 2026

    @reardonj
    Contributor

    Hi, I have same problem, do you have ideas/workaround?

    Is it causing an actual issue or just annoying logging statements? The solution I suggest to update Resource's Concurrent implementation to use F's racePair should be sufficient to fix it if you have time to contribute.

  8. geny200 commented on Nov 3, 2026

    @geny200
    Contributor

    Is it causing an actual issue or just annoying logging statements?

    At the moment, this isn’t critical; but we use structured logging, so 1 - we receive notifications about incorrect logs, and 2 - topic with dead‑message logs becomes garbage.

  9. reardonj commented on Nov 3, 2026

    @reardonj
    Contributor

    Is it causing an actual issue or just annoying logging statements?

    At the moment, this isn’t critical; but we use structured logging, so 1 - we receive notifications about incorrect logs, and 2 - topic with dead‑message logs becomes garbage.

    Are you using IOApp? If so, you probably should override reportFailure (it's hidden by default because it is protected, but adjust the scaladoc filter) to log such failures in a way that works with your structured logging. It is possible CE will log a less spurious failure one day.

    If you aren't using IOApp, you'll have to look at how your runtime is setup to do something similar, there should be a hook for it.

  10. mvanhorn commented on Nov 6, 2026

    @mvanhorn
    Contributor

    Resource's Concurrent instance inherited the default racePair implementation instead of delegating to the underlying effect's racePair. This bypassed the underlying effect's handling of raced fiber errors, allowing an acquisition error to be reported even when the caller handled it.

    ResourceConcurrent now delegates racePair to F.racePair and converts its outcomes and fibers back into Resource values.

    #4712

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions