Когда начал писать для себя 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.