« 2章.Hello, dRuby (その2) | メイン | 2章.Hello, dRuby (その4) »

2章.Hello, dRuby (その3)

「2章.Hello, dRuby の3回目(その3)」ですが、前回のエントリでは見れなかったDRbObjectのmethod_missingの中が気になるので、今回は本を読み進めず、こちらの中の概要だけでも見てみることにしました。以下がDRbObjectのmethod_missingの全てです。

def method_missing(msg_id, *a, &b)
  if DRb.here?(@uri)
obj = DRb.to_obj(@ref)
DRb.current_server.check_insecure_method(obj, msg_id)
return obj.__send__(msg_id, *a, &b) 
  end
  succ, result = self.class.with_friend(@uri) do
    DRbConn.open(@uri) do |conn|
      conn.send_message(self, msg_id, a, b)
    end
  end
  if succ
    return result
  elsif DRbUnknown === result
    raise result
  else
    bt = self.class.prepare_backtrace(@uri, result)
result.set_backtrace(bt + caller)
    raise result
  end
end

すごくちょっとしかありません。これくらいならみれるかなあ。じゃあまず最初の分岐。

if DRb.here?(@uri)

これは、スクリプトを実行している自分がオブジェクトを公開しているサーバー自身かどうかを訊ねています。自分がオブジェクトを公開しているサーバーだったら、リモート呼び出しの仕組みを使わずにそのまま呼んじゃえという事だと思われます。対して、リモート呼び出しのときに実行されるのが以下。

succ, result = self.class.with_friend(@uri) do
  DRbConn.open(@uri) do |conn|
    conn.send_message(self, msg_id, a, b)
  end
end

かなり短いですが、結構ムズい。渡されるブロックが入れ子になって2つあります。多分yieldの2段呼びだと想像しつつ、この5行をもうしばらく眺める。ちなみにyieldとはメソッドにブロックを渡すとそれを任意のタイミングでメソッドの中から呼べるというものみたいです。こんなやつです。

def add 
  p '足します'
  yield(1,2) 
  p '足しました'
end
add do |a,b|
  p a + b
end

上のスクリプトを実行すると、下のように出力されます。

足します
3
足しました

 大抵の場合、yieldは上の例のようなヘボい使い方はせず、イテレータで回す時に使われるようです。yieldの説明はこれくらいにして、さっきの5行ですが、よく見ると単純で、多分サーバーへの接続を確保し、そこへメソッド呼び出しのメッセージを送るという処理なのだなと言う想像くらいは容易につきます。これをわざわざyieldでブロックを渡してやろうとしているということは、複数の接続先が配列などで管理されていて、そこから今回の送り先を選び出して送るか、または、送り先全てに対して順次送ったりするんだなと推測出来そう。っと、そういう前提を抱きつつ中を見てみることにします。

def self.with_friend(uri)
  friend = DRb.fetch_server(uri)
  return yield() unless friend
・・・

 。。。。はい、全然よくわかりません。。friendって何かしら。お友達関係にあるサーバーと連携できるのかな。とりあえずHello, World.レベルの単純なクライアントからの場合はfriendはnilっぽいですね。とりあえず今回はfriendはnilと読みかえることにします。だからこのreturn yieldも実行されて、中のDRbConn.open(@uri) do ・・・ブロックが動きます。

 DRbConnとは、DRbObjectの中で使われる単純なクラスで、具体的にはサーバーとの接続をプールしているみたいです。毎回繋げに行くのはコストが掛かるから、一度つなげた接続は使いまわして、メソッド呼び出しに掛かるコストを減らしてくれるんですね。で、えっと、DRbConn.openなので、これはクラスメソッドか。Rubyでは“self.メソッド名”と定義すると、クラスメソッドになるようです。なので、self.openメソッドを見てみます。

def self.open(remote_uri)  # :nodoc:
  begin
conn = nil
・・・
conn = self.new(remote_uri) unless conn
succ, result = yield(conn)
return succ, result
・・・

 実際のコードはpoolにいれたりpoolに照会したり、poolに対するアクセスをアトミックにしたりともっと長いけど、肝心なところだけ抜粋したつもりです。poolに何も入っていない最初のアクセスの場合は、connもnilのままなので、ここでDRbConnのインスタンスを作って、それ自身を渡してyieldですね。で、そうすると2段ブロックの中の方が動いてその結果を返すと。ふむふむ。ということで次はDRbConnのsend_messageです。

def send_message(ref, msg_id, arg, block)  # :nodoc:
  @protocol.send_request(ref, msg_id, arg, block)
  @protocol.recv_reply
end

ここでのrefはDRbObjectですね。msg_id, arg, blockは、method_missingに渡された引数そのままです。@protocolはDRbTCPSocketだということは以前のエントリで既に見たので、DRbTCPSocketのsend_requestを見てみます。

def send_request(ref, msg_id, arg, b)
  @msg.send_request(stream, ref, msg_id, arg, b)
end

ちなみに@msgは何物かというと

@msg = DRbMessage.new(config)

 と初期化されていました。他プロセスのオブジェクトに対して実際にメッセージを送るのはDRbMessageがやっているようです。で応答をrecv_replyで受け取ってsuccとresultに詰めるという感じです。succが成功or失敗の真偽値、resultがメソッドの戻り値です。ちなみにDRbMessageの中ではMarshalを使ってdumpしているようです。詳しくはまた今度見ることにします。

 終わった。送信部分の流れは大体わかりました。もしかして後はDRbServerのmain_loopの中さえ見りゃ、dRubyの基本的な内部構造にはさらっと目を通したことになる。か?

 ところで、yield2段を目で追うのがつらかったので、今回からデバッガを使い始めました。しかし、そこで少しハマりました。デバッガとかで見ていると、デバッガが変数のインスペクション情報を取得するために勝手にto_sとかを呼び、私自身が試そうとしているスクリプトよりも先にリモート呼び出しをしているので、既にDRbConnにプールされていました。くそー、まだ一度もリモートで呼び出ししていないからインスタンス出来てないはずなのにどこで作ってるんだよ、わからん、って思って悩んだというわけです。それは置いといて、デバッガもリモートオブジェクトだと言う意識がないということだなあ。デバッガも騙されるdRubyって。。ちょっとびっくりです。

 次から本に戻ります。どこを読んでたかなあ。あ、まだHello, World.か。

トラックバック

このエントリーのトラックバックURL:
http://www.grandnature.net/bin/mt-tb.cgi/54

コメントを投稿

About

2007年09月07日 12:55に投稿されたエントリーのページです。

ひとつ前の投稿は「2章.Hello, dRuby (その2)」です。

次の投稿は「2章.Hello, dRuby (その4)」です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。