ZIO 2: A ZIO.timeout interrupt example with ZIO.attempt

As a little ZIO 2 example with Scala 3, here’s some code that starts to show how to use ZIO.timeout along with ZIO.attempt while accessing an internet URL with Scala’s Source.fromURL.

Basically I attempt to access a URL using Scala’s Source.fromURL, and then I add a timeout to that, specifically a ZIO##timeout:

//> using scala "3"
//> using lib "dev.zio::zio::2.1.0"
package zio_http_timeout

import zio.*
import zio.Console.*
import scala.io.Source

object ZioHttpTimeout extends ZIOAppDefault:

    val blueprint: ZIO[Any, Throwable, Option[String]] =
        ZIO.attempt {
            Source.fromURL("http://httpbin.org/get").mkString
        }.timeout(5.milliseconds)

    /**
     * NOTE: `timeout` doesn’t make this an error, it’s still
     * success, but it’s value is `None`.
     */
    val run = blueprint.foldZIO(
        failure => printLineError(s"FAILURE = $failure"),
        success => printLine(     s"SUCCESS = $success")
    )

As shown in the comments, the way this works is:

  • If the URL is downloaded within the timeout setting, the success value is printed. This will be the text you download from the URL. Note that it is wrapped inside a Some.
  • Interestingly, if the ZIO.timeout is triggered, the success value is still run, and the result of it is a None.

More on ZIO.timeout

So a key here is starting to know how to work with the return value from ZIO.timeout. Here’s what the ZIO Scaladoc says about its timeout method:

Returns an effect that will timeout this effect, returning None if the timeout elapses before the effect has produced a value; and returning Some of the produced value otherwise.

If the timeout elapses without producing a value, the running effect will be safely interrupted.

WARNING: The effect returned by this method will not itself return until the underlying effect is actually interrupted. This leads to more predictable resource utilization. If early return is desired, then instead of using effect.timeout(d), use effect.disconnect.timeout(d), which first disconnects the effect's interruption signal before performing the timeout, resulting in earliest possible return, before an underlying effect has been successfully interrupted.

That’s helpful, because it explains the Some and None usage, and also talks about how to interrupt/disconnect the underlying effect.