[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はデーモンスレッド以外のスレッドが終了したとき、プログラムを終了します。このとき、デーモンが処理をしているかどうかは関係ありません。なので、処理が途中でぶった切られる可能性があります。それがまずい場合はやっぱり自前でシャットダウンする機構を作るしかないのですが。。。

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で良いかな。