4
votes

I'm playing with scala Futures with default global context and my own ExecutionContext.

I'm curious how global context is shutdown after all executions. Because if I create my own ExecutionContext I have to manually shutdown.

Example,

1) Using global executionContext,

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

object ParallelTasksWithGlobalExecutionContext {

  private val data: Iterable[Input] = Iterable("data1", "data2", "data3")

  def main(args: Array[String]): Unit = {

    val f: Future[Unit] = Future.traverse(data) { d =>

      println(s"[${Thread.currentThread().getName}]-Firing $d")
      processData(d)

    } map { processed =>
      processed.foreach(p => println(s"""[${Thread.currentThread().getName}]-$p"""))
    }

    Await.result(f, Duration.Inf)
  }

  type Input = String
  type Output = String

  def processData: (Input => Future[Output]) = data => {
    Future {
      Thread.sleep(5000)
      s"[Thread-${Thread.currentThread().getName}] data $data is processed."
    }
  }
}

Output

$ sbt "runMain ParallelTasksWithGlobalExecutionContext"
[info] Running ParallelTasksWithGlobalExecutionContext 
[run-main-0]-Firing data1
[run-main-0]-Firing data2
[run-main-0]-Firing data3
[scala-execution-context-global-59]-[Thread-scala-execution-context-global-58] data data1 is processed.
[scala-execution-context-global-59]-[Thread-scala-execution-context-global-59] data data2 is processed.
[scala-execution-context-global-59]-[Thread-scala-execution-context-global-60] data data3 is processed.
[success] Total time: 6 s, completed Apr 1, 2018 12:44:36 AM

After the execution is completed, application is terminated.

2) Using own ExecutionContext - the application does not terminate after all executions are done until I manually .shutdown.

import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService, Future}
import scala.util.{Failure, Success}

object ParallelTasksWithCustomExecutionContext {

  private val data: Iterable[Input] = Iterable("data1", "data2", "data3")
  implicit val singleThreadContext: ExecutionContextExecutorService = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(3))

  def main(args: Array[String]): Unit = {

    Future.traverse(data) { d =>

      println(s"[${Thread.currentThread().getName}]-Firing $d")
      processData(d)

    } onComplete {
      case Success(processed) =>
        processed.foreach(p => println(s"""[${Thread.currentThread().getName}]-$p"""))
        //singleThreadContext.shutdown()
      case Failure(f) =>
        f.printStackTrace()
        //singleThreadContext.shutdown()
    }

  }

  type Input = String
  type Output = String

  def processData: (Input => Future[Output]) = data => {
    Future {
      Thread.sleep(5000)
      s"[Thread-${Thread.currentThread().getName}] data $data is processed."
    }
  }
}

Output

$ sbt "runMain ParallelTasksWithCustomExecutionContext"
[info] Running ParallelTasksWithCustomExecutionContext 
[run-main-0]-Firing data1
[run-main-0]-Firing data2
[run-main-0]-Firing data3
[pool-7-thread-1]-[Thread-pool-7-thread-1] data data1 is processed.
[pool-7-thread-1]-[Thread-pool-7-thread-2] data data2 is processed.
[pool-7-thread-1]-[Thread-pool-7-thread-3] data data3 is processed.
<hangs>

And this is JVisualVM Thread monitor,

jvisual-vm

My question is how scala's global context terminates automatically without asking the client?

1

1 Answers

2
votes

Scala global context is created with usage of Thread factory which makes Threads daemons, so they (threads) won’t prevent the JVM from exiting once all user threads have finished their execution.

Check ExecutionContextImpl method def createDefaultExecutorService(reporter: Throwable => Unit): ExecutorService.