ErlangとRubyの連携(Erlang -> Ruby編)

RubyErlangで連携処理する方法を調べてみました。

ErlangからRubyを呼ぶ
まずはRubyで実装したライブラリへErlangからメッセージを投げて結果を受け取る場合です。

この方法では呼び出されるRuby側のプロセスはおそらくOSのプロセスになるでしょう。従ってErlangの最大の特徴とも言える軽量プロセスが活かせないのですが、例えばRubyで実装された過去の資産を活かしたくて、おなかつ軽量さがあまり要求されない場合などは有用かもしれません。

そうではなくspawnされた軽量プロセス内で大量にRubyにコールするような使い方は多分控えたほうが良さそうです。下手したら実行ユーザに許された数のOSのプロセスを食いつぶすかもしれませんし。

この方法はオライリーの「Erlangプログラミング」にのってました(ただしRuby側ライブラリのerlectricityの仕様が変わったらしく書いてある通りでは動きませんでした)。

最初にはRuby側でErlangとやり取りする為のライブラリ、"erlectricity"をインストールしておく必要があります。

$ sudo gem install erlectricity

rubyのソースは以下のように。基本的には「Erlangプログラミング」に掲載されていたものを少し修正したものです。

require "rubygems"
require "erlectricity"

in_io = IO.new(0)
out_io = IO.new(1)

receive(in_io, out_io) do |f|
  f.when([:hello, String]) do |text|
    f.send!([:hello, "hello #{text}"])
    f.receive_loop
  end

  f.when([:goodbye, String]) do |text|
    f.send!([:goodbye, "bye #{text}"])
    f.receive_loop
  end
end

つづいて、このライブラリを使用するErlang側のソースコード

-module(erlang_client).
-export([hello/1, goodbye/1]).

hello(Name) -> greeting(hello, Name).

goodbye(Name) -> greeting(goodbye, Name).

greeting(Command, Name) ->
    Cmd = "ruby ruby_server.rb",
    Port = open_port({spawn, Cmd}, 
		     [{packet, 4}, use_stdio, exit_status, binary]),
    Payload = term_to_binary({Command, list_to_binary(Name)}),
    port_command(Port, Payload),
    receive
	{Port, {data, Data}} ->
	    {Command, Bin} = binary_to_term(Data),
	    io:format("~p~n", [binary_to_list(Bin)])
    end.

上記のソースを同じディレクトリに置いておいて、Erlangノードを起動。
動作を確認します。

$ erl
c(erlang_client).

erlang_client:hello("hiroe").
"hello hiroe"

erlang_client:goodbye("hiroe").
"bye hiroe"

どうやら動いたようです。もっともその動作スピードはお世辞にも早いとは言えません。実行時にOSプロセスをforkしてその中でrubyを立ち上げてるようなので当然と言えば当然ですが。

個人的には今回とは逆の、Ruby側からErlangを呼び出すほうが需要があるような気もしてます。
例えば、何万人のユーザを抱えるバックグラウンドの処理をErlangで実装しておいて、フロントのWebインターフェースをRailsで組むような場合ですね。次はそのような場合について調べてみたいと思います。