Essential Effects logo

Essential Effects

Subscribe
Archives
January 1, 2021

Essential Effects: Happy new year, plus background tasks management

Happy New Year everybody!

I didn’t finish the book in 2020, but, you know, stuff happened. Many folks who took my course or have read a draft of the book wanted something that would highlight the main points of the book, so I’m currently writing a final chapter: a job scheduler case study.

I’ve got some good feedback coming in from early reviewers, and I hope to soon make the next iteration available for early-release purchase.

Here’s some new content: how to use Resource to control the lifetime of a background task.

Be well,

.. Adam


Example: Managing a background task

A perhaps less obvious use of a Resource is to manage the lifecycle of a background task. For example, we may want to fork some (often non-terminating) effect, and later cancel it when it isn’t required to run anymore. This suggests we can combine use of a Fiber with a Resource, where the Resource effects are defined as:

  • acquire: start the task, producing a Fiber
  • release: cancel the Fiber

Then the lifetime of the background task would directly correspond to the execution of the use effect of the Resource:

package com.innerproduct.ee.resources

import cats.effect._
import cats.implicits._
import com.innerproduct.ee.debug._
import scala.concurrent.duration._

object ResourceBackgroundTask extends IOApp {
  def run(args: List[String]): IO[ExitCode] =
    for {
      _ <- backgroundTask.use { _ =>
        IO.sleep(200.millis) *> IO("$s is so cool!").debug // <1>
      }
      _ <- IO("done!").debug
    } yield ExitCode.Success

  val backgroundTask: Resource[IO, Unit] = {
    val loop = (IO("looping...").debug *> IO.sleep(100.millis)).foreverM // <2>

    Resource
      .make(IO("> forking backgroundTask").debug *> loop.start)( // <3>
        IO("< canceling backgroundTask").debug.void *> _.cancel // <4>
      )
      .void // <5>
  }
}

<1> The background task will only be running during our use effect. We sleep a little bit, to ensure the background task does some work.

<2> The background task itself is a loop that prints and sleeps. We use the foreverM combinator, which is equivalent to

- val loop: IO[Nothing] = step.flatMap(_ => loop)
+ val loop: IO[Nothing] = step.foreverM

<3> The acquire effect forks a Fiber and…

<4> … the release effect cancels it.

<5> In this example we don’t give the user of the Resource access to the Fiber, although you could imagine where this may be useful.

ResourceBackgroundTask outputs:

[ioapp-compute-0] > forking backgroundTask   <1>
[ioapp-compute-1] looping...
[ioapp-compute-2] looping...
[ioapp-compute-3] looping...
[ioapp-compute-4] $s is so cool!             <2>
[ioapp-compute-4] < canceling backgroundTask <3>
[ioapp-compute-4] done!

<1> Our effect is forked as a Fiber.

<2> Once the use effect finishes…

<3> … the Fiber is canceled.

Since this “background task” pattern is common, Cats Effect defines the background method on an IO:

def background: Resource[IO, IO[A]] // <1>

<1> The resource “value” is an IO[A], which is an effect which lets you join the effect running in the background; it’s literally the join method of the Fiber that the Resource manages.

Our code from the example–with debug effects removed–could be rewritten as:

- Resource.make(loop.start)(_.cancel)
+ loop.background
Don't miss what's next. Subscribe to Essential Effects:
Powered by Buttondown, the easiest way to start and grow your newsletter.