mrubyにおいて、mrb_stateを共有しているRubyコード間で、userdataを自由に読み書きできるmruby-userdataというmrbgemを作りました。
これは、mrubyの状態遷移情報を格納しているmrb_stateを共有している限り、あるタイミングでコードAが呼ばれて、次に別のタイミングでコードBが呼ばれた場合、コードAでuserdataオブジェクトを保存しておけば、コードBからそのオブジェクトを取り出せる仕組みです。
グローバル変数を使えばコード間でやり取りができますが、なんとなくグローバル変数を使うのは実装上バグを生みそうなので、C側にuserdataを保存しておくハッシュテーブルオブジェクトを作っておいて、そこにRuby側から保存したい時に保存しておき、取り出したい時に取り出すためのRubyメソッドによるインターフェイスを実装しました。
比較的安全にuserdataを保存しておけると思います。
userdataの操作例
例えば以下のようなコードAがあるとします。
[program lang=’ruby’ escaped=’true’]
u = Userdata.new u.hoge = {:test => 1}
[/program]
このように、userdataにhogeというオブジェクト名で何かハッシュを保存しておきます。
そして、同じmrb_stateで実行される他のコードB上から、以下のようなコードを実行すると、
[program lang=’ruby’ escaped=’true’]
u = Userdata.new hash = u.hoge hash[:foo] = 2 u.hoge = hash p u.hoge # => {:test => 1, :foo =>2}
[/program]
このように、別のコードで保存しておいたuserdataオブジェクトを取り出して、さらに上書きすることができます。
userdataキーを使う場合
さらに、Userdataインスタンス生成時に、引数にキーを渡す事で、userdataキーによって保存しておきたいuserdataを複数作る事ができます。
例えば、以下のようなmirb結果になります。
[program lang=’ruby’ escaped=’true’]
mirb - Embeddable Interactive Ruby Shell This is a very early version, please test and report errors. Thanks :) > u = Userdata.new => #<Userdata:0x7f96ca808ad0 mrb_userdata_key_store="mrb_userdata_default_key"> > u.hoge => nil > u.hoge = 1 => 1 > u.hoge => 1 > q = Userdata.new => #<Userdata:0x7f96ca808590 mrb_userdata_key_store="mrb_userdata_default_key"> > q.hoge => 1 > q.hoge = q.hoge + 1 => 2 > u.hoge => 2 > s = Userdata.new "my_key" => #<Userdata:0x7f96ca807d80 mrb_userdata_key_store="my_key"> > s.hoge => nil > s.hoge = 1 => 1 > u.hoge => 2 > q.hoge => 2
[/program]
何もuserdataキーの指定がない場合は、デフォルトのuserdataキー名を使用するので、uインスタンスもqインスタンスも同じuserdataを参照できています。
一方で、”my_key”という独自のuserdataキーを指定したsインスタンスは、uやqインスタンスとは違うuserdataを扱えている事がわかります。
最後に
このように、mrb_stateを共有している場合のメリットとして、userdataの受け渡しが可能なmrbgemを作りました。これによって、mod_mrubyやngx_mrubyにおいて、初期化時にredisに接続しておいて、そのredisコンテキストオブジェクトをuserdataに保存しておけば、次のフックのタイミングで、redisに再接続することなくredisコンテキストオブジェクトを取り出して、redisの操作をすることが可能になるでしょう。
これで色々捗りそうです。