reliable-msg を Ruby 1.9 系でも動くようにする

やりたいこと

タイトルの通り
reliable-msg を Ruby 1.9 系でも動くようにしたい

git でソースを作者様リポジトリから取ってくる

[hamajyotan@host ~]$ git clone https://github.com/assaf/reliable-msg.git
Cloning into reliable-msg...
remote: Counting objects: 129, done.
remote: Compressing objects: 100% (63/63), done.
remote: Total 129 (delta 67), reused 121 (delta 63)
Receiving objects: 100% (129/129), 45.14 KiB, done.
Resolving deltas: 100% (67/67), done.
[hamajyotan@host ~]$ 

とりあえず Ruby 1.9 環境で rake テスト

[hamajyotan@host ~]$ cd reliable-msg
[hamajyotan@host reliable-msg]$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]
[hamajyotan@host reliable-msg]$ rake
(in /home/sakaguchi/reliable-msg)
/usr/local/bin/ruby -w -I"lib" "/usr/local/lib/ruby/1.9.1/rake/rake_test_loader.rb" "test/test-rails.rb" "test/test-queue.rb" "test/test-topic.rb"
/home/sakaguchi/reliable-msg/lib/reliable-msg/queue.rb: /home/sakaguchi/reliable-msg/lib/reliable-msg/queue.rb:332: Invalid retry (SyntaxError)
rake aborted!
Command failed with status (1): [/usr/local/bin/ruby -w -I"lib" "/usr/local...]
/usr/local/lib/ruby/1.9.1/rake.rb:993:in `block in sh'
/usr/local/lib/ruby/1.9.1/rake.rb:1008:in `call'
/usr/local/lib/ruby/1.9.1/rake.rb:1008:in `sh'
#
# 途中省略。。
#
/usr/local/lib/ruby/1.9.1/rake.rb:2013:in `top_level'
/usr/local/lib/ruby/1.9.1/rake.rb:1992:in `run'
/usr/local/bin/rake:31:in `<main>'
[hamajyotan@host reliable-msg]$ 

構文エラーのようだ。

色々改修していく

lib/reliable-msg/queue.rb 332行目

retry が使われている

    def next selector, &block
      load = false
      @mutex.synchronize do
        load ||= (@list.nil? || @list.empty?)
        @list = block.call() if load
        @list.each_with_index do |headers, idx|
          if selector.match headers
            @list.delete_at idx
            return headers[:id]
          end
        end
        unless load
          load = true
          retry
        end
      end
      return nil
    end

これは Ruby 1.9 ではダメ、代わりに redo を使いたいところ
#next メソッドが実行されている箇所を確認する
lib/reliable-msg/queue.rb 249行目 付近

        # Validate the selector: nil, string or hash.
        selector = case selector
          when String
            {:id=>selector}
          when Hash, Selector, nil
            selector
          else
            raise ArgumentError, ERROR_INVALID_SELECTOR
        end
        # If using selector object, obtain a list of all message headers
        # for the queue (shared by all Queue/Selector objects accessing
        # the same queue) and run the selector on that list. Pick one
        # message and switch to an :id selector to retrieve it.
        if selector.is_a?(Selector)
          cached = @@headers_cache[@queue] ||= CachedHeaders.new
          id = cached.next(selector) do
            if tx
              tx[:qm].list :queue=>@queue, :tid=>tx[:tid]
            else
              repeated { |qm| qm.list :queue=>@queue }
            end
          end
          return nil unless id
          selector = {:id=>id}
        end

#next メソッドを呼んでいるレシーバの cached 更に、引数の selector
これらは共に変数なので再評価されても副作用はない。
よって、 retry をそのまま redo に変更しても問題なさそう。
参考 ⇒ http://d.hatena.ne.jp/hamajyotan/20110410/1302409054

lib/reliable-msg/client.rb 92行目と100行目
      Selector.new &block

これは

      Selector.new(&block)

こうする。

lib/reliable-msg/selector.rb 70行目

メタプログラミング Ruby 曰く、ブランクスレートしてるコード

    instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|instance_eval$/ }

object_id を undef しちゃダメとの警告、故に例外に加える

    instance_methods.each { |name| undef_method name unless name =~ /^(__.*__)|instance_eval|object_id$/ }
test/test-queue.rb 123, 131, 146, 160 行目
    begin
      @queue.get do |msg|
        assert msg && msg.id == id1, "Block called without the message"
        raise AbortTransaction
      end
        flunk "Message not found in queue, or exception not propagated"
      rescue AbortTransaction
    end

ブロック外の変数 msg がブロック引数 msg とかぶっている

    begin
      @queue.get do |m|
        assert m && m.id == id1, "Block called without the message"
        raise AbortTransaction
      end
        flunk "Message not found in queue, or exception not propagated"
      rescue AbortTransaction
    end
lib/reliable-msg/cli.rb 197行目
        else
          raise InvalidUsage
      end
      rescue InvalidUsage
        puts USAGE
      end
    end

インデントがズレているゆえの警告

        else
          raise InvalidUsage
        end
      rescue InvalidUsage
        puts USAGE
      end
    end
test/test-queue.rb 29行目
require 'lib/reliable-msg'

require できないので修正、他のテストスクリプトと合わせる。

require 'reliable-msg'
lib/queue-manager.rb 187, 191, 194行目
        return if @@active == self
        Thread.critical = true
        if @@active.nil?
          @@active = self
        else
          Thread.critical = false
          raise RuntimeError, ERROR_QM_STARTED
        end
        Thread.critical = false

Thread.critical は無くなった

        return if @@active == self
        # Thread.critical = true
        if @@active.nil?
          @@active = self
        else
          # Thread.critical = false
          raise RuntimeError, ERROR_QM_STARTED
        end
        # Thread.critical = false
lib/reliable-msg/queue.rb 70行目あたり
    def initialize(queue = nil, options = nil)
      @priority, @expires, @max_deliveries, @delivery = nil # 追加

初期化されないインスタンス変数の参照警告に対する対応
端折るが、同警告の対応をちらほらと。

ハマりポイント

ブロック引数とブロック外引数が同名である場合の挙動

Ruby 1.8

irb(main):001:0> RUBY_VERSION
=> "1.8.6"
irb(main):002:0> x = 5
=> 5
irb(main):003:0> [1, 2, 3].each { |x|
irb(main):004:1*   puts x
irb(main):005:1> }
1
2
3
=> [1, 2, 3]
irb(main):006:0> x
=> 3

Ruby 1.9

irb(main):001:0> RUBY_VERSION
=> "1.9.2"
irb(main):002:0> x = 5
=> 5
irb(main):003:0> [1, 2, 3].each { |x|
irb(main):004:1*   puts x
irb(main):005:1> }
1
2
3
=> [1, 2, 3]
irb(main):006:0> x
=> 5

Ruby 1.8 系では、外側の引数へ影響を与え、 Ruby 1.9 系では、外側の引数はシャドーされる。
ReliableMsg で1箇所、 Ruby 1.8 系の挙動を期待するコードがあった。

改修後、 Ruby 1.9 系でテスト実行

[hamajyotan@host reliable-msg]$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]
[hamajyotan@host reliable-msg]$ rake
(in /home/sakaguchi/reliable-msg)
/usr/local/bin/ruby -w -I"lib" "/usr/local/lib/ruby/1.9.1/rake/rake_test_loader.rb" "test/test-rails.rb" "test/test-queue.rb" "test/test-topic.rb"
Loaded suite /usr/local/lib/ruby/1.9.1/rake/rake_test_loader
Started
I, [2011-04-18T01:16:06.721649 #12139]  INFO -- : Loaded queues configuration from: /home/sakaguchi/reliable-msg/queues.cfg
I, [2011-04-18T01:16:06.721784 #12139]  INFO -- : Using message store: disk
I, [2011-04-18T01:16:06.723568 #12139]  INFO -- : Accepting requests at: druby://localhost:6438
#
# 途中省略。。
#
I, [2011-04-18T01:16:25.856555 #12139]  INFO -- : Stopped queue manager at: druby://localhost:6438
I, [2011-04-18T01:16:25.856668 #12139]  INFO -- : Using message store: disk
I, [2011-04-18T01:16:25.857284 #12139]  INFO -- : Accepting requests at: druby://localhost:6438
I, [2011-04-18T01:16:25.857772 #12139]  INFO -- : Stopped queue manager at: druby://localhost:6438
.
Finished in 19.137064 seconds.

11 tests, 97 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 37415
[hamajyotan@host reliable-msg]$

すべてテストをパスした!!更に警告無し!!

gem 化して確認

gem 化
[hamajyotan@host reliable-msg]$ gem build reliable-msg.gemspec
WARNING:  bin/queues is missing #! line
  Successfully built RubyGem
  Name: reliable-msg
  Version: 1.2.0
  File: reliable-msg-1.2.0.gem
インストール
[hamajyotan@host reliable-msg]$ sudo gem install reliable-msg-1.2.0.gem
Successfully installed reliable-msg-1.2.0
1 gem installed
キューマネージャ起動、終了
[hamajyotan@host reliable-msg]$ queues manager start
I, [2011-04-18T01:19:04.841320 #12398]  INFO -- : Loaded queues configuration from: /home/sakaguchi/reliable-msg/queues.cfg
I, [2011-04-18T01:19:04.841743 #12398]  INFO -- : Using message store: disk
I, [2011-04-18T01:19:04.845010 #12398]  INFO -- : Accepting requests at: druby://localhost:6438
# ここで Ctrl+C
^CI, [2011-04-18T01:19:06.555624 #12398]  INFO -- : Stopped queue manager at: druby://localhost:6438
[hamajyotan@host reliable-msg]$

動いた!!

とりあえず

fork して作業成果を配置。
https://github.com/hamajyotan/reliable-msg