Reputation: 34099
I have following code:
object KafkaApi {
private implicit val main: ExecutionContextExecutor = ExecutionContext.global
private val workers = ExecutionContext.fromExecutor(Executors.newCachedThreadPool())
def main(args: Array[String]) {
foo.unsafeRunAsync(_ => ())
//foo.unsafeRunSync()
println("Hello")
}
def foo: IO[Unit] =
for {
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO.shift(workers)
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO.shift(main)
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
_ <- IO {
println(Thread.currentThread().getName)
}
} yield ()
}
and the output is:
main
Hello
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
scala-execution-context-global-14
scala-execution-context-global-14
scala-execution-context-global-14
What is the difference between main and scala-execution-context-global-14?
If these two are different, how to get the main thread back?
Upvotes: 1
Views: 1267
Reputation: 23788
Running the code above, why the application never get terminated?
This additional question is too big for a comment so here goes my answer.
The thing is that in JVM all Thread
s are divided into "normal" and "daemon" threads. The important thing here is that
The Java Virtual Machine exits when the only threads running are all daemon threads.
So if you have any running non-daemon Thread
, JVM thinks your application is still working even if it actually does nothing (maybe it is just waiting for some input). The "main" thread is obviously a "normal" thread. Threads created by standard ExecutionContext.global
are daemon and thus don't stop your app from quitting when the main thread finishes. Threads created by Java's Executors.newCachedThreadPool
are non-daemon and thus keep the application alive. There are several possible solutions:
Don't use other ExecutionContext
except for the global
i.e. don't use Executors.newCachedThreadPool
at all. Depending on your case this might be or not be what you want.
Explicitly shutdown
your custom ExecutorService
when all its job is done. Be careful here because shutdown
doesn't wait for all active tasks to be finished. So the code should become something like
private val pool = Executors.newCachedThreadPool
implicit private val workers = ExecutionContext.fromExecutor(pool)
// do whatever you want with workers
// optionally wait for all the work to be done
pool.shutdown()
val workers = ExecutionContext.fromExecutor(Executors.newCachedThreadPool(new ThreadFactory {
private val defaultDelegate = Executors.defaultThreadFactory()
override def newThread(r: Runnable): Thread = {
val t = defaultDelegate.newThread(r)
//let the default factory do all the job and just override daemon-flag
t.setDaemon(true)
t
}
}))
IMHO the main trade-off between #2 and #3 is convenience vs correctness. In #3 you don't have to think where all tasks are finished so it is safe to call shutdown
which is convenient. The price is that if for some reason you misjudged and your "main" thread quits before all other tasks are finished, you will not know that anything went wrong because daemon threads will be just silently killed. If you go with #2 and do the same mistake either your app will continue to run if you din't call shutdown
in that code path, or you will see some warning in the log that the pool was shutdown while there still were some tasks in progress. So if this is just a middle step in a long sequence of processing what for some reason requires custom thread pool I'd probably go with #3; but if this parallel execution is the main behavior I'd go with more explicit #2 way.
Upvotes: 4