ブロック式ログインを非ブロック式ログインに変換する
↓みたいな、ブロックで処理を受け取って、ログインして実行するAPIがあって、
# ログインして何かする def do_as( user ) begin # ログインして.. puts "login as #{user}" # ブロックの処理を実行 # 引数としてセッションIDを渡す yield "sid-#{user}" ensure # ログアウト puts "logout" end end
これは、
do_as( "unageanu" ) {|sid| puts "run:#{sid}" }
こんな感じで呼び出せて、まぁ、特に問題なく使えるわけだけど、諸般の事情によりこれを
session = nil begin session = new Session session.login( "unageanu" ) # ログイン session.request {|sid| # 処理 puts "run:#{sid}" } ensure session.logout if session # ログアウト end
みたいな感じで呼び出せるようにしたい。begin,ensureとか書くがいかにも面倒だけど、そこは大人の事情があるのです。
ということで
次のようなセッションクラスを書いてみた。戦略は以下
- セッション作成時にリクエスト実行スレッドを開始し、ログインまで進めてwaitさせておく。
- セッションにリクエストが送付されたら、キューを使ってスレッドに処理を送り、実行させる。
- リクエスト送付側のスレッドは、リクエストが完了するまでwait
- リクエストを実行するスレッドは、リクエスト実行完了後、結果を渡し送付側スレッドを起こす
- セッションが不要になったら、closeで破棄する。(このとき、ログアウトが実行される。)
# セッション class Session def initialize @alive = true @alive_mutex = Mutex.new @q = Queue.new @t = Thread.fork { yield proc {|*args| while( @alive_mutex.synchronize { @alive } ) req = @q.pop req.call( *args ) if req end } } end # リクエストを送る def request( &block ) return unless block_given? req = Request.new(block) @q.push req req.wait end # セッションを破棄する def close @alive_mutex.synchronize { @alive = false } @q.push nil @t.join end # リクエスト class Request def initialize( block ) @mutex = Mutex.new @cv = ConditionVariable.new @finished = false @value = nil @proc = proc {|*args| begin @value = block.call(*args) ensure @mutex.synchronize{ @finished = true @cv.signal } end } end # リクエストの完了を待ち、結果を返す。 def wait @mutex.synchronize{ @cv.wait( @mutex ) until @finished } @value end # リクエストを実行する。 def call(*args) @proc.call(*args) end end end
こんな感じで使います。
# セッションを開始 session = nil begin session = Session.new {|wait| # ログインを実行して do_as( "unageanu" ) { |sid| wait.call( sid ) # ここで待つ。 } } # リクエストを発行。 # リクエスト(=ブロックで指定した処理)が完了するまで、スレッドは待ち状態になり、 # ブロックの実行結果が戻り値として返される。 puts session.request {|sid| "aaa:#{sid}" } puts session.request {|sid| "bbb:#{sid}" } puts session.request {|sid| "ccc:#{sid}" } ensure # いらなくなったら破棄すること。 session.close if session end
実行結果です。
login as unageanu aaa:sid-unageanu bbb:sid-unageanu ccc:sid-unageanu logout