LibreOffice の力で PDF エクスポートする librepdf

現在大きな仕事を終えて、今は自身の知識の棚おろし中・・ノウハウの結晶化をしておきたいという個人的な動機から librepdf なんていう gem を公開しました。
https://github.com/hamajyotan/librepdf
ざっくりいうと、 LibreOffice の機能を用いて各種ファイルから PDF エクスポートできるというシロモノで、 MS Office 文書なんかも OK です。JRuby 専用、LibreOffice の UNO APIJava で叩いて、それを Ruby から使えるようにしています。まだまだまだ不安定ですが、きっと開示したほうがハッピーだなと思った次第です。

インストール

$ gem install librepdf

事前に LibreOffice をサービスとして起動する

$ /opt/libreoffice3.5/program/soffice.bin \
  --accept="socket,host=127.0.0.1,port=8100,tcpNoDelay=1;urp;" \
  --headless \
  --invisible \
  --nodefault \
  --nofirststartwizard \
  --nolockcheck \
  --nologo \
  --norestore

起動したら 8100 ポートのリスンを確認

$ netstat -ant | grep 8100
tcp        0      0 127.0.0.1:8100              0.0.0.0:*                   LISTEN

使い方サンプル

/home/hamajyotan/test.doc から、 /home/hamajyotan/test.pdf を作成
require 'rubygems'
require 'librepdf'

con = Connection.new "host" => "127.0.0.1", "port" => 8100
doc = con.load "file:///home/hamajyotan/test.doc"
doc.convert_pdf "file:///home/hamajyotan/test.pdf"
doc.close
con.close
上記の後処理省力化的な書き方版
require 'rubygems'
require 'librepdf'

Librepdf::Connection.new "host" => "127.0.0.1", "port" => 8100 do |con|
  con.load "file:///home/hamajyotan/test.doc" do |doc|
    doc.convert_pdf "file:///home/hamajyotan/test.pdf"
  end
end
僕はブレースの方が好み
require 'rubygems'
require 'librepdf'

Librepdf::Connection.new("host" => "127.0.0.1", "port" => 8100) { |con|
  con.load("file:///home/hamajyotan/test.doc") { |doc|
    doc.convert_pdf "file:///home/hamajyotan/test.pdf"
  }
}
パスワード付きファイルを開く
require 'rubygems'
require 'librepdf'

Librepdf::Connection.new("host" => "127.0.0.1", "port" => 8100) { |con|
  con.load("file:///home/hamajyotan/test.doc", "Password" => "SECRETPASSWORD") { |doc|
    doc.convert_pdf "file:///home/hamajyotan/test.pdf"
  }
}
ページを指定して PDF エクスポート

ページ範囲外の指定は例外が投げられます。

require 'rubygems'
require 'librepdf'

Librepdf::Connection.new("host" => "127.0.0.1", "port" => 8100) { |con|
  con.load("file:///home/hamajyotan/test.doc") { |doc|
    doc.convert_pdf "file:///home/hamajyotan/test1.pdf", "FilterData" => { "PageRange" => "1-1" }
    doc.convert_pdf "file:///home/hamajyotan/test2.pdf", "FilterData" => { "PageRange" => "2-2" }
    doc.convert_pdf "file:///home/hamajyotan/test3.pdf", "FilterData" => { "PageRange" => "3-3" }
  }
}

クラスの説明

Librepdf::Connection

LibreOffice への接続を担う

  • #initialize options = {}
    • options ハッシュへは、 "host" と "port" が受理可能
    • ブロックを渡すと、ブロック引数に自身のインスタンスを渡してブロック終了時にクローズする
  • #closed?
    • 接続を閉じていれば true, そうでなければ false
  • #close
    • 接続を閉じる
  • #load input_url, options = {}
Librepdf::Document

LibreOffice にロードされたドキュメントを示す、Librepdf::Connection#load の返り値として得る以外にインスタンス生成方法は無い。
正確には、このクラスのインスタンスではなく、ドキュメント種類に応じたサブクラスのインスタンスが生成される。例えば Librepdf::Document::Calc など。

  • #closed?
    • 接続を閉じていれば true, そうでなければ false
  • #close
    • 接続を閉じる
  • #convert_pdf output_url, options = {}
    • PDF エクスポートを行う
    • options ハッシュへは、 UNO の storeToURL の第2引数に渡すプロパティの一部が設定可能

今後やりたいこと

CentOS に gem kyotocabinet-ruby を入れる

導入した環境

$ cat /etc/redhat-release
CentOS release 5.5 (Final)
$ ruby -v
ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-linux]
$ gem -v
1.3.7

新しめの gcc が必要

gcc 4.1 ではダメ

$ gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

故に gcc44 をインストール

$ sudo yum -y install gcc44 gcc44-c++

kyotocabinet をインストール

$ wget http://fallabs.com/kyotocabinet/pkg/kyotocabinet-1.2.71.tar.gz
$ tar zvxf kyotocabinet-1.2.71.tar.gz
$ cd kyotocabinet-1.2.71
$ CC=gcc44 CXX=g++44 ./configure && make && sudo make install

(場合によっては) /usr/local/lib ディレクトリを ldconfig

$ sudo sh -c "echo /usr/local/lib > /etc/ld.so.conf.d/local.conf"
$ sudo /sbin/ldconfig

gem kyotocabinet-ruby のインストール ⇒ 失敗

$ sudo gem install kyotocabinet-ruby
Building native extensions.  This could take a while...
ERROR:  Error installing kyotocabinet-ruby:
        ERROR: Failed to build gem native extension.

/usr/local/bin/ruby extconf.rb
setting variables ...
  $CFLAGS = -I. -I/usr/local/include -Wall -g -O2 -O2
  $LDFLAGS = -L.  -rdynamic -Wl,-export-dynamic -L. -L/usr/local/lib
  $libs =  -lkyotocabinet -lz -lstdc++ -lrt -lpthread -lm -lc
checking for kccommon.h... yes
creating Makefile

make
g++ -I. -I/usr/local/lib/ruby/1.8/i686-linux -I/usr/local/lib/ruby/1.8/i686-linux -I. -DHAVE_KCCOMMON_H  -D_FILE_OFFSET_BITS=64 -fPIC -I. -I/usr/local/include -Wall -g -O2 -O2   -c kyotocabinet.cc
kyotocabinet.cc: In function ‘VALUE db_increment(int, VALUE*, VALUE)’:
kyotocabinet.cc:2501: 警告: converting to ‘int64_t’ from ‘double’
kyotocabinet.cc:2531: 警告: converting to ‘int64_t’ from ‘double’
kyotocabinet.cc:2547: 警告: converting to ‘int64_t’ from ‘double’
kyotocabinet.cc: In function ‘VALUE db_match_prefix(int, VALUE*, VALUE)’:
kyotocabinet.cc:3587: 警告: converting to ‘int64_t’ from ‘double’
kyotocabinet.cc: In function ‘VALUE db_match_regex(int, VALUE*, VALUE)’:
kyotocabinet.cc:3663: 警告: converting to ‘int64_t’ from ‘double’
/usr/local/include/kcmap.h: At global scope:
#
# 途中省略...
#
Gem files will remain installed in /usr/local/lib/ruby/gems/1.8/gems/kyotocabinet-ruby-1.27.1 for inspection.
Results logged to /usr/local/lib/ruby/gems/1.8/gems/kyotocabinet-ruby-1.27.1/gem_make.out

gccコンパイルに失敗している

gem kyotocabinet-ruby のインストール ⇒ 成功

gem install の際にも gcc44 を用いてコンパイルする

$ sudo CC=gcc44 CXX=g++44 gem install kyotocabinet-ruby
Building native extensions.  This could take a while...
Successfully installed kyotocabinet-ruby-1.27.1
1 gem installed

成功!

動作確認

$ irb
irb(main):001:0> require 'rubygems'; require 'kyotocabinet'
=> true
irb(main):002:0> KyotoCabinet
=> KyotoCabinet

ちゃんと見えてるっぽい

C++ で Ruby 拡張ライブラリ ( Data_Wrap_Struct とか )

とりあえず以下ダラダラのアウトプットはこちら

gist に置いといた https://gist.github.com/1437782

作りたいもの

  • 僕は神になる。ヒト(クラス)を作ってみるぞ
    • 人とは、名前と年齢を持っている存在である
    • 人とは、名前と年齢を伝える為、ちゃんとご挨拶ができる存在である

作り方とか

  1. C++ で、ヒトクラスを作成する
    • デフォルトコンストラクタ使わない、常に引数 (名前, 年齢) を受けるようにする
  2. C++ 製ヒトクラスを元に Ruby 拡張ライブラリを書く

C++ でヒトクラスを作る

human.hxx
/*
 * human.hxx
 */

#ifndef _INCLUDE_HUMAN_H_
#define _INCLUDE_HUMAN_H_

#include <string>
#include <sstream>

class Human
{
  private:
    static const int _signature = 0x123f3f7c;

  public:
    Human(const char* name, int age);
    virtual ~Human() {}

    bool isLegal() const { return this->_sig == _signature; }

    std::string toString() const;
    std::string greet() const;

  private:
    int _sig;
    std::string _name;
    int _age;
};

#endif // _INCLUDE_HUMAN_H_
human.cxx
/*
 * human.cxx
 */

#include "human.hxx"

Human::Human(const char* name, int age) {
  this->_sig  = _signature;
  this->_name = name;
  this->_age  = age;
}

std::string Human::toString() const {
  std::ostringstream ret;
  ret << "Human [name="
      << this->_name
      << ", age="
      << this->_age
      << "]";
  return ret.str();
}

std::string Human::greet() const {
  std::ostringstream ret;
  ret << "My name is "
      << this->_name
      << ". I'm "
      << this->_age
      << " years old.";
  return ret.str();
}
C++ 製ヒトクラスはこんな感じで使える
#include <iostream>
#include "human.hxx"

void show(Human* p) {
  std::cout << p << std::endl;
  if (p->isLegal()) { // 正しいオブジェクトの場合のみ出力する
    std::cout << "\t" << p->toString() << std::endl
              << "\t" << p->greet()    << std::endl;
  }
}

int main() {
  Human h("hamajyotan", 18); // ごく普通にインスタンス生成
  show(&h);

  Human* p; // 初期化されていないポインタを与える
  show(p);  // この場合、アドレスのみしか出力されない

  p = new Human("foo", 20); // new でインスタンス生成
  show(p);
  delete p;

  return 0;
}

実行結果例。 2回目の show 呼び出しではアドレスしか出力されていない

$ ./a.out
0xbfcc104c
        Human [name=hamajyotan, age=18]
        My name is hamajyotan. I'm 18 years old.
0xbfcc1078
0x8d00028
        Human [name=foo, age=20]
        My name is foo. I'm 20 years old.
C++ 製ヒトクラスのメソッドまとめ
  • コンストラク
    • 引数 2つ、 name と age
  • toString()
    • オブジェクトの文字列表現を返す
  • greet()
    • オブジェクトの挨拶文を返す
  • isLegal()
    • オブジェクトが初期化されている (コンストラクタを経由している)場合にのみ true を返す
    • つまり、初期化されていない等の理由による不正なアドレスでは false を返す

C++ 製ヒトクラスを元に Ruby 拡張ライブラリを書く

humanwrap.cxx
/**
 * humanwrap.cxx
 */

#include <new> // for replacement new
#include "ruby.h"
#include "human.hxx"

/**
 * Ruby 製 Human クラスのインスタンスから
 * C++ 製 Human クラスのインスタンスのために割り当てたメモリ領域のポインタを得ます.
 */
static Human* getHuman(VALUE self) {
  Human* p;
  Data_Get_Struct(self, Human, p);
  return p;
}

/**
 * GC によりメモリが解放される際に呼び出されます.
 *
 * wrap_Human_init で replacement new を使い
 * C++ 製 Human の初期化を実施するため明示的なデストラクタ呼び出しが必要になります。
 * 
 * C++ 製クラスのためにメモリ領域が割り当てられたにも関わらず、
 * #initialize の引数が不正だった場合など、 C++ 製 Human のインスタンスが作成されない経路も存在します
 * この場合ポインタは不正なアドレスを指しているため、デストラクタを呼び出さないようにします。
 */
static void wrap_Human_free(Human* p) {
  if (p->isLegal()) p->~Human();
  ruby_xfree(p);
}

/**
 * 初期化時にメモリを割り当てます.
 */
static VALUE wrap_Human_alloc(VALUE klass) {
  // Human インスタンスのためのメモリ空間を確保し self に関連付けます
  // 更に、 GC が実行された際のハンドラも登録します
  return Data_Wrap_Struct(klass, NULL, wrap_Human_free, ruby_xmalloc(sizeof(Human)));
}

/**
 * 
 */
static VALUE wrap_Human_init(VALUE self, VALUE _name, VALUE _age) {
  // 以下 2行で #initialize に渡された引数の検証を行います
  // 引数が正しくない場合、例外が投げられるために C++ 製 Human クラスは初期化されません。
  // 
  // この場合も GC の際に wrap_Human_free が呼ばれますが、
  // wrap_Human_free に渡されるポインタは初期化されていないために不正なアドレスを指します
  const char* name = StringValuePtr(_name); // 文字列でなければ例外が投げられます
  int age = NUM2INT(_age); // 数値でなければ例外が投げられます

  Human* p = getHuman(self); // メモリ領域を得ます
  new (p) Human(name, age);  // replacement new により既存のメモリ空間にインスタンスを初期化します

  return Qnil; // 使われません、適当な値を返しておきます
}

/**
 * toString() を実行して結果を Ruby の String クラスに変換して返します.
 */
static VALUE wrap_Human_toString(VALUE self) {
  return rb_str_new2(getHuman(self)->toString().c_str());
}

/**
 * greet() を実行して結果を Ruby の String クラスに変換して返します.
 */
static VALUE wrap_Human_greet(VALUE self) {
  return rb_str_new2(getHuman(self)->greet().c_str());
}

/**
 * require 'human' の際に最初に実行されます.
 */
extern "C" void Init_human() {
  // Ruby の Human クラスを定義する
  VALUE c = rb_define_class("Human", rb_cObject);

  rb_define_alloc_func(c, wrap_Human_alloc); // 初期化時にメモリを確保する関数を登録する
  rb_define_private_method(c, "initialize", RUBY_METHOD_FUNC(wrap_Human_init), 2); // #initialize
  rb_define_method(c, "inspect", RUBY_METHOD_FUNC(wrap_Human_toString), 0);        // #inspect
  rb_define_method(c, "to_s", RUBY_METHOD_FUNC(wrap_Human_toString), 0);           // #to_s
  rb_define_method(c, "greet", RUBY_METHOD_FUNC(wrap_Human_greet), 0);             // #greet
}
extconf.rb
require "mkmf"
$libs += " -lstdc++ " # for STL
create_makefile("human")

拡張ライブラリを作成してみる

特定のディレクトリに 4 ファイルを集めて ruby extconf.rb そして make
human.so が出来上がる。

$ ls /home/hamajyotan/tmp/
extconf.rb  human.cxx  human.hxx  humanwrap.cxx
$ ruby extconf.rb
creating Makefile
$ make
g++ -I. -I/usr/local/include/ruby-1.9.1/i686-linux -I/usr/local/include/ruby-1.9.1/ruby/backward -I/usr/local/include/ruby-1.9.1 -I. -D_FILE_OFFSET_BITS=64  -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long   -o humanwrap.o -c humanwrap.cxx
g++ -I. -I/usr/local/include/ruby-1.9.1/i686-linux -I/usr/local/include/ruby-1.9.1/ruby/backward -I/usr/local/include/ruby-1.9.1 -I. -D_FILE_OFFSET_BITS=64  -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long   -o human.o -c human.cxx
g++ -shared -o human.so humanwrap.o human.o -L. -L/usr/local/lib -Wl,-R/usr/local/lib -L.  -rdynamic -Wl,-export-dynamic   -lstdc++  -lpthread -lrt -ldl -lcrypt -lm   -lc
$ ls
Makefile  extconf.rb  human.cxx  human.hxx  human.o  human.so  humanwrap.cxx  humanwrap.o
$

使ってみる

human.so の作られたディレクトリでそのまま irb 叩いてみる

irb(main):001:0> require './human'
=> true
irb(main):002:0> h = Human.new 'hamajyotan', 18
=> Human [name=hamajyotan, age=18]
irb(main):003:0> h.greet
=> "My name is hamajyotan. I'm 18 years old."
irb(main):004:0>

(゚∀゚)キタコレ!!

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

ReliableMsgAgent で Ap4r と同様のディスパッチを実行する

ReliableMsgAgent で「ap4r と同様のメッセージディスパッチ」をする

  • ap4r の I/F を用いてメッセージを put する
  • put したメッセージは、 ap4r にディスパッチさせずに reliable-msg-agent に自律的に取得させる。要するに pull 型アプローチをとる ap4r のようなもの
  • その際のディスパッチ方法として、 ap4r と同様のルールに則る。
    • たとえば ap4r のディスパッチモード HTTP の場合以下条件が成立する事で成功とみなす
ap4r の準備

以下、ap4r の作業ディレクトリを /home/hamajyotan/ap4r/ と仮定
ap4r のインストール

$ gem install ap4r --no-ri --no-rdoc

activesupport のインストール。 ap4r 初期化に必要

$ gem install activesupport -v "<3.0.0" --no-ri --no-rdoc

ap4rワークスペースを作る

$ cd /home/hamajyotan/ap4r/
$ ap4r_setup .
make application root directory [/home/hamajyotan/ap4r] ...
make directories for AP4R [config, log, public, script, tmp] ...
copy files from $GEM_HOME/gems/ap4r-0.3.7/config to /home/hamajyotan/ap4r/config ...
copy files from $GEM_HOME/gems/ap4r-0.3.7/script to /home/hamajyotan/ap4r/script ...
copy file from $GEM_HOME/gems/ap4r-0.3.7/fresh_rakefile to /home/hamajyotan/ap4r/Rakefile ...

[/home/hamajyotan/ap4r] has successfully set up!

$

以下のように /home/hamajyotan/ap4r/config/queues_notargets.cfg を作成する
ap4r が勝手にメッセージをディスパッチしないようにする

---
store:
  type: disk
drb:
  host:
  port: 6438
  acl: allow 127.0.0.1 allow ::1 allow 10.0.0.0/8
dispatchers:
  -
    targets: notargets
    threads: 1
#carriers:
#  -
#    source_uri: druby://another.host.local:6438
#    threads: 1
ap4r の起動
$ cd /home/hamajyotan/ap4r/
$ ruby script/mongrel_ap4r start -A config/queues_notargets.cfg
---
===
===
** Starting AP4R Handler with config/queues_notargets.cfg
Loaded queues configuration from: /home/hamajyotan/ap4r/config/queues_notargets.cfg
Using message store: disk
Accepting requests at: druby://localhost:6438
about to start dispatchers with config
---
- threads: 1
  targets: notargets

start dispatcher: targets= #<ReliableMsg::MultiQueue:0x2b89a7d39e90>, index= 0)
dispatch targets are : notargets;
queue manager has forked dispatchers
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Mongrel available at 0.0.0.0:7438
** Use CTRL-C to stop.
** Mongrel start up process completed.
reliable-msg-agent の設定ファイルの準備

ap4r を起動したコンソールとは別窓で作業
以下、reliable-msg-agnet の作業ディレクトリを /home/hamajyotan/reliable-msg-agent/ と仮定
gem をインストールしたディレクトリ配下の resources/ ディレクトリから必要なファイルをコピーする

$ cp $GEM_HOME/gems/reliable-msg-agent-0.1.0/resources/agent.conf /home/hamajyotan/reliable-msg-agent/
$ cp $GEM_HOME/gems/reliable-msg-agent-0.1.0/resources/examples/ap4r-dispatch-agent.rb /home/hamajyotan/reliable-msg-agent/
agent.conf に設定を加える

コンフィグにエージェントの定義ファイルのパスを教える。

$ echo agent: /home/hamajyotan/reliable-msg-agent/ap4r-dispatch-agent.rb >> /home/hamajyotan/reliable-msg-agent/agent.conf

コンフィグの確認

---
logger: " Proc.new { |file| l = Logger.new(file); l.level = Logger::DEBUG; l } "

consumers:
  -
    source_uri: druby://localhost:6438
    every: 1.0
    target: queue.agent
    threads: 1

agent: /home/hamajyotan/reliable-msg-agent/ap4r-dispatch-agent.rb

コンフィグを少し書き換える。

  • modify_rules
    • url
      • ディスパッチ先 url http://localhosthttp://localhost:9292 に書き換える
---
logger: " Proc.new { |file| l = Logger.new(file); l.level = Logger::DEBUG; l } "

consumers:
  -
    source_uri: druby://localhost:6438
    every: 1.0
    target: queue.agent
    threads: 1
    modify_rules:
      url: " Proc.new { |url| url.port = 9292; url } "

agent: /home/hamajyotan/reliable-msg-agent/ap4r-dispatch-agent.rb
ここでエージェントの #call メソッドの定義を確認しておく
# this script is evaluated by the context of ReliableMsg::Agnet::Agent class.

require "yaml"
require "ap4r"

#
# The method of processing the message is defined.
#
# if the evaluation result is nil or false,
# it is considered that it failes.
#
# === Args
#
# +msg+     :: fetched message from reliable-msg queue.
# +conf+    :: consumer configurations.
# +options+ :: the options (it is still unused.)
#
def call msg, conf, options = {}

  # The following codes use the mechanism of sending the message by ap4r.
  dispatcher = Ap4r::Dispatchers.new nil, [], @logger

  @logger.debug { "dispatcher get message\n#{msg.to_yaml}" }
  response = dispatcher.send(:get_dispather_instance,
                             msg.headers[:dispatch_mode],
                             msg,
                             conf).call
  @logger.debug { "dispatcher get response\n#{response.to_yaml}" }

end

ap4r の実装の一部をコンフィグで埋め込んでいる、ほぼおまじない。
これにより、 ap4r で実装されている内容と同様のメッセージディスパッチが可能になる。※ ap4r 0.3.7 で確認

reliable-msg-agent の起動
$ reliable-msg-agent start -c /home/hamajyotan/reliable-msg-agent/agent.conf
*** Starting ReliableMsg-Agent...
I, [2011-04-12T01:22:28.678494 #9696]  INFO -- : *** reliable-msg agent service starting...
I, [2011-04-12T01:22:28.678822 #9696]  INFO -- : --- starting workers.
I, [2011-04-12T01:22:28.680012 #9696]  INFO -- : *** reliable-msg agent service started.
適当な web サーバを準備する

更に別窓で作業する。
以下を満たせばなんでも良い

  • (先に、コンフィグに設定している為)9292番ポートでリスンする
  • ステータスコードに 200 で、レスポンスボディに true (を含む文字列) を返す

今回は rack で適当に作る

$ gem install rack --no-ri --no-rdoc

rack のコンフィグファイルを適当に書く
/home/hamajyotan/stub.ru

#
# /home/hamajyotan/stub.ru
#
require "rubygems"
require "rack"

class Stub
  def call env
    [200, {"Content-Type" => "text/plain"}, ["true"]]
  end
end
run Stub.new

rack で web サーバ起動しておく

$ rackup /home/hamajyotan/stub.ru
ap4r へメッセージを put する

更に別窓で作業する。
以下の代わりに ap4rrails プラグインを使って put しても良い

$ irb -rubygems -r reliable-msg
irb(main):001:0> q = ReliableMsg::Queue.new "queue.agent"
 => #<ReliableMsg::Queue:0x2ac0e926f7a8 @queue="queue.agent">
irb(main):002:0> q.put "", {:dispatch_mode => :HTTP,
irb(main):003:0>            :target_method => :POST,
irb(main):004:0>            :target_url => "http://localhost/",
irb(main):005:0>            :queue => "queue.agent",
irb(main):006:0>            :delivery => :once}
 => "2f5e6c80-4688-012e-8954-f9a630fcd34e"
irb(main):007:0> exit
$
reliable-msg-agent を起動したコンソールを見てみる
  1. サービス起動
  2. メッセージ取得
  3. url 書き換え http://localhost/ -> http://localhost:9292
  4. http://localhost:9292 へアクセスして成功

の一連のログが出力されている模様

$ reliable-msg-agent start -c ./agent.conf
*** Starting ReliableMsg-Agent...
*** reliable-msg agent service starting...
--- starting workers.
*** reliable-msg agent service started.
message fetched - <2f5e6c80-4688-012e-8954-f9a630fcd34e>
dispatcher get message
--- !ruby/object:ReliableMsg::Message
headers:
  :dispatch_mode: :HTTP
  :target_url: http://localhost/
  :created: 1302539965
  :expires:
  :queue: queue.agent
  :delivery: :once
  :id: 2f5e6c80-4688-012e-8954-f9a630fcd34e
  :target_method: :POST
  :priority: 0
  :max_deliveries: 5
id: 2f5e6c80-4688-012e-8954-f9a630fcd34e
object: ""

Ap4r::Dispatcher after modification
--- !ruby/object:ReliableMsg::Message
headers:
  :dispatch_mode: :HTTP
  :target_url: http://localhost:9292/
  :created: 1302539965
  :expires:
  :queue: queue.agent
  :delivery: :once
  :id: 2f5e6c80-4688-012e-8954-f9a630fcd34e
  :target_method: :POST
  :priority: 0
  :max_deliveries: 5
id: 2f5e6c80-4688-012e-8954-f9a630fcd34e
object: ""

response status [200 OK]
dispatcher get response
--- !ruby/object:Net::HTTPOK
body: "true"
body_exist: true
code: "200"
header:
  content-type:
  - text/plain
  connection:
  - close
  date:
  - Mon, 11 Apr 2011 16:39:25 GMT
  transfer-encoding:
  - chunked
http_version: "1.1"
message: OK
read: true
socket:
stub.ru (webサーバ) を起動したコンソールを見てみる

アクセスがあった模様

$ rackup /home/hamajyotan/stub.ru
127.0.0.1 - - [12/Apr/2011 01:39:25] "POST / HTTP/1.1" 200 - 0.0023
内容をマンガで説明
  1. push-msg.sh が ap4r へメッセージを put する
  2. ap4r の周りを巡回している reliable-msg-agent がメッセージを get する
  3. (任意)メッセージヘッダの :target_url の書き換えを行う
    • http://localhost => http://localhost:9292
  4. reliable-msg-agent は ap4r のディスパッチルールに則り http://localhost:9292 へアクセス

[追記] http のタイムアウト

ap4r の dispatchers 機能をエージェントに記述しているため、コンフィグ consumers には、ap4r における dispatchers と同様のコンフィグが書ける。
試しに Ap4r::Dispachers に実装されている http タイムアウトを実験する。

http タイムアウトに関する設定を追記

reliable-msg-agent のコンフィグファイルに http タイムアウトに関する設定を追記
3秒以内に処理が終わらなければ失敗とみなす
/home/hamajyotan/reliable-msg-agent/agent.conf

---
logger: " Proc.new { |file| l = Logger.new(file); l.level = Logger::DEBUG; l } "

consumers:
  -
    source_uri: druby://localhost:6438
    every: 1.0
    target: queue.agent
    threads: 1
    modify_rules:
      url: " Proc.new { |url| url.port = 9292; url } "
    http:
      timeout: 3

agent: /home/hamajyotan/reliable-msg-agent/ap4r-dispatch-agent.rb
stub.ru が 10秒かかるようにしておく
#
# /home/hamajyotan/stub.ru
#
require "rubygems"
require "rack"

class Stub
  def call env
    sleep 10
    [200, {"Content-Type" => "text/plain"}, ["true"]]
  end
end
run Stub.new
上記設定でメッセージを put してみる

エージェントの処理がタイムアウトで落ちている
reliable-msg-agent では以下のログが出力される

#
# 途中省略
#
message fetched - <52222a00-4692-012e-8f7a-f9afb181b616>
dispatcher get message
--- !ruby/object:ReliableMsg::Message
headers:
  :dispatch_mode: :HTTP
  :target_url: http://localhost/test/index.html
  :created: 1302544318
  :expires:
  :queue: queue.agent
  :delivery: :once
  :id: 52222a00-4692-012e-8f7a-f9afb181b616
  :target_method: :POST
  :priority: 0
  :max_deliveries: 5
id: 52222a00-4692-012e-8f7a-f9afb181b616
object: ""

Ap4r::Dispatcher after modification
--- !ruby/object:ReliableMsg::Message
headers:
  :dispatch_mode: :HTTP
  :target_url: http://localhost:9292/test/index.html
  :created: 1302544318
  :expires:
  :queue: queue.agent
  :delivery: :once
  :id: 52222a00-4692-012e-8f7a-f9afb181b616
  :target_method: :POST
  :priority: 0
  :max_deliveries: 5
id: 52222a00-4692-012e-8f7a-f9afb181b616
object: ""

set HTTP read timeout to 3s
error in fetch-msg/agent-proc: execution expired
/home/hamajyotan/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/timeout.rb:64:in `rbuf_fill'
        /home/hamajyotan/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/net/protocol.rb:134:in `rbuf_fill'
        /home/hamajyotan/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/net/protocol.rb:116:in `readuntil'
        /home/hamajyotan/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/net/protocol.rb:126:in `readline'
        /home/hamajyotan/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/net/http.rb:2028:in `read_status_line'
#
# 途中省略
#
msg-agent:75:in `send'
        /home/hamajyotan/.rvm/gems/ruby-1.8.7-p302/gems/reliable-msg-agent-0.1.0/bin/reliable-msg-agent:75
        /home/hamajyotan/.rvm/gems/ruby-1.8.7-p302/bin/reliable-msg-agent:19:in `load'
        /home/hamajyotan/.rvm/gems/ruby-1.8.7-p302/bin/reliable-msg-agent:19

Ruby の retry はどこからリトライしているのか ?(Ruby 1.8系)

ちなみに

  • 下記は、Ruby 1.9系では構文エラーになる。

知ってちょっとびっくりした

#
# test.rb
#
class RetryTest
  def initialize
    @x = 0
    puts "initialize!!"
  end
  def test x
    @x += 1
    yield @x
    retry if x > @x
  end
end

puts RUBY_VERSION

t = RetryTest.new
t.test(3) { |i| puts "test - #{i}" }
$ ruby test.rb
1.8.7
initialize!!
test - 1
test - 2
test - 3

RetryTest#test は

  • @x をインクリメント
  • ブロックを評価、その際に引数に @x を渡す
  • @x が x を超えない場合は リトライ する

リトライってどこからしているの?

実行結果から、 #test が再実行されている事は明らかだが・・

#
# test.rb
#
class RetryTest
  def initialize
    @x = 0
    puts "initialize!!"
  end
  def test x
    @x += 1
    yield @x
    retry if x > @x
  end
end

def hoge
  puts "#hoge called!"
  3
end

puts RUBY_VERSION

t = RetryTest.new
t.test(hoge) { |i| puts "test - #{i}" }
$ ruby test.rb
1.8.7
initialize!!
#hoge called!
test - 1
#hoge called!
test - 2
#hoge called!
test - 3

どうやら引数も再評価されている。これは予想の上だった。

更にびっくりした

インスタンス生成とメソッド呼び出しを1行で書いてみる

#
# test.rb
#
class RetryTest
  def initialize
    @x = 0
    puts "initialize!!"
  end
  def test x
    @x += 1
    yield @x
    retry if x > @x
  end
end

def hoge
  puts "#hoge called!"
  3
end

puts RUBY_VERSION

RetryTest.new.test(hoge) { |i| puts "test - #{i}" }
$ ruby test.rb
1.8.7
initialize!!
#hoge called!
test - 1
initialize!!
#hoge called!
test - 1
initialize!!
#hoge called!
test - 1
以下延々と続く・・・

リトライのたびにインスタンスが生成されている
引数の評価だけでなく、行全体が再度評価されている模様。
これは初見だとハマると思う。

Ruby 1.9 系ではこんなエラー

$ rvm 1.9.2 test.rb
test.rb:9: Invalid retry
test.rb: compile error (SyntaxError)

ReliableMsg からメッセージ拾って何か処理する ReliableMsgAgent

ReliableMsgAgent

まだまだ改善の余地があるが書いてみました。

こんな実装
  • ReliableMsg に置いてあるメッセージを自律的に取得する
  • メッセージに応じて何か処理をする
    • 「何か処理」は自分で定義できる
ReliableMsgAgent のインストール
$ gem install reliable-msg-agent

ちょっと試すその1

ReliableMsg のキューを起動しておく
$ queues manager start
I, [2011-04-10T02:56:06.061758 #32480]  INFO -- : Created queues configuration file in: /usr/lib/ruby/gems/1.8/gems/reliable-msg-1.1.0/queues.cfg
I, [2011-04-10T02:56:06.062504 #32480]  INFO -- : Using message store: disk
I, [2011-04-10T02:56:06.074105 #32480]  INFO -- : Accepting requests at: druby://localhost:6438
ReliableMsgAgent プロセス起動

別窓で

$ reliable-msg-agent start
*** Starting ReliableMsg-Agent...
I, [2011-04-10T02:57:54.933661 #32520]  INFO -- : *** reliable-msg agent service starting...
I, [2011-04-10T02:57:54.934218 #32520]  INFO -- : --- starting workers.
I, [2011-04-10T02:57:54.938412 #32520]  INFO -- : *** reliable-msg agent service started.
ReliableMsg キューにメッセージを投げてみる

更に別窓で

$ irb -rubygems -r reliable-msg
irb(main):001:0> q = ReliableMsg::Queue.new "queue.agent"
 => #<ReliableMsg::Queue:0x2abc07e020e8 @queue="queue.agent">
irb(main):002:0> q.put "(*^o^)=3"
 => "36086840-4501-012e-1595-f3f4f884137e"
irb(main):003:0> exit
$
ReliableMsgAgent を起動したコンソールを見る
$ reliable-msg-agent start
*** Starting ReliableMsg-Agent...
I, [2011-04-10T02:57:54.933661 #32520]  INFO -- : *** reliable-msg agent service starting...
I, [2011-04-10T02:57:54.934218 #32520]  INFO -- : --- starting workers.
I, [2011-04-10T02:57:54.938412 #32520]  INFO -- : *** reliable-msg agent service started.
I, [2011-04-10T03:00:43.902741 #32520]  INFO -- : message fetched - <36086840-4501-012e-1595-f3f4f884137e>
I, [2011-04-10T03:00:43.903403 #32520]  INFO -- : message received
--- !ruby/object:ReliableMsg::Message
headers:
  :expires:
  :created: 1302372043
  :delivery: :best_effort
  :id: 36086840-4501-012e-1595-f3f4f884137e
  :priority: 0
  :max_deliveries: 5
id: 36086840-4501-012e-1595-f3f4f884137e
object: (*^o^)=3

ReliableMsg からメッセージを拾って、内容をログ出力している。

ReliableMsgAgent を終了する

INT か TERM シグナルで止まる。
Ctrl+C

I, [2011-04-10T03:10:16.513932 #32520]  INFO -- : *** reliable-msg agent service stopping...
I, [2011-04-10T03:10:16.514195 #32520]  INFO -- : --- stopping workers.
I, [2011-04-10T03:10:16.514432 #32520]  INFO -- : *** reliable-msg agent service stopped.
$

ちょっと試すその2

  • メッセージ取得時の動作を変更してみる。
    • ReliableMsgAgent の既定の動作は、メッセージを取得してログ出力するのみ。
    • エージェントの #call メソッドを(再)定義するとそれを変更できる。
設定ファイルの雛形を準備する

以下、作業ディレクトリを /home/hamajyotan/reliable-msg-agent/ と仮定
gem をインストールしたディレクトリ配下の resources/ ディレクトリから必要なファイルをコピーする

$ cp $GEM_HOME/gems/reliable-msg-agent-0.1.0/resources/agent.conf /home/hamajyotan/reliable-msg-agent/
$ cp $GEM_HOME/gems/reliable-msg-agent-0.1.0/resources/agent.rb /home/hamajyotan/reliable-msg-agent/
agent.conf に設定を加える

要するに、「agent.rb はここにあるよ」という事をコンフィグに書いている。

$ echo agent: /home/hamajyotan/reliable-msg-agent/agent.rb >> agent.conf

コンフィグファイルはこんな感じになっている

$ cat agent.conf
---
logger: " Proc.new { |file| l = Logger.new(file); l.level = Logger::DEBUG; l } "

consumers:
  -
    source_uri: druby://localhost:6438
    every: 1.0
    target: queue.agent
    threads: 1

agent: /home/hamajyotan/reliable-msg-agent/agent.rb
$
agent.rb を見てみる

agent.rb は、エージェントのクラス実装の一部を成している。

# this script is evaluated by the context of ReliableMsg::Agnet::Agent class.

require "yaml"

#
# The method of processing the message is defined.
#
# if the evaluation result is nil or false,
# it is considered that it failes.
#
# === Args
#
# +msg+     :: fetched message from reliable-msg queue.
# +conf+    :: consumer configurations.
# +options+ :: the options (it is still unused.)
#
def call msg, conf, options = {}
  @logger.info { "message received\n#{msg.to_yaml}" }
end
agent.rb を書き換える

例えば、メッセージでうけた名称のディレクトリを /tmp に作成する処理

#
# /home/hamajyotan/reliable-msg-agent/agent.rb
#
require "fileutils"

def call msg, conf, options = {}
  dir = File.join("/tmp", msg.object)
  FileUtils.mkdir_p dir
  @logger.info { "directory created - #{dir}" }
end
ReliableMsgAgent の起動

今度は、コンフィグファイルを指定しつつ実行する

$ reliable-msg-agent start -c /home/hamajyotan/reliable-msg-agent/agent.conf
*** Starting ReliableMsg-Agent...
I, [2011-04-10T03:39:36.582628 #32659]  INFO -- : *** reliable-msg agent service starting...
I, [2011-04-10T03:39:36.582793 #32659]  INFO -- : --- starting workers.
I, [2011-04-10T03:39:36.584784 #32659]  INFO -- : *** reliable-msg agent service started.
ReliableMsg キューにメッセージを投げてみる

別窓で

$ irb -rubygems -r reliable-msg
irb(main):001:0> ReliableMsg::Queue.new("queue.agent").put("foo")
 => "f11a65e0-4506-012e-1595-f3f4f884137e"
irb(main):002:0> ReliableMsg::Queue.new("queue.agent").put("bar")
 => "f93693a0-4506-012e-1595-f3f4f884137e"
ruby-1.8.7-p302 :003 > exit
$
ReliableMsgAgent を起動したコンソールを見る
$ reliable-msg-agent start -c /home/hamajyotan/reliable-msg-agent/agent.conf
*** Starting ReliableMsg-Agent...
I, [2011-04-10T03:39:36.582628 #32659]  INFO -- : *** reliable-msg agent service starting...
I, [2011-04-10T03:39:36.582793 #32659]  INFO -- : --- starting workers.
I, [2011-04-10T03:39:36.584784 #32659]  INFO -- : *** reliable-msg agent service started.
I, [2011-04-10T03:41:45.406074 #32659]  INFO -- : message fetched - <f11a65e0-4506-012e-1595-f3f4f884137e>
I, [2011-04-10T03:41:45.406460 #32659]  INFO -- : directory created - /tmp/foo
I, [2011-04-10T03:41:58.519396 #32659]  INFO -- : message fetched - <f93693a0-4506-012e-1595-f3f4f884137e>
I, [2011-04-10T03:41:58.519885 #32659]  INFO -- : directory created - /tmp/bar

ReliableMsg からメッセージを拾って、ディレクトリを作成している模様。

本当にディレクトリ作られたの?

確認

$ ls -l /tmp/{foo,bar}
/tmp/bar:
合計 0

/tmp/foo:
合計 0
$

たしかに作られている。

今後の課題

  • コンフィグ要素である target の書き方を ap4r に合わせたい
    • target にワイルドカードを使いたい
    • target に複数のキューを並べたい
    • その場合、 target でなく targets であるべきか