Akkaのエラーハンドリングストラテジを確認したい
はじめに
Akkaを実運用に突っ込もうと考えたときに一番最初に気になるのはActorがくたばった時の挙動ですよね。 知らない間にActorがくたばっていてメッセージだけが虚空の彼方に消えていったなんて事態になった日には次回の自身のボーナスも虚空に消えかねません。
ボーナスを死守するためにもエラーハンドリングは必須の知識として学ぶべきです。
サンプルコード
今回はこんな感じのサンプルコードを使用します。
package org.jyuch import akka.actor.SupervisorStrategy.{Escalate, Restart, Resume, Stop} import akka.actor.{Actor, ActorSystem, OneForOneStrategy, Props} import scala.concurrent.duration._ import scala.language.postfixOps object Hello { def main(args: Array[String]): Unit = { val system = ActorSystem() val calc = system.actorOf(CalcActor.props()) calc ! Add(1, 2) calc ! Add(-1, 2) calc ! Add(1, 2) calc ! Subtract(3, 2) io.StdIn.readLine() system.terminate() } } trait OperandActor extends Actor { override def postRestart(reason: Throwable): Unit = { super.postRestart(reason) //println(reason.getClass.getTypeName) } } class CalcActor extends OperandActor { val add = context.actorOf(AddActor.props(), "add") val subtract = context.actorOf(SubtractActor.props(), "subtract") def receive = { case Result(v) =>{ println(this + " : " + v) } case a: Add => add ! a case s: Subtract => subtract ! s } override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: Exception => Stop } } object CalcActor { def props(): Props = Props[CalcActor] } class AddActor extends OperandActor { def receive = { case Add(x, y) => { println(this) if (x < 0 || y < 0) throw new Exception() sender() ! Result(x + y) } } } object AddActor { def props() = Props[AddActor] } class SubtractActor extends OperandActor { def receive = { case Subtract(x, y) => { println(this) if (x - y < 0) throw new Exception() sender() ! Result(x - y) } } } object SubtractActor { def props() = Props[SubtractActor] } sealed abstract class Operand case class Add(x: Int, y: Int) extends Operand case class Subtract(x: Int, y: Int) extends Operand case class Result(value: Int)
子Actorの後始末
エラーハンドリングストラテジは親ActorのsupervisorStrategy
で制御されますが、何も指定しない(デフォルト)とくたばったActorだけを再起動します。
supervisorStrategy
で指定できるストラテジは以下の感じになります。
ストラテジ | 意味 |
---|---|
OneForOneStrategy |
子Actorがくたばったらそいつだけ何とかする |
AllForOneStrategy |
子Actorがくたばったらすべての子Actorを何とかする |
で、何とかする内容が以下の感じです。
ストラテジ | 意味 |
---|---|
Escalate |
上位のActorに判断を委譲する |
Restart |
子Actorを再起動する |
Resume |
エラーを気にせず続行する |
Stop |
子Actorを停止する |
そして、ストラテジをこんな感じに指定します。
import akka.actor.OneForOneStrategy import akka.actor.SupervisorStrategy._ import scala.concurrent.duration._ override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException ⇒ Resume case _: NullPointerException ⇒ Restart case _: IllegalArgumentException ⇒ Stop case _: Exception ⇒ Escalate }
ガーディアンアクター
上記の指定で子がくたばった時の挙動を制御できるようになりました。じゃあ自身がくたばったらどうなるのさ?
自身がほかのActorの子だったらそいつが決めますが、自身がsystem.actorOf
が生成されていた場合は自身の命運はガーディアンアクターが決めます。
Akka 2.1以降では設定ファイルのakka.actor.guardian-supervisor-strategy
でガーディアンアクターのストラテジを設定出来ます。
actor { # Either one of "local", "remote" or "cluster" or the # FQCN of the ActorRefProvider to be used; the below is the built-in default, # note that "remote" and "cluster" requires the akka-remote and akka-cluster # artifacts to be on the classpath. provider = "local" # The guardian "/user" will use this class to obtain its supervisorStrategy. # It needs to be a subclass of akka.actor.SupervisorStrategyConfigurator. # In addition to the default there is akka.actor.StoppingSupervisorStrategy. guardian-supervisor-strategy = "akka.actor.DefaultSupervisorStrategy"
現状ではakka.actor.StoppingSupervisorStrategy
かakka.actor.DefaultSupervisorStrategy
が指定できるようです。
おわりに
これならActorがくたばったときにいい感じにActorを再起動できそうです。
これでボーナスを心配することなく安心して今夜も眠ることが出来そうです。
おわり
Scalaでも演算子のオーバーロードをしたい
はじめに
Scalaの演算子オーバーロードについてサクッと確認してみます。
オーバーロード
まぁ、とりあえず言語仕様を見ておけば安心だよねといった感じです。困ったら公式ドキュメントを読む。日本書紀にもそう書いてあります。
case class AltInt(val value: Int) { def +(x: AltInt) = plus(x) def plus(x: AltInt) = AltInt(value + x.value) def unary_~ = AltInt(value * -1) def +:(x: Int) = AltInt(value + x) def -:(x: AltInt) = { println(s"$value - ${x.value} = ${value - x.value}"); AltInt(value - x.value) } def *(x: AltInt) = AltInt(x.value * value) def ! = println(value) }
中置演算子
Scalaでは引数を一つ取るメソッドはすべて中置演算子として扱えます。
逆に+
のような演算子も普通*1のメソッドのように呼び出せます。
val a = AltInt(10) val b = AltInt(20) println(a.plus(b)) println(a plus b) println(a.+(b)) println(a + b)
なお、優先順位はメソッドの先頭の文字によって規定されるようです。
先頭の文字 | 優先度 |
---|---|
(all letters) | 低い |
| | |
^ | |
& | |
= ! | |
< > | |
: | |
+ - | |
* / % | |
(all other special characters) | 高い |
val a = AltInt(10) val b = AltInt(20) println(a * a + b) // 120
また、メソッドが:
で終わる場合は引数とレシーバの位置関係が逆転します。
println(1 +: a)
また、通常演算子は左結合ですがこの場合は右結合になります。
println(b -: a -: a)
10 - 10 = 0 0 - 20 = -20 AltInt(-20)
後置演算子
中置演算子と同じように引数を取らないメソッドは後置演算子として呼び出せます。
println(a!) println(a.!)
前置演算子
前置演算子はunary_記号
で定義できますが、記号に入ることができるのは+
・-
・!
・~
に限られます。
println(~a) println(a.unary_~)
おわりに
素直でいいですね。
*1:普通?普通ってなんだ?
implicitパラメータとは何ぞや
はじめに
PlayでコントローラからビューからFuture[T].map
からお前らimplicit
パラメータ好きすぎだろってくらい多用されています。これはゆるふわっと使っていると後でヤバい目に遭うなと思ったので軽く調べてみました。
ゆるふわ愛されimplicit
言語仕様のimplicitsセクションを見ながら確認します。困ったときは公式ドキュメントを読む。古事記にもそう書いてあります。
とりあえず以下が一番シンプルな形です。
object Hello { def main(args: Array[String]): Unit = { implicit val hoge = "jyuch" greet("Hello") } def greet(message: String)(implicit who: String): Unit = { println(message + " " + who) } }
Scalaコンパイラはimplicit
パラメータが明示的に与えられていないときはメソッドの呼び出し箇所からプレフィックス無しでアクセス可能なimplicit
付き識別子を探し出してパラメータを適用します。
Scalaコンパイラ頑張りすぎてない?大丈夫?過労死しない?顔色悪いんじゃない?
また、プレフィックスなしでアクセス可能なら検索対象になりうるということは、何でもかんでもワイルドカードでインポートすると誤爆する可能性もあるってことですよね。
当たり前と言えば当たり前ですが、候補となるパラメータが複数ある場合はどちらを使えばいいか判断ができないためコンパイルエラーとなります。
def main(args: Array[String]): Unit = { implicit val hoge = "jyuch" implicit val fuga = "hoge" // Error:(8, 10) ambiguous implicit values: // both value fuga of type String // and value hoge of type String // match expected type String // greet("Hello") greet("Hello") }
こちらも当たり前と言えば当たり前ですが、トレイトの実装クラスや抽象クラスの実装クラスも適用します。
object Hello { def main(args: Array[String]): Unit = { implicit val fuga = new Fuga() greet("Hello") } def greet(message: String)(implicit who: Hoge): Unit = { println(message + " " + who.who) } } trait Hoge { val who = "hoge" } class Fuga extends Hoge { override val who = "jyuch" }
また、シングルトンオブジェクトでも出来るようです。(これは意味の無い例ですが)
object Hello { implicit object Hoge { val who = "jyuch" } def main(args: Array[String]): Unit = { greet("Hello") } def greet(message: String)(implicit who: Hoge.type): Unit = { println(message + " " + who.who) } }
また、パターンとして
- compound type(
hoge: Hoge with Fuga
みたいなアレ) - parameterized type(
S[T1, T2...Tn]
みたいなアレ) - type projection S#U(ネストクラスをいい感じにするアレ)
- 型エイリアス
implicit val aaa: Hoge => Fuga = { x: Hoge => Fuga(x.name) }
でdef piyo(implicit a: Hoge => Fuga)
ってなるアレ(名前を知らない)
も大丈夫なようです。*1
で、一番気になるのが暗黙の型変換と暗黙のパラメータを組み合わせて適用可能なのかといったところです。これが可能ならば暗黒プログラミングが捗る可読性が下がってつらいですね。
object Hello { implicit def hoge2Fuga(hoge: Hoge): Fuga = hoge match { case Hoge(x) => Fuga(x) } def main(args: Array[String]): Unit = { implicit val name = Hoge("jyuch") greet("Hello") } def greet(message: String)(implicit who: Fuga): Unit = { println(message + " " + who.name) } def piyo(implicit a: Hoge => Fuga) {} } case class Hoge(name: String) case class Fuga(name: String)
Error:(14, 10) could not find implicit value for parameter who: example.Fuga greet("Hello") Error:(14, 10) not enough arguments for method greet: (implicit who: example.Fuga)Unit. Unspecified value parameter who. greet("Hello")
暗黙の型変換をぶちかましつつ暗黙の引数に突っ込むまではしないようです。
おわってない
と、ここまでで仕様書のImplicit Parametersセクションの半分です。
残り半分くらいでネストされた場合のimplicit
パラメータの挙動について述べていますが、力尽きました。
おわりに
Scalaむずかしい
*1:2.12の言語仕様ではもう一つ箇条書きの項目があるのですが、何を言っているのか弊社には全然わかりませんでした