Когда начал писать для себя hello world на akka сразу захотелось сделать какой-то артефакт, удобный для жизни в дикой среде production. Хочется, чтобы приложение вело себя как примерный гражданин, умело правильно реагировать на всякие системные сигналы и прочее.
С упаковкой приложения в jar вместе с scala runtime удалось легко разобраться. Помогут sbt-native-packager
и sbt-assembly
. Напишу про это позже, когда ещё пойму как правильно результат сложить в .deb пакет.
Не сразу почему-то удалось сделать правильные реагирования на сигналы системы. Меня пока не интересуют всякие специфичные штуки, достаточно было двух сигналов выключения - SIGINT и SIGTERM, по которым хотелось аккуратно потушить ноду в akka cluster - убить её локальные акторы, отсоединиться и пр.
Сначала попробовал использовать модный_молодёжный метод scala.sys.addShutdownHook. Он принимает => Unit
и выполняет его в отдельном треде, когда завершается jvm. Вызов не гарантируется, что в данном случае ок. Плюс метода - переносимость. Минус - код возврата не возможно поставить, метод System.exit()
выполняется, но код не ставиться. Экспериментально понял, что на SIGINT код всегда 130, на SIGTERM - 146. Вторая проблема - akka при выключении часть логов пишет в stdout, которые при использовании хука уже не выводятся. Не проверял, но кажется stdout закрывается раньше, чем вызывается этот хук.
Пришлось взять в руки обработчик сигналов и делать на нём. Он из пакета sun.misc
, так что с альтернативными jvm могут быть вопросы, но меня это не волнует в данный момент.
В итоге метод выглядит примерно так:
import java.util.concurrent.atomic.AtomicBoolean
import sun.misc.SignalHandler
import sun.misc.Signal
import scala.concurrent.duration._
import com.typesafe.config.ConfigFactory
import akka.cluster.Cluster
import akka.actor.ActorSystem
import akka.util.Timeout
object Main extends App with SignalHandler {
val SIGING = "INT"
val SIGTERM = "TERM"
val terminated = new AtomicBoolean(false)
// регистрируем сам App объект, как обработчик сигналов
Signal.handle(new Signal(SIGING), this)
Signal.handle(new Signal(SIGTERM), this)
val config = ConfigFactory.load()
val system = ActorSystem("main", config)
implicit val executionContext = system.dispatcher
implicit val defaultTimeout = Timeout(500.millis)
val cluster = Cluster(system)
//hook для akka, будет использоваться для любых остановок, не только по сигналам
system.registerOnTermination {
System.exit(0)
}
// собственно обработчик
override def handle(signal: Signal): Unit = {
if (terminated.compareAndSet(false, true) && List(SIGING, SIGTERM).contains(signal.getName)) {
system.shutdown()
}
}
}
Теперь и сигналы честно обрабатываются, и коды возвратов правильные, и все выходы из приложения сводятся в хук для akka.