[Scala] sbt consoleでCtrl-Dが効かなくなったらthread設定を疑う
#scala環境
scalaVersion := "2.11.6"
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.11"
悩み
akkaでthread pool作って遊んでたらsbt consoleをctrl-Dで抜けられなくなった。
scala> import scala.concurrent.Future
scala> import akka.actor.ActorSystem
scala> implicit val ec = ActorSystem().dispatcher
ec: scala.concurrent.ExecutionContextExecutor = Dispatcher[akka.actor.default-dispatcher]
scala> Future(1)(ec)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@9ccb14d
scala> :q // consoleから抜けられない(´;ω;`)ブワッ
akkaが原因かな?と思い、ExecutorServiceを使ってみたけど同じくconsoleを抜けられない。
scala> import scala.concurrent.{Future, ExecutionContext}
scala> import java.util.concurrent.Executors
scala> implicit val ec = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(1))
ec: scala.concurrent.ExecutionContextExecutorService = scala.concurrent.impl.ExecutionContextImpl$$anon$1@8a0f6d7
scala> Future(1)(ec)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@64918adb
scala> :q // consoleから抜けられない(´;ω;`)ブワッ
scala.concurrent.ExecutionContext.Implicits.global
はそんなことないんだけどなぁ
scala> import scala.concurrent.{Future, ExecutionContext}
scala> implicit val ec = ExecutionContext.Implicits.global
ec: scala.concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl@73be4237
scala> Future(1)(ec)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@42ea62b5
scala> :q
[success] Total time: 77 s, completed 2015/09/15 22:23:06
>
threadをdaemonにすると解決するよ
Javaはデーモンスレッド以外のスレッドが終了したとき、プログラムを終了します。このとき、デーモンが処理をしているかどうかは関係ありません。なので、処理が途中でぶった切られる可能性があります。それがまずい場合はやっぱり自前でシャットダウンする機構を作るしかないのですが。。。
作成したthreadがdaemonでないから、メインのthreadを止めたあとも動き続けてしまっているらしい。
threadをdaemonにして再挑戦。
akka版
scala> import scala.concurrent.Future
scala> import com.typesafe.config.ConfigFactory
scala> import akka.actor.ActorSystem
scala> val config = ConfigFactory.parseString("akka.daemonic = on")
config: com.typesafe.config.Config = Config(SimpleConfigObject({"akka":{"daemonic":"on"}}))
scala> implicit val ec = ActorSystem("default", config).dispatcher
ec: scala.concurrent.ExecutionContextExecutor = Dispatcher[akka.actor.default-dispatcher]
scala> Future(1)(ec)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@70f8e6ab
scala> :q
[success] Total time: 101 s, completed 2015/09/15 23:07:48
> // (∩´∀`)∩ワーイ
ExecutorService版
scala> import java.util.concurrent.{Executors, ThreadFactory}
scala> import scala.concurrent.{ExecutionContext, Future}
scala> val factory = new ThreadFactory() {
| def newThread(r: Runnable) = {
| val t = new Thread(r)
| t.setDaemon(true)
| t
| }
| }
factory: java.util.concurrent.ThreadFactory = $anon$1@765df09a
scala> implicit val ec = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(1, factory))
ec: scala.concurrent.ExecutionContextExecutorService = scala.concurrent.impl.ExecutionContextImpl$$anon$1@623404ab
scala> Future(1)(ec)
res0: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@5b37f8e0
scala> :q
[success] Total time: 42 s, completed 2015/09/15 23:41:56
> // (∩´∀`)∩ワーイ
scala.concurrent.ExecutionContext.Implicits.global
もdaemonみたい。
https://github.com/scala/scala/blob/v2.11.6/src/library/scala/concurrent/impl/ExecutionContextImpl.scala#L74
そもそもdaemonとnon-daemonどっちがいいんです?
Java力低いので、scala.concurrent.ExecutionContext.Implicits.global
がdaemonだしdaemonでいいじゃん!程度にしか考えてない。
今回みたいなthread poolで使うケースだと、親のthreadが終了したら残っていても仕方ないのでdaemonで良いかな。