OOPのメッセージ送信をTCP上で
Rubyはオブジェクト指向言語なので、オブジェクトのメソッド呼び出しは「オブジェクトに対してメッセージを送る」と考えることもできるそうな(アラン・ケイが発祥?)。
であれば実際にオブジェクトに対してメッセージをTCPとかで送受信したくなるのが人情というものです。標準添付ライブラリであるdRubyを使えばあっさりできるようですが、なるべく自然な書き方で実現できると楽しいだろうということで挑戦してみました。
require 'drb/drb' def rclass(klass, &blk) cls = nil self.class.module_eval <<-EOS class #{klass} end cls = #{klass} EOS cls.module_eval(&blk) cls.module_eval do def self.new(*args) q = Queue.new Thread.new do obj = self.allocate obj.__send__(:initialize, *args) DRb.start_service('druby://localhost:0', obj) q << DRb.uri sleep end uri = q.shift DRb.primary_server = nil robj = DRbObject.new_with_uri(uri) robj end end end
上記のようなクラス定義メソッド("class"に似せたつもり)を用意して…
require 'rclass' rclass :Hoge do @@counter = 0 def initialize(name, age) @name = name @age = age @@counter += 1 end def hello(something) puts "[#{@name}(#{@age})] hello #{something} (#{@@counter})" end end foo = Hoge.new('foo', 55) foo.hello('foofoo') bar = Hoge.new('bar', 66) bar.hello('barbar')
件のメソッドrclassでHogeクラスを定義して、オブジェクト作成・メソッド呼び出しを行うと…
komamitsu@potato:~/git/message_passing$ ruby test.rb [foo(55)] hello foofoo (1) [bar(66)] hello barbar (2)
と、実行結果はまともなのですが、実はHogeクラスのオブジェクトのメソッド呼び出しはTCP経由でメッセージ送受信をしているという…
23:12:28.964864 IP localhost.46662 > localhost.51613: S 1625095976:1625095976(0) win 32792 <mss 16396,sackOK,timestamp 5164511 0,nop,wscale 6> 23:12:28.964907 IP localhost.51613 > localhost.46662: S 1625225372:1625225372(0) ack 1625095977 win 32768 <mss 16396,sackOK,timestamp 5164511 5164511,nop,wscale 6> 23:12:28.964940 IP localhost.46662 > localhost.51613: . ack 1 win 513 <nop,nop,timestamp 5164511 5164511> 23:12:28.967773 IP localhost.46662 > localhost.51613: P 1:50(49) ack 1 win 513 <nop,nop,timestamp 5164512 5164511> 23:12:28.967860 IP localhost.51613 > localhost.46662: . ack 50 win 512 <nop,nop,timestamp 5164512 5164512> 23:12:28.971431 IP localhost.51613 > localhost.46662: P 1:15(14) ack 50 win 512 <nop,nop,timestamp 5164513 5164512> 23:12:28.971466 IP localhost.46662 > localhost.51613: . ack 15 win 513 <nop,nop,timestamp 5164513 5164513> 23:12:28.977087 IP localhost.48225 > localhost.49348: S 1617232670:1617232670(0) win 32792 <mss 16396,sackOK,timestamp 5164514 0,nop,wscale 6> 23:12:28.977131 IP localhost.49348 > localhost.48225: S 1614737937:1614737937(0) ack 1617232671 win 32768 <mss 16396,sackOK,timestamp 5164514 5164514,nop,wscale 6> 23:12:28.977166 IP localhost.48225 > localhost.49348: . ack 1 win 513 <nop,nop,timestamp 5164514 5164514> 23:12:28.981728 IP localhost.48225 > localhost.49348: P 1:50(49) ack 1 win 513 <nop,nop,timestamp 5164515 5164514> 23:12:28.981772 IP localhost.49348 > localhost.48225: . ack 50 win 512 <nop,nop,timestamp 5164515 5164515> 23:12:28.984434 IP localhost.49348 > localhost.48225: P 1:15(14) ack 50 win 512 <nop,nop,timestamp 5164516 5164515> 23:12:28.984471 IP localhost.48225 > localhost.49348: . ack 15 win 513 <nop,nop,timestamp 5164516 5164516> 23:12:28.986242 IP localhost.49348 > localhost.48225: F 15:15(0) ack 50 win 512 <nop,nop,timestamp 5164516 5164516> 23:12:28.988235 IP localhost.51613 > localhost.46662: F 15:15(0) ack 50 win 512 <nop,nop,timestamp 5164517 5164513> 23:12:28.989741 IP localhost.48225 > localhost.49348: F 50:50(0) ack 16 win 513 <nop,nop,timestamp 5164517 5164516> 23:12:28.989807 IP localhost.49348 > localhost.48225: . ack 51 win 512 <nop,nop,timestamp 5164517 5164517> 23:12:28.990879 IP localhost.46662 > localhost.51613: F 50:50(0) ack 16 win 513 <nop,nop,timestamp 5164518 5164517> 23:12:28.990945 IP localhost.51613 > localhost.46662: . ack 51 win 512 <nop,nop,timestamp 5164518 5164518>
ちょっとハマった点は、別スレッド内でDRb.start_serviceしても、同じところで動作していると判断されてしまいTCPでの通信が行われなかった(__send__によるメソッド呼び出しになっちゃう)こと。dRubyのコードを眺めつつ気がついた苦肉の策として「DRb.primary_server = nil」でとりあえず回避しています。それに伴う影響はちゃんと考えていません。
今回はちょっと手抜きで別スレッドでdRubyサーバーを立ててましたが、頑張れば別プロセスや別ホスト上で動かすこともできそうなので分散処理にもつながるかも、とそれらしいことを書いてみたり。手抜きついでで言うとGCのタイミングでスレッドを止めないといけないですね。
それにしても、今回初めてdRubyを使ってみたのですが使いやすくて素晴らしいっす。