Erlang + Lua = erluna

第四回 Erlang 分散処理勉強会から早や3ヶ月…、学ぼう・試そうと思っていた事が思うようにできない日々を悶々と過ごしておりましたが、何とか時間を作って Erlang linkd-in driver を試すために Lua を組み込んでみました。

何が出来るのか?

とても行数は少ないのですが、こんなんでも Erlang から Lua を呼べます。
設定ファイルを Lua で記述するような簡単な使い方は勿論、ちょっとした拡張を Lua で行うためにも利用できます。
例えば、Kai の Write/Write Conflict をサーバサイドで解決する機能を提供する計画があるのですが、幾つかの解決用アルゴリズムLua で記述してプラグイン化しておく事で、Erlang を知らない利用者の方であっても比較的学習コストの低い Luaプラグインを記述する事ができます。

使い方

基本的な使い方は付属の README をご確認ください。

README には載せてませんが、Lua 側で時間の掛かる処理を行う場合は、Erlang VM 起動時の Async Threads の数にご注意下さい。
同時に実行する時間の掛かる処理の数だけ +A オプションに指定する値を増やす事で、Erlang VM が処理待ちで一時停止する事を避けられます。

例えば、下記のような関数が sleep.lua という名前のファイルにあるとします。

function say_hello_after_sleep ()
  os.execute("sleep 5")
  print "hello"
end

+A オプションを指定せずに erl コマンドを実行すると、Async Threads が 0 で Erlang VM が起動しますが…

% erl -pa /path/to/erluna/ebin
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> 

この状態で前述の Lua 関数 say_hello_after_sleep を評価すると、評価が終わるまで Erlang 全体が停止します。

1> {ok, Lua} = erluna:start().
{ok,{erluna,#Port<0.648>}}
2> Lua:eval_file("/path/to/sleep.lua").
ok
3> Lua:apply("say_hello_after_sleep", {}).
hello
     {ok,[]}
4>

プロンプト 4> を表示するまでに 5 秒間の待ち時間があります。

erluna は、処理の実行を Async Threads へ押し付けるので、+A オプションを指定して erl コマンドを実行すると上記の問題は発生しません。

% erl +A 1 -pa /path/to/erluna/ebin
Erlang R13B04 (erts-5.7.5) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:1] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
1> 

下記は、上記と同じくプロンプト 4> が表示されまで 5 秒待たされますが、それは apply/3 の内部で receive で結果を受信しているためです。
上記と異なり、他の Erlang Process の処理は中断されません。*1

1> {ok, Lua} = erluna:start().
{ok,{erluna,#Port<0.649>}}
2> Lua:eval_file("/path/to/sleep.lua").
ok
3> Lua:apply("say_hello_after_sleep", {}).
hello
     {ok,[]}
4>

試しに、apply/3 内部で利用している async_apply/3 と receive_data/1 を直接評価すると、async_apply/3 が即座に true を返しているのが解ると思います。
空いている Async Threads が存在しない場合、Main Thread で実行してしまうため async_apply/3 は即座に true を返しません。

1> {ok, Lua} = erluna:start().
{ok,{erluna,#Port<0.649>}}
2> Lua:eval_file("/path/to/sleep.lua").
ok
3> Lua:async_apply("say_hello_after_sleep", {}).
true
4> hello
4> Lua:receive_data().
{ok,[]}
5> 

一つ目のプロンプト 4> の hello は say_hello_after_sleep の出力です。

ちなみに、特に排他制御を行っていないため、複数の Async Threads から一つの Lua スタックを操作すると意図しない動作をするハズです。
start/1 を評価する度に Lua スタックを一つ生成するので、同時に複数の処理を Lua で行いたい場合は、処理したい個数分 start/1 を評価して下さい。
当然、変数の状態などは、Lua スタック毎に異なるので Erlang 側で、その辺りを制御する必要があります。
とは言え、Lua で同時に複数の処理を行いたいのであれば Lua のコルーチンが使えないかを始めに検討するべきです。

他の似たソリューションの紹介

erlua
Lua を組み込んだ erlua というコマンドが CNode として起動し、Erlang 側と通信する仕組みです。

erlmon
監視で有名(?)な erlmon は、linkd-in driver で Lua を組み込んで利用しています。
潔く Lua の C API が丸出しであるため、Lua C API の知識がないと利用できません。

今後について

apply/3 の第二引数がタプルで気持ち悪いので、どうにかしたい所です。
他は特に何も考えていません。ご意見募集中です。
他に懸念点として、実は erluna.so の位置指定が適当です。code:priv_dir/1 を利用できれば良いのですが…。
yatch と同様に erluna も、某V先生の Reltool 解説待ちです!w

*1:厳密には apply/3 内部の async_apply/3 が true を戻すまで止まっている気がしますけど、即座に true を戻すので気にしない