Search code examples
scalaconcurrent.futures

Executing dependencies in order using Scala Futures


I have a task runner as follows

package taskman

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

final class Task private (
    desc: String,
    dependencies: Seq[Task] = Seq.empty[Task],
    body: => Unit
) {

  def run(): Future[Unit] = Future {
//    println(s"Executing dependencies for $desc")
    Future
      .sequence(dependencies.map(_.run()))
      .map { _ => body }
  }
}

object Task {
  def task(desc: String, dependencies: Seq[Task] = List.empty[Task])(
      body: => Unit
  ): Task =
    new Task(desc, dependencies, body)
}

object taskTest extends App {
  import Task._

  val boilWater = task("Boil water") {
    println("Boiling water ")
  }

  val boilMilk = task("Boil milk") {
    println("Boiling milk")
  }

  val mixWaterAndMilk =
    task("Mix milk and water", Seq(boilWater, boilMilk)) {
      println("Mixing milk and water")
    }

  val addCoffeePowder = task("Add coffee powder", Seq(mixWaterAndMilk)) {
    println("Adding coffee powder")
  }

  val addSugar = task("Add sugar", Seq(addCoffeePowder)) {
    println("Adding sugar")
  }

  val makeCoffee = task("Make coffee", Seq(addSugar)) {
    println("Coffee is ready to serve")
  }

  Await.result(makeCoffee.run, 10.seconds)
}

I expect the dependencies to run in parallel and after finishing then execute the body. But I am always getting this in the wrong order.

The expected order is as follows

Boiling water Boiling milk Mixing milk and water Adding coffee powder Adding sugar Coffee is ready to serve

Boiling milk and water can be done in any order but the rest of the things should be executed in order. I am executing the body on Future.sequence.map {}, but still the order isn't correct. There is something wrong with this code for sure but I am not able to figgure it out.


Solution

  • The problem is the spurious extra Future in run.

    def run(): Future[Unit] = Future { // <-- Not required
      Future
        .sequence(dependencies.map(_.run()))
        .map { _ => body }
    }
    

    Removing it fixes the problem:

    def run(): Future[Unit] = 
      Future
        .sequence(dependencies.map(_.run()))
        .map { _ => body }