akkaでメールボックスをクリアしつつリスタートしたかったけど何か微妙。

リスタートの時にメールボックスクリアしたくなったんですが、(厳密にはRestartではなくてStop => 新しいのをStartみたいなのでもOKなんですが・・)あんまりいい方法がなくてうーんって感じだったので検討をメモ書き程度に。

結論としてはstashするか、deadletterが出るのを我慢するか、unhandledを我慢するか、雑に無視するかのどれかが良いという感じでしたが、まだあんまりこれだ!という手が見つかっていないのでちょっと参考にならないかもしれません。

また、自分が扱ってる状況とサンプルがだいぶ違うのでモチベーションが伝わらないと思います。なんでこんなことやってるんだというツッコミは半分くらい例が悪いことによるものです(;´Д`) (上手い例が思いつかずコードを超多くするか、極端に端折るかの二択になってしまいました。。。)

考えるのに使ったコードは↓にあります。 https://github.com/matsu-chara/AkkaSandbox/tree/master/src/main/scala/clear

そもそも標準APIにないんだからやらない方が良いというようなことな気がしつつ、考えていきます。

ベースの例

いきなり長いですが、やってることは簡単で、1~30の数字をprintlnするだけです。 printするWorkerActorはFSMになっていてconnectメッセージを処理しないとメッセージをprintする処理が出来ないようになっています。(connectが来ていない状態でメッセージを受け取るとunhandledになる。) また、途中で10を受け取ると例外が投げられるようになっています。

実験のためwhenUnhandledでわざとstopを呼んでいるので、下の例を実行しても1~9までしか表示されません。 1~9まで表示 => 10で例外 => 11を処理しようとするがunhandledになりstopする => 以降、全部deadletterとなります。

package clear

import akka.actor.SupervisorStrategy._
import akka.actor._
import clear.WorkerActor.{Running, Starting, State}

import scala.concurrent.Await
import scala.concurrent.duration.Duration

object ClearRestart extends App {
  val system = ActorSystem("clear")
  val actor = system.actorOf(Props[SupervisorActor], "supervisor")

  try {
    Thread.sleep(200)
    (1 to 10).foreach(actor ! _)  // 10を受け取ると例外が出てworkerがrestartされる
    (11 to 20).foreach(actor ! _) // restart時のconnectより先に積まれるのでunhandled

    Thread.sleep(1000)
    (21 to 30).foreach(actor ! _) // 以前のjobは実行されなくてもいいので、unhandledにならないで欲しい。そして、準備が出来たらそこから先のジョブは実行して欲しい
  } finally {
    Thread.sleep(3000)
    Await.result(system.terminate(), Duration.Inf)
  }
}

class SupervisorActor extends Actor {
  private val actor = context.actorOf(Props[WorkerActor], "worker")

  override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
    case _: ActorInitializationException ⇒ Restart
    case _: ActorKilledException ⇒ Stop
    case _: DeathPactException ⇒ Stop
    case _: Exception ⇒ Restart
  }

  def receive: Receive = {
    case x => actor forward x
  }
}

class WorkerActor extends FSM[State, Int] {
  startWith(Starting, 0)

  self ! "connect"

  when(Starting) {
    case Event("connect", _) =>
      println("connect")
      goto(Running)
  }

  when(Running) {
    case Event(10, _) =>
      throw new RuntimeException("die")
      stay()
    case Event(x, _) =>
      Thread.sleep(100)
      println(x)
      stay()
  }

  whenUnhandled {
    case _ =>
      println("unhandledになったのでstopします")
      stop()
  }
}

object WorkerActor {
  sealed trait State
  case object Starting extends State
  case object Running extends State
}

これを例に、「死ぬ前に積まれたjobは実行されなくてもいいのでunhandledにならないで欲しい。そして、準備が出来たらそこから先のジョブは実行して欲しい」といった要望を満たすことを考えてみます。 実行結果としては、「1-9は表示」、「10-20は表示されなくても良い」、「21-30は表示」という状態を目指します。 以降はここからのdiffで見ていきます。

1. シンプルにunhandledを無視

WorkerActorのStarting状態でunhandledが出るのが嫌なので、無視するcase節を追加すればいい!という作戦です。

これで結構上手くいくんですが、無理やり無視しているので本来無視したくないものも無視してしまうようなリスクがあるような無いような・・。リスタートしてから前世のメールボックスの内容を一つ一つ無視して捨てるのではなく、もう少しスマートに、リスタート時にmailboxごと破棄できないのか?と思ったのが今回の発端です。

    when(Starting) {
      case Event("connect", _) =>
        println("connect")
        goto(Running)
+    case Event(x, _) =>
+      println(s"初期化中なので無視します $x") // 本当に無視して大丈夫なのか・・状態不整合が隠れていないか不安。
+      stay()
    }

2. stashしてみよう

前節とやっていることはあまり変わらないのですがstashでもやってみました。 こちらは無視されるのではなくunstash時に既存メッセージが戻ってきます。今回は無視したいんですがclearStashはprivate APIなので呼べずunstashするしかなさそうでした。(すごい調べたわけではないですが多分あってるはず・・・。)

unstashしなければ無限に溜め込めるというわけでもない(いずれStashOverFlowで例外が飛ぶ)のでstashだけ呼んでunstashは呼ばない作戦は悪手になりそうです。(厳密にはstashを使う頻度とアクターの寿命とかによりそうですが、そこに頼るのは危険そうです。)

   when(Starting) {
     case Event("connect", _) =>
       println("connect")
+      unstashAll() // 古いジョブを実行したい場合はこちら。unstashしない場合、clearする方法は無さそうなのでいずれStashOverFlowになる?
       goto(Running)
+    case Event(_, _) =>
+      println("stashします")
+      stash()
+      stay()
   }

3. stopしてstartする

一番目の例で書いたスマートな方法に近いイメージのものです。

supervisorではRestartではなくStopを指定します。WorkerActorをwatchしておき、Terminatedメッセージを受け取ったら新しくアクターを作成します。

これにより前世の記憶を消し去ることに成功しました。しかし、こうするとstopした瞬間に積まれていたメッセージがデッドレターになるため、それが許容できるかどうか?といった話が出てきそうです。普段デッドレターが出ないようなケースだと、共通処理でデッドレターを監視して検知とかやっているとちょっとめんどくさそうな。でも普通そこまでやらない気がします。デッドレター自体は普通に作っても出たりしますし・・。ということで大抵のケースでは許容できそうという思いがあります。

 class SupervisorActor extends Actor {
-  private val actor = context.actorOf(Props[WorkerActor], "worker")
+  private var actor = createWorker()
+
+  private def createWorker() = {
+    val a = context.actorOf(Props[WorkerActor], "worker")
+    context.watch(a)
+    a
+  }

   override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
     case _: ActorInitializationException ⇒ Restart
     case _: ActorKilledException ⇒ Stop
     case _: DeathPactException ⇒ Stop
-    case _: Exception ⇒ Restart
+    case _: Exception ⇒ Stop // 止めると未処理メッセージがdeadletterになる
   }

   def receive: Receive = {
+    case Terminated(ref) =>
+      actor = createWorker()
     case x => actor forward x
   }
 }

ちなみにデッドレターログは以下のようにして止めることが出来ます。ActorSystem全体で止めることになるので完全に止めるのは少し不安な気がします。(何かの異常でデッドレター出てる時に気づけなくなるため) ログに出しておいて気にしないようにするのが良さそうです。

設定値のintは、10だとデッドレターを10件までログに出す。20だと20件までログに出す。といった意味です。

-  val system = ActorSystem("clear")
+  val system = ActorSystem("clear", ConfigFactory.parseString("akka.log-dead-letters = 0")) // deadletter logをオフ(システムグローバルにオフだとちょっと不安・・)

4. restart時に頑張る

stop & startと仕組みはほぼ同じですが、中間にアクターを増やすことで「中間のアクターをリスタートさせた結果、WorkerActorがstop => startされる」というロジックでほぼ同じことが出来ます。context.watchし忘れを防止出来る一方で、登場人物が増えるというなんだかどっちもどっちな方法です。

未処理メッセージがデッドレターになるのはこちらも同様です。

 class SupervisorActor extends Actor {
-  private val actor = context.actorOf(Props[WorkerActor], "worker")
+  private val actor = context.actorOf(Props[WorkerVisorActor], "worker_visor")

   override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
     case _: ActorInitializationException ⇒ Restart
@@ -39,6 +40,19 @@
   }
 }

+class WorkerVisorActor extends Actor {
+  private val actor = context.actorOf(Props[WorkerActor], "worker")
+
+  override def supervisorStrategy: SupervisorStrategy = OneForOneStrategy() {
+    case _: Exception ⇒ Escalate // 孫はstopされるが、未処理のメッセージがdeadletterになってしまう。
+  }
+
+  def receive: Receive = {
+    case x =>
+      actor forward x
+  }
+}

5. もうunhandledで良くない?

ここまでくると一番最初に書いた単純に無視する案が一番良いように思えてきました。

そして無視するならwhenUnhandledでログ出してstayすれば同じじゃないかという気持ちに・・。(本来のコードではunhandledのときの処理をある程度共通化していて、そこはオーバーライドしたくないという動機があったのですが、それもそこまですごい重大な動機でもなく、ここまで頑張る必要もないな・・・という感じです)

とはいえFSMのStateがいくつかある場合、whenUnhandledでログだけ出してstayする方法ではアクターの全Stateでunhandledなメッセージが無視される一方で、最初に書いた無視案では特定のStateでだけunhandledなメッセージを無視する挙動を書くことができるのでwhenUnhandledではstopさせたりアラートを出したり出来るかもしれません。そう考えるとそこまで捨てたもんじゃない?という気持ちもあります。

デッドレターになるのも良さそうですが、無視するのに比べると若干実装めんどくさいような気もするので(とはいえ数行?)選びどころですかね。 一律で無視すると実装ミスで発生した意図しないメッセージまで無視してしまう可能性あるのでちゃんとやりたいならこっちのほうが良い気がしています。

6. カスタムdispatcher

custom dispatcherを自前で作ればできそうという書き込みをMLでみつけました。

https://groups.google.com/forum/?hl=en.#!searchin/akka-user/Clearing$20all$20mailboxes$20while$20restarting%7Csort:relevance/akka-user/3qJLNUTcLDc/wJxLx75orEEJ

試してはいないんですが、カスタムで作られたdispatcherをメンテしたくない気持ちがあります。(リンク内にもoverkillとありますし・・。) ただカスタムdispatcherってわりと普通に作る物なのかどうかよくわかってないので、その辺どうなんだろうと思いつつ今回は終わりです。

カーネギーメロンのDBに関する講義が面白いのでおすすめ

ここに書くことによって途中でやめられなくするメソッドです。

ハッカーニュースを眺めていたら以下のようなCS系講義動画のまとめリポジトリが流れていました。

GitHub - Developer-Y/cs-video-courses: List of Computer Science courses with video lectures.

へーっと思いながら何個かポチってみたところ以下に出くわしました。

15721.courses.cs.cmu.edu

英語が(自分にとって)聞き取りやすく、動画の品質(画質やスライドがちゃんと見えるかどうかといった部分)も良いものでかつ興味のある内容で出来ればスライドもおしゃれで・・・となるとなかなか少ないですが、これはかなり見やすいです。 スライドも概念図が頻繁に登場したりして、これだけでも聞き取れなかった部分などをかなり補完できます。

スケジュールページのVideoアイコンからページに飛んでもらえるとわかるのですが、スライドごとに動画を飛ばしたり戻したりできるのでわからなかったところはもう一回、この辺はわかるから2スライド分とばすみたいなことがやりやすいです。

http://cmudb.io/15721-s16-lect01

さらにページを良く見てみると結構いろいろな講義が上がっているようです。 今回紹介する、Database Systems 以外にも

など色々あります。

特にSeven Databases in Seven WeeksはMemSQL, Microsoft(Hekaton), NuoDB, MongoDB, Tokutek, VoltDBのCTOやリードエンジニアが一回ずつそれぞれのDBについて紹介してくれるという非常に豪華な内容になっています。

どんな講義?

さてDatabase Systemsの話にもどります。

この講義ではsingle nodeのin-memory databaseの内部アーキテクチャについて扱います。(分散DBについては対象外) classicalなDBMSについては扱わずstate-of-the-artなトピックについて扱うとのことです。(classicalなやつが軽視されているわけではなく、別の講義などで学習済みであることを想定しているっぽいです。)

具体的には、以下のようなトピックがあります。

  • Concurrency Control
  • Indexing
  • Storage Models, Compression
  • Join Algorithms
  • Logging & Recovery Methods
  • Query Optimization, Execution, Compilation
  • New Storage Hardware

より詳細には以下にまとまっています。 Schedule - CMU 15-721 :: Database Systems (Spring 2016)

内容としてはin-memory DBって既存のDBにキャッシュを詰みまくるのとどう違うの?という疑問に直球で答えを返せるようなイメージでしょうか。 ディスク中心のDBでは如何にディスク I/Oを避けるかが主要な関心事になっていますが、in-memoryではデータアクセスコストが(ディスクアクセスと比べれば)かなり低くなるのでボトルネックが変化します。 既存のアーキテクチャはdisk I/Oを避けるのに特化した構成になっているため、in-memoryの速さを活かすためには新たなボトルネックに合わせたin-memoryを前提としたアーキテクチャが必要になります。 この講義では、そのようなアーキテクチャについて一つ一つ学んでいく形になります。

例えば、 ディスク指向のDBではデータアクセスの際、 ディスクアドレスを計算 => buffer pool managerにキャッシュがメモリにあるか問い合わせる 、といった処理が入りますが、in-memoryを前提にすればこの処理はカットできる。とかロックをかけること自体がネックになるのでCASを使ったものにしたりロックの粒度を荒くしてときにはDB全体でロックをかけるといったことも行われる。(そうすると、全体が逐次実行になる。その結果CPUキャッシュが効くようになる!すごい!)といったことなどが挙げられます。

また、この講義では指定された論文を読んでまとめを提出したりコードを書く課題などがあります。 論文リストはスケジュールに載っています。 リスト内には2015年発表のものなど、かなり新しめの論文などが含まれているため読んでいても知ってるモダンなDBの中身について書いてあったり、あるいは全く知らなかった新しいDBについて知ることが出来ます。割とこっちが面白いというか、最近のとりあえずこれ抑えようリストが上がっているのは非常に助かっています。

で、どうなの?

こういう意識高いやつ何回か挑戦しつつ結局二回目くらいまで聞いて挫折しているんですが、現在のところ5回目くらいまで聞けているので結構良いんじゃないかと思っています。 どちらかというと動画を見るよりかは論文を読むのを中心に進めています。

読んだものを簡単に紹介します。(内容のサマリというよりかは、メモ書きに近いです)

M. Stonebraker, et al., What Goes Around Comes Around, in Readings in Database Systems, 4th Edition, 2006 (Optional)

データモデリングについての35年分の歴史を辿り、そこから得られる教訓についてまとめた論文。IMS => CODASYL => Relational => ...と進んでいく中で何を学んだのか?これから同じ失敗や議論を繰り返さないためにはどうすればよいか?について書いてあります。(その分野の巨人が動かないと結局流行らないよね。とか、改良があったとしても移行する動機が薄いと普及しないよね。みたいな技術的なところ以外(そこも含めて技術?)の要因についても背景をしっかりおさえて書いてあります。

A. Pavlo, et al., What's New with NewSQL?, 2015 (Optional)

NewSQLという分野(ACID特性を諦めないでNoSQLのようなスケーラビリティを目指すDBMS)についてのまとめです。スケーラビリティを求めてEventual Consistencyでも動くコードをアプリケーションで頑張って書かなくてもDBがやってくれる方が生産性が良いってGoogle Spannerの著者も言っていたらしいです。

The authors of Spanner even remark that it is better to have their application programmers deal with performance problems due to overuse of transactions, rather than writing code to deal with the lack of transactions as one does with a NoSQL DBMS [24].

この分野ではCockroach DB, SAP HANA, VoltDBなどがありますが(本文中ではもっと紹介されています)、これらのDBがどのようなアーキテクチャになっているかそれぞれざっくり知ることが出来ます。

H. Garcia-Molina, et al., Main Memory Database Systems: An Overview, in IEEE Trans. on Knowl. and Data Eng., 1992

ディスクではなくメモリに全てのデータを格納することを前提としたDBのアーキテクチャについてまとめられています。 いい面ばかりではなく、HDDは故障しても部分的にリストアできたりできるけどメモリは全体が壊れやすい。もしハードウェアが信頼できるようになったとしてもOSが暴走したらメモリが書き換えられたりして結局DBに格納されてるデータが壊れる・・?といった懸念があるのでチェックポイント増やしたほうが良いか・・?でもそうするとチェックポイントたくさんつくるために新しくボトルネック出来てしまう・・?といった事柄についても議論されています。

上の方で書いたMMDBならロックの粒度をDB全体にしても良いっと書いてあるのはこの論文です。ロックの対象をDB全体にするとトランザクションが逐次実行になります。逐次実行になるとCPUのキャッシュが活かせる(今まではトランザクションが待ち状態になるとキャッシュが無駄になっていた)ため更に性能が見込めるかもといった内容もあり面白いです。(今までdisk I/Oと戦っていたのにいきなりCPUのキャッシュの話に・・!)

その他にもConcurreny Control, index, ロックなどあらゆる事柄の変化について解説されています。

X. Yu, et al., Staring into the Abyss: An Evaluation of Concurrency Control with One Thousand Cores, in VLDB, 2014

1000コア時代が到来した時に今のConcurrency Controlの仕組みでスケールするか検証したらダメだったので頑張ろう・・!みたいな論文。 大きく分けて2phase-commit方式とTimeStamp Ordering方式があり、それぞれの中でも異なる実装方式を比較したりしています。 コア数増えるとAtomicIntegerのCAS使うとキャッシュコヒーレンスのための命令が飛び交って遅くなってしまう。 => バッチでtimestampをまとめて発行すればいいかと思ったけど、トランザクションが失敗した時にtimestampがまとめて無効になってしまうから競合が多いと逆効果になったり。などTAoMP に書いてあるような知識が役立ちそうでした。 図10の競合が多い場合の書き込みワークロードを見るとコア数が1のときのほうがコア数500や1000のときよりスループットが高い(10万txn/s => 5万txn/sになってしまう)といったケースもあり、このようなケースでもスケールできる方式が求められていそうです。 この論文でこの方式はうまくいかないと書いてあっても現代の1000より大幅に少ないコア数だと普通に成立したりする気がするのでその辺は注意が必要ですね。

ということで割りとなんとなくですが、学んでいますという紹介というか勉強ログ的な記事でした。

(ansible 2.1までは)ansible_managedを使うと毎回changed扱いになる可能性がある

以下の話題はansible 2.2以降では修正されています。 https://github.com/ansible/ansible/pull/18094

ansible_managed

ansibleのtemplate内で {{ ansible_managed }} とすると Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}的なおしゃれな文言を埋めてくれる機能があります。ファイルの先頭にコメントとして付けておくと既にansibleで管理されているファイルなのか、元から入っているファイルなのかわかりやすくなる効果があります。*1

ところがデフォルトで入っているこのdatetimeの部分が曲者で、ファイルの内容が変わったかどうかをチェックしていたりはせず単にファイルの修正時刻をフォーマットするだけなので、git checkoutやcloneなどの操作で容易に内容が変わってしまいます。もちろんconfファイルがchangedになるとhandlerが呼ばれてサーバーがリスタートされたりするわけでansibleの実行に無駄に時間がかかってしまいます。

http://docs.ansible.com/ansible/intro_configuration.html#ansible-managed をよく見ると書いてあることなんですが、知らなくて毎回changedになっているところがありました( ◜◡‾)

ansible.cfg[default]ansible_managed = Ansible managed のような静的な文字列を設定すればこの問題を回避できます。

設定例

[defaults]
ansible_managed = Ansible managed file, do not edit directly

この件について調べてみると以下のissue & PRで修正済みでした。(2.2には既にこの修正が入っています)

議論によるとdatetime以外のuidやhostも実行環境によって変わってしまうから除去したとのことでした。とはいえ2.2以降でさらに変わるかもしれないオーラがありそうです。

ansible 2.2にあげたいという気持ちが高まるという点ではansible_managedはおすすめ機能かもしれません!(?)

*1:自分の環境だとサーバーに潜ってファイルいじったりとかはしないので気休め程度ですが、一部だけ管理抜けてる・・?みたいなことに気づきやすくなるメリットがあります。(一行目にansible_managedが入っていることを仕組み的に強制できたりはしないので、結局安心できるものではないんですが・・)

akka FSMでstateTimeoutが一度メッセージを受け取らないとスケジュールされないときはinitializeの呼び忘れかも

結論

initialize() https://github.com/akka/akka/blob/v2.4.13/akka-actor/src/main/scala/akka/actor/FSM.scala#L511-L522 をFSMのコンストラクタの最後(または適切なライフサイクルメソッド内)で呼びましょう。

ActorのreceiveTimeout

akka actorにはreceiveTimeoutという一定時間メッセージが来なかったらReceiveTimeoutメッセージを送るという便利機能があります。 これを使うと、返信が一定時間なかったらもう一度メッセージを送るといったリトライ処理が書けるようになります。

http://doc.akka.io/docs/akka/2.4/scala/actors.html#Receive_timeout

class FooActor extends Actor {
  context.setReceiveTimeout(2.seconds)

  def receive = {
    case ReceiveTimeout =>
      println("timeout")
      context.setReceiveTimeout(Duration.Undefined) // Undefinedにすると解除できる
    case x => println(x)
  }
}

FSMのstateTimeout

そしてFSMにはstateTimeoutという、その状態になってから一定時間メッセージが来なかったらタイムアウトする機能があります。

object FsmMain extends App {
  val system = ActorSystem("MyActorSystem")
  val bar = system.actorOf(Props[Bar])
}

// `initialize()`を呼び忘れているのでこのままでは動かない(後述)
class Bar extends FSM[Int, Unit] {
  startWith(0, (), timeout = Some(2.seconds)) // whenで指定したtimeoutを上書きできる

  when(0, stateTimeout = 3.seconds) {
    case Event(StateTimeout, _) =>
      println("timeout")
      stay()
    case Event(x, _) =>
      println(x)
      stay() forMax(4.seconds) // whenで指定したtimeoutを上書きできる
  }
}

また、stateTimeoutの値は when で登録する以外にも startWithの引数で startWith(state, data, timeout = Some(2.seconds)としたり、状態遷移で goto(...) forMax(3.seconds) のようにforMaxを指定することで上書きすることができます。 この機能により「リクエストを送り、最大3秒間Response待ち状態になる。タイムアウトしたらリトライを行うかリクエストに対してエラーを返す」といった処理が書きやすくなります。

なお、FSM自体もActorなので引き続き前述のsetReceiveTimeoutを利用することが出来ます。 その場合はwhenの中に case Event(ReceiveTimeout, _) => を記述すれば取得することが出来ます。(個人的には一緒に使うとややこしいことになりそうなのでやめておきたい気持ちがあるような・・・)

FSM stateTimeoutのtimer起動のタイミング

で、この機能を使うかーと思いTimeoutメッセージが届くか実際に実験をすると、FSM作成直後や再起動直後にtimerが作動しておらず、timeoutメッセージが届かない症状に出くわしました。 例えば一つ上のFsmMainBarの例を実行すると、永久に println("timeout") を通過することはありません。この状態でstateTimeoutさせるためには一つ以上のメッセージを送る必要がありました。

timeoutのスケジュールはreceiveの中で呼ばれるmakeTransitionの中 https://github.com/akka/akka/blob/v2.4.13/akka-actor/src/main/scala/akka/actor/FSM.scala#L699-L705 で行われています。 ということは初回はmakeTransitionが呼ばれないのか・・?おかしいなー、おかしいなー、と思ったのですが、よくよく考えるとFSMのコンストラクタでinitialize()を呼び忘れていました 😨

initialize()https://github.com/akka/akka/blob/v2.4.13/akka-actor/src/main/scala/akka/actor/FSM.scala#L511-L522 で定義されています。実装的にはほとんど makeTransitionを呼び出すだけです。 ドキュメント http://doc.akka.io/docs/akka/2.4/scala/fsm.html#A_Simple_Example にも以下のようにしっかり書いてあります。

finally starting it up using initialize, which performs the transition into the initial state and sets up timers (if required).

これを呼び出すとしっかりとtimeoutされだすようになりました。

// 修正版
class Bar extends FSM[Int, Unit] {
  startWith(0, (), timeout = Some(2.seconds))

  when(0, stateTimeout = 3.seconds) {
    case Event(StateTimeout, _) =>
      println("timeout")
      stay()
    case Event(x, _) =>
      println(x)
      stay() forMax(4.seconds)
  }

  initialize()
}

めでたしめでたし。

FSMだと状態がwhenで初めて登録されるので、登録されるまでmakeTransitionできないためこのような仕様になっている・・んでしょうか? initialize()は忘れていても気づきにくい(特に最初はタイムアウト使って無くて後から入れる場合など)ので影響がありそうなら気をつけましょう(´・_・`)

OpenStackのUbuntuでdocker使ったらコンテナからのcurlがハングする件

dockerコンテナ上で何故かcurlが上手くつながらない問題にハマったのでメモするシリーズ。

環境

  • Ubuntu 14.04.5 LTS ( on OpenStack)
  • Docker version 1.11.2, build b9f10c9

症状

ホストで curl https://github.com は正常にレスポンスを受け取れるのにもかかわらず、コンテナ上で同じことを行うとレスポンスが帰ってこない(ハングする)。また極稀にレスポンスが中途半端に返ってくることがある。

原因

コンテナのMTUの値がホストのeth0と合っていなかった。( ip addr などで確認)

(暫定的な)対処法

DOCKER_OPTSに --mtu 1400 など(数字はeth0のMTUと合わせる)を加えてdockerを再起動する。

背景とか

調べてみると何個かこういう報告がありました。

don't try to use default route MTU as container MTU by phemmer · Pull Request #18108 · docker/docker · GitHub というPRがあり、docker v1.10からはdefault route(今回はeth0)からMTUを取得するのを止めたようです。

とはいえ--mtuでMTUを固定するのはハックで、本来はPMTU discoveryを正しく設定すべきらしいです。ちゃんと設定している人がどのくらいいるのかは不明です。 https://github.com/docker/docker/issues/22297#issuecomment-214570420