読者です 読者をやめる 読者になる 読者になる

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

ProtocolBuffersでprimitiveのデフォルト値と値が入っていないことを区別したいときにどう書くか

結論

wrappers.protoが便利

背景

protobufでは値を省略したときに、その型で定められたデフォルト値が代入されます。 例えばstringを省略すると自動的に""を指定したことになり、「値が指定されなかった」のか「空文字列を明示的に入れた」のかを区別しないように書くことが求められます。

この仕様は便利なのですが、時には区別したいケースもあります。 このとき下の方式2, 3のようなテクニックを用いて未指定とデフォルト値を区別することができます。

message Example {
    // 1. ダイレクトに定義(デフォルト値と未指定を区別できない)
    string foo = 1;

    // 2. 専用のmessageを定義
    message Bar {
        string value = 1;
    }
    Bar bar = 2;

    // 3. oneofで定義
    oneof baz_option {
        string baz = 3;
    }
}

2のmessageをがんばって定義する形式は、パラメータが増えるとどんどんmessageが増えて辛くなってしまいます。 3は少しトリッキーなのがネックです。(また値の有無を switch-caseif (getBazOptionCase == BAZ) のように確認することになるため若干記述量も増します。)

wrappers.proto

と思ったらprimitive値をラップするものが公式で存在しました。

https://github.com/google/protobuf/blob/v3.1.0/src/google/protobuf/wrappers.proto#L31-L34

// Wrappers for primitive (non-message) types. These types are useful // for embedding primitives in the google.protobuf.Any type and for places // where we need to distinguish between the absence of a primitive // typed field and its default value.

とあり、まさに値がない事とデフォルト値を指定されたことを区別することができます。(Anyに値を突っ込むのにも使えるようです。)

これはmessageを都度定義する方式をただ汎用化したものかと思っていたのですが、仕様上で特別扱いされ、 Jsonマッピングした際にラップを無視して単に1.0や"str"のようなプリミティブな値として表現されるようです。 https://github.com/google/protobuf/blob/v3.1.0/src/google/protobuf/wrappers.proto#L50

都度メッセージを定義してしまうと "nyan": { "value": "str" } のような冗長なJsonになってしまうので、これを回避できるのは便利そうです。

仕様にもひっそりと Wrapper types という表現で特別扱いされていました。 protocol buffers - JSON MAPPING

Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer.

実際に動かしてみると以下のようになります。(コードは https://github.com/matsu-chara/proto_wrap にあります)

syntax = "proto3";

package example;

import "google/protobuf/wrappers.proto";

message Example {
    // 1. ダイレクトに定義(デフォルト値と未指定を区別できない)
    string foo = 1;

    // 2. 専用のmessageを定義
    message Bar {
        string value = 1;
    }
    Bar bar = 2;

    // 3. oneofで定義
    oneof baz_option {
        string baz = 3;
    }

    // 4. wrapperで定義
    google.protobuf.StringValue mofu = 4;
}
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.StringValue;
import com.google.protobuf.util.JsonFormat;
import example.Mofu.Example;

public class Main {
    public static void main(String[] args) throws InvalidProtocolBufferException {
        Example ex = Example.newBuilder()
                .setFoo("foo")
                .setBar(Example.Bar.newBuilder().setValue("bar"))
                .setBaz("baz")
                .setMofu(StringValue.newBuilder().setValue("mofu"))
                .build();

        System.out.println("instance.toString");
        System.out.println(ex.toString());
        System.out.println("json");
        System.out.println(JsonFormat.printer().print(ex));
        System.out.printf("");

        Example empty = Example.getDefaultInstance();
        // foo == ""
        System.out.println("foo isEmpty: " + empty.getFoo().isEmpty());

        // bar == null (getBar returns defaultBar if bar == null)
        System.out.println("hasBar: " + empty.hasBar());
        System.out.println("bar isEmpty: " + empty.getBar().getValue().isEmpty());

        // baz ==  BAZOPTION_NOT_SET (getBaz returns "" if BazOptionCase != Baz)
        System.out.println("bazOptionCase: " + empty.getBazOptionCase());
        System.out.println("baz isEmpty: " + empty.getBaz().isEmpty());

        // mofu == null (getMofu returns defaultMofu if mofu == null)
        System.out.println("mofu hasMofu: " + empty.hasMofu());
        System.out.println("mofu isEmpty: " + empty.getMofu().getValue().isEmpty());
    }
}
instance.toString
instance.toString
foo: "foo"
bar {
  value: "bar"
}
baz: "baz"
mofu {
  value: "mofu"
}

json
{
  "foo": "foo",
  "bar": {
    "value": "bar"
  },
  "baz": "baz",
  "mofu": "mofu"
}
foo isEmpty: true
hasBar: false
bar isEmpty: true
bazOptionCase: BAZOPTION_NOT_SET
baz isEmpty: true
mofu hasMofu: false
mofu isEmpty: true

jsonの出力ではStringWrapperにあたる部分が簡略化されていることが分かります。
emptyの取得ではhasMofuやoneofといったテクニックで値が無いことが確認できます。

このようにWrapper typesを使えば未指定とデフォルト値を区別することが簡単にできて、なおかつjson変換の際に冗長さを減らすことができます。 もちろんフィールドが増えたり使いまわされたりするならmessageを定義するのが良いこともあるので、適切に使い分ける必要があるとは思いますが、単純にwrapしたいだけであれば選択肢に入ると思います。

ただし不用意にラップすると(クラスが増えるので)ビルド時間が増えたりするといったデメリットがあったりしますし、そもそものprotobufの設計思想から若干それているような気がしなくもないので第一選択肢としてはデフォルト値を利用する戦略を取るのが望ましい気がします。

redis叩く君をつくった

ponyで何か作りたいなと思ったのでひとまずScalaで作ってそれを移植しようかなーと思い立ち作ってみた。

GitHub - matsu-chara/redica

get/setしか叩け無いけどまあいいよね( ◜◡‾) レスポンスのパースが限定的なので勉強がてらもう少し作って見るかも。

せっかくだからnon-blockingにしたりとかredis-clusterとかまでやってみようかなー。(ponyとは・・)

Ponylang 0.3祝いと最近のPony事情

はろぽに〜₍₍ (ง´・_・`)ว ⁾⁾

Pony 0.3リリース 🎉

2016年8月26日にPony 0.3がリリースされました。 チュートリアルについてのブログを書いたときから大分経ったような気がするので、 最近のPony事情についてちょっとまとめようかなーと思います。

(ついでにチュートリアルについての記事はdeprecated的な文言を先頭に付けました。試してないですが多分色々動かないところがあると思います。( ◜◡‾))

Pony を取り巻く環境の変化

Main君爆誕

今年の5月ごろPonyのマスコットキャラクターであるMain君が生まれました。goで言うところのgopherポジションだと思います。 Introducing Main, the Pony Mascot.

https://pony.groups.io/g/user/attachment/285/0

最初は( ◜◡‾)???となったんですが、しばらく眺めていると謎の良さみが深まります。slackのemojiに登録しましょう。

ちなみに3月末頃に別の案が提案されていました。

どちらがいいとかは置いておいて、ponyのマスコットはMain君です。

ちなみにMain君ステッカーはこちらで購入することができます。もしぬいぐるみが出たら家用と職場用で2つ購入しようと思います。 Main by Sean T Allen (#12903) - Sticker Mule

さよならcausality

Ponyの開発、商用サポートなどを手掛けていたcausality社がcloseすることになったという情報が8月21日に公開されました。

しかしPonyの開発はコミュニティドリブン(+大学の研究などで?)続いていくと宣言されていますし、ponycのコミットを見る限りちゃんと続いているので言語的には問題なさそうです。

資金獲得などの面で色々あったのだと思いますが、やはりちゃんとした技術を売りにしたスタートアップでもうまくいかないことはあるんだなーという気持ちに。

Pony VUG始まる

Ponyのあれこれについて解説するライブ放送の録画がvimeoで見られるようになっています!

Sean Allen on Vimeo

僕のオススメは

  • Ponyに導入される(?)依存型について説明した 「Pony VUG #4: Luke Cheeseman: Simple Value-Dependent Types In Pony」

vimeo.com

コンパイル時にメソッドを呼び出して計算した結果を型パラメータに突っ込むみたいなことも出来て結構強力そうです。すごく複雑なことをしなくても、例えば行列の次元数を型パラメータにしておけば、行列積でかけ間違えてたらコンパイルエラーといった分かりやすいメリットを簡単に得ることが出来そうです。

  • reference capabilityのsubtyping関連を見なおして色々証明したらしい 「Pony VUG #5: George Steed: A Principled Design of Capabilities in Pony」

vimeo.com

の2つです。

まだPonyのGCについての論文 を読んだことがなければ 「Pony VUG #6: Andrew Turley: The Art of Forgetting - Garbage Collection in Pony」もおすすめです。

vimeo.com

という気持ちです。

Pony VUGは言語の入門だけでなく、実装の詳細やこれから入るかもしれない機能について突っ込んで触れられているので必見です。

チュートリアルがgitbookになった

なりました。 http://tutorial.ponylang.org/

以前は付いていたsyntax highlightがつかなくなった気がするんですが・・・という気持ちですがメジャー言語になればつくはずなので問題無いですね( ◜◡‾)

rfcの募集が始まった

すでにいくつかはマージされ、実装されています。

https://github.com/ponylang/ponyc/search?utf8=%E2%9C%93&q=implement+RFC&type=Issues

ちなみに、この方式はRustなどに影響を受けているようです。

Pony has adopted a RFC process

リリースサイクルがついに整う

何百コミットもあったのに一向にリリースされなかったPonyですが、homebrewの人が発端でリリースしようよ => 整えるか! という流れで無事に0.3リリースにたどり着いたようです🎉

今まで0.2.1使って「動かない(´;ω;`) => masterビルドして使ってね」コンボを食らってる人が何人もいたのでこれで少し落ち着きそうです。

ちなみに0.2.1からの差分は 1092 commits 524 changed files with 13,181 additions and 2,797 deletions です。そりゃ動かんわ! https://github.com/ponylang/ponyc/compare/0.2.1...0.3.0

言語で変わったところ

詳しくはチェンジログ参照なんですが、32bit ARM/X86対応が加わったりしました。

またletとほぼおなじだけど、ポインタをもたせるのではなくて直接埋め込むのでポインタデリファレンスの分少しだけ高速な Embedded fieldsという概念が加わりました。速そう(?) pony-tutorial/variables.md at 5295d2c35f21953e9fe6f997b9100273a6f72b42 · ponylang/pony-tutorial · GitHub

serializationの実装がmasterに入ったりしてdistributed Ponyへの道が近づいたようです。

https://pony.groups.io/g/user/topic/first_version_of_pony/1680751?p=,,,20,0,0,0:RecentPostDate%2FSticky,,,20,2,0,1680751

他にもコレクションのメソッドが増えたり、速くなったり、ネットワーク周りが改良されたり色々色々あるのですが、全部は把握できていないので誰か教えて下さい。 🙏

ちなみに全くどうでもよいですが、記念すべき1000個めのイシューはスケジューラのセグメンテーションフォールトでした。

Segfault in Pony scheduler · Issue #1000 · ponylang/ponyc · GitHub

今後のPony?

特に自分が未来を知っているわけではないですがErlangっぽいprocess monitoringの仕組みをいれる取り組みが進行中?( Erlang-style monitoring · Issue #350 · ponylang/ponyc · GitHub )とありますし、serializeとかが入ったので、distributed Ponyが粛々と進んでいる気配があります。 かと思えば依存型が入る?など謎の高度な仕組みが入ってきそうで最終的にどうなるのかよくわからないですが今後も注目の言語だと言えそうです。 ということで

何卒🙏