mmag

ハマったことメモなど

Elixirのsupervisor, worker, superviseヘルパーがイケメンだった

最近Elixirのことしか書いてないです。たぶんもうしばらくこんな感じです。

ErlangでOTP使うときとか、スーパーバイズしたい子供をつくるときに、child_specと呼ばれる書き方でどんな子供をつくるか指定します。公式のドキュメントにはこんな風に書いてあります。

child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
  Id = term()
  StartFunc = {M,F,A}
    M = F = atom()
    A = [term()]
  Restart = permanent | transient | temporary
  Shutdown = brutal_kill | int()>0 | infinity
  Type = worker | supervisor
  Modules = [Module] | dynamic
    Module = atom()

それぞれのパラメータの意味は各自お調べいただくとして、使われ方はこんな感じ。

start_pool(Name, Limit, MFA) ->
  ChildSpec = {
    Name,
    {ppool_sup, start_link, [Name, Limit, MFA]},
    permanent,
    10500,
    supervisor,
    [ppool_sup]
  },
  supervisor:start_child(ppool, ChildSpec).

アトムが多いので割りと読むときは何とかなるんですけど、問題は書くときなんですよ。ええ。何番目に何とか覚えてられないわけですよ。ゆとりですから。

で、ここでElixirが登場するわけです。上の例と同じようなものを書くとこんな雰囲気。

def start_pool(name, limit, mfa) do
  child_spec = supervisor(
    PpoolSup,
    [name, limit, mfa],
    function: :start_link,
    restart:  :permanent,
    shutdown: 10500
  )
  Supervisor.start_child(Ppool, child_spec)
end

最初にこっちを読んで、「OTPって何ぞ?パラメータの意味わかんねー」って言ってErlangに行き、「パラメータの書き方なにこれキモッ」って言ってから戻ってきたら実はElixir超絶わかりやすかったってやつでした。意味わかんねーとか言ってごめんなさい。言わずもがなですが、key-value形式になってるのでパラメータの意味が一目瞭然なのですね。セルフドキュメントですよ。しかも後半3行(function:からshutdown:まで)はどんな順番で書いてもOKなので書くときのストレスも少ないのです。

もっと言うと、実はデフォルトのパラメータが当てられていたりするのです。上の例、よく見ると与えてるパラメータ減ってるんですよね。最後の[ppool_sup]とか。そのデフォルトは以下。

[id:       module,
 function: :start_link,
 restart:  :permanent,
 shutdown: :infinity,  # worker関数の場合は5000
 modules:  [module]]

moduleにはsupervisor関数の第一引数に与えたモジュール名がそのままスコーンと入ります。これらデフォルト値を考慮すると、function: :start_linkrestart: :permanentは書かなくても動作は変化しません。function: :start_linkは慣習になっているようなので省略していいでしょう。restart: :permanentについては、明示しておいた方が何かと良いと思います。削るメリットはあんまり無いです。

そしてもうひとつ、コールバック関数であるinit/1で役立つのがsupervise/2です。init/1は、{:ok, supervisor_spec_tuple}という形のタプルを返すように作らなければならないのですが、これがまたハマりそうで。ええ、ゆとりですから。

{ok, {
  {one_for_all, 10, 60},
  children
}}.

childrenは前記のchild_specのリストです。ね。10と60の辺り、ハマりそうな臭いがします。これは、「エラー等で落ちた子供を60秒間に10回再起動させて、それでもダメだったら諦めろ」という意味なのですが、これがElixirだと、

supervise(
  children,
  strategy:     :one_for_all,
  max_restarts: 10,
  max_seconds:  60
)

こう書けると。わかりやすくなってますね。もちろん後半3つは順不同。strategy以外はデフォルト値が用意されてて、

max_restarts: 3,  # v1.0.0-rc1以前は5だった
max_seconds:  5

です。いい感じにErlangの覚えづらいところを隠してくれてますね。

しかしホントElixirばっかり触っててRuby(特にRails)書けなくなってきちゃいました。就職してから大丈夫なんですかね、ぼく。