Debian Ruby1.9会議の詳細のお知らせ
先日お知らせしたDebian Ruby1.9会議の開催日時と場所が以下に決まりました。
- 日時: 2009-07-05(日) 14:00〜17:00
- 場所: かながわ労働プラザ 第10会議室
参加される方へのお願い
参加される方との連絡・情報交換のためにGoogleグループに場を設けています。ATNDで参加登録された方は、私(akira.yamada gmail.com)まで以下の二点をお知らせください。
- 登録するメールアドレス
- ATNDでのユーザ名
Debian Ruby1.9会議のお知らせ 2
きたる2009-07-05(日)にDebian Ruby1.9会議を開催することになりました。Debianにおける主要なRubyパッケージ開発者のうちの実に二名が参加することが決定しています…… とか言っても、お気付きの通り、そのうちの一人は私です。
今回の開催はRubyパッケージやRubyGemsのパッケージの開発者であるdaigoさんから声をかけていただいたのがきっかけです。現在、構成変更などの議論を進めている関係で、Debian/sidにおけるRuby 1.9のパッケージのリリースが遅れています。その状況を改めて確認し、解決しなければならないことや決定しなければならいことを話し合い、物事を先に進めていこうというのが主旨です。
かなり奥まったところにある話題なので、興味を持つ人は限られるかもしれませんが、話を聞いてみたい、意見がある、パッケージ開発者を見てみたい、といった方がおられましたらご参加ください。Debian Ruby1.9会議の参加登録はATNDで受け付けています。
開催地は川崎〜横浜のどこかになることが決定しています。ただし具体的な会場などは未定で、二人以外の参加者がどれくらいあるかに応じて会場を確保したいと考えています。
私個人としては、参加者がある程度集まって、時間に余裕があるようなら、おまけ的にRuby on Debian談義をするのも悪くないかなと思っていますが、これは単に思い付きレベルの話で未定です。(それにしても奥まった話題のような気もしますが。)
とちぎRuby会議01に行ってきた 3
迷った迷った。直前まで行こうかどうしようか迷った。が、行ってみて良かった。
この手の集まりに行くのはほんとうに久しぶり。久しぶりの参加が新幹線に乗っていくことになるとは我ながら驚きなんだけれども、ともかくいつもよりも早起きをして、寝過ごさないことだけにひたすら集中して移動した。車中では迷いの元であった原稿をパチパチとはじいたり。
とちぎRuby会議は二部構成となっていた。一部は原さんとごとけんさんによる招待講演とLightning Talksが行われ、二部は定期的に開催されている勉強会toRubyの特別版が行われた。
原さんは「博士の愛したRubyと数学」で、いかにRubyを身体の一部として使ってきたか、使っていくか、そしてその経験から未来のRubyの姿を予想するという内容だった。「未来にはもしかするとグローバル変数がなくなるかもしれない。その分クールになった。『むかしは良かった、グローバル変数なんてのがあってね』『グローバル変数を使うときにはちょっとドキドキしたもんだ』なんてことから『復刻版Ruby』が出るかもしれない!」なんて話も。
加えて、(脱線して?)ペンへの愛着についての話題がも盛り込まれていたのだが、その中であった「計算をするとき、紙との間にあるのはペン。ムキーとなってきたとき、それが良いものであることによって、投げ出すまでにあと10秒をもたせることができる。その10秒で先に進める。だから手に気持ち良いものが良い」というのが印象に残った(だいたいこのようなことだったと思う)。講演内容としては「Rubyもそのようなものに」と続くのだが、それはそれとして、ペン以外に紙やキーボードへの愛着もあるのかななんてことを思った。後で尋ねてみるのを忘れてた。
ごとけんさんは「20世紀Ruby」として、なつかしのPerl/Ruby Conferenceから現在までのごとけんさん自身のRubyとの関わり方をベースにした、Rubyをどう楽しむかというお話だった。思いがけずなつかしい名前が出てきてうんうんとうなずきながら聞いていた。WEBrickもさることながら、leakproxy.rbにはずいぶんとお世話になったなあ、なんて。
toRuby特別版ではdRubyを使ってみなでつなぎあってみるというのが行われた。無線LANのトラブルがあったりもしながら、あらかじめ用意されたサーバに接続するだけでなく、各人のPCに対して接続しあうなどできていたようだ。MS-Windowsだとうまく動かなかったり、1.9.1だと動作がおかしいかもしれないという話が出たりと、必ずしもすんなりとはいかないようだったが、終わるころにはみなそれなりに動かすことができたようだったし、時間中はなごやかな雰囲気であったこともあり、参加者はみな楽しめたようだ。やはりつながって動くものを目の前にすると楽しい。
私はというと、OS X上のmacportsで1.8.7と1.9.1を用意しておいたのだが、自ホストのアドレスを逆引きできないせいか(追求してない)DRb.start_serviceで例外が上がるなどして少し手間取った。自アドレスを明示することで例外を避けられたし、1.9.1では例外が上がらなかった。
その後、会場の片付けを少し手伝って、懇親会の会場へ移動。電車の時間を早めにしてしまったので途中あわてて抜け出す感じになってしまったが、直接にはお話ししたことのない人々といろいろとお話できて楽しかった。駅に着いてみれば時間に余裕があったので、せめてちゃんと挨拶をしてくればよかったと待ち合い室のベンチで思った。
勉強会というのに興味があったので、実際に参加してみようと年初に考えたのがきっかけの一つで、こうして実際に見られたのは、少し無理してでもいったかいがあった。運営の雰囲気くらいはつかめたかな。別の勉強会にも参加してみようと思う。その上で、何かネタがあれば近くで勉強会みたいなものを考えるのにも実は興味がある。横浜〜関内駅あたりで企画したらのってくれる人はいるかしら。
toRubyの最後にKEEP-PROBLEM-TRYがあったのでなぞらえてみると、KEEP: こうしたイベントや勉強会への参加を続ける、PROBLEM: せっかくなので行きたかったカフェが営業してなかった(確認不足)、TRY: しゃべる側にまわれるようネタ出し、というところかな。
少しだけど写真を撮ってきたのでよければどうぞ: とちぎRuby会議01
Capistranoであえて非並列にコマンド実行する
システム管理系でCapistranoを使おうとする、なんでもかんでも同時並列実行されると困るというようなことがなくもない。ちょっと考えてみたんだけど、どんなものだろうか。もう少しなんとかなりそうな気もする。
def serialize_for_hosts
task = current_task
hosts = find_servers_for_task(task)
if task.options.include?(:hosts)
save_hosts = task.options[:hosts]
end
begin
hosts.each do |host|
task.options[:hosts] = [host]
yield
end
ensure
task.options[:hosts] = save_hosts if defined?(save_hosts)
end
end
task :bar do
run "echo bar"
end
task :foo do
run "echo foo start"
serialize_for_hosts do
run "sleep 2"
run "echo date"
hogehoge
end
run "echo foo end"
end
某メモより。
gonzuiをrackupしてみた
気になりながらもちゃんと中までは見ていなかったRackを触ってみることにした。「Hello, World!」的なコードはすでにあるので何かをRackに載せてみようと思い、最近身のまわりでちょっとしたブームであるgonzuiを選んだ。Rackのバージョンは0.4.0。
gonzuiに含まれるwebインタフェースであるgonzui-serverはWEBrickを使って書かれている。検索エンジンとwebインタフェースの間には中間層のようなものはないようなので、効率は悪くなりそうだがwrapperを入れることにした。最悪のケースだと[gonzui-engine]<==>[gonzui-server]<--(webrick api)=(rack spec)-->[rack]=[webrick handler]<==>[webrick]のようになる。その代わりに足まわりを変えられる利点が得られる。
とまどったところはいくつかある。
一つはURLMapの使い方で、コードを書き足しながらいろいろ試していたため、ある時点でこんな状態になった。
map "/foo" do run App_foo.new end run App.new
これはrackup用のスクリプトで、最初はrun App.newだったところにmapを足したもの。意図は/fooへのアクセスをApp_fooで処理させるコードを足したつもりだった。結果は以下のようになる。
$ rackup test.ru
.../gems/rack-0.4.0/lib/rack/builder.rb:49:in `to_app': undefined method `call' for {"/foo"=>#}:Hash (NoMethodError)
mapはアプリケーションのリストの最後にハッシュを入れて、そこでURLと振り先のアプリケーションとの対応付けを作る。一方runはアプリケーションのリストの最後に入れる。rackupはこのリストに対してto_appを送り、内部のオブジェクト構造を作る。その際、リストの最後にあるハッシュについてだけは特別な扱いをしていて、URLMapオブジェクト(アプリケーションと呼ぶべき?)に変換する。しかし中間にあるハッシュについてはケアされないため上のような例外が発生する。正しくはAppに対してもmapを使う。
map "/foo" do run App_foo.new end map "/" do run App.new end
もう一つのとまどいはRack::CommonLoggerの動作で、ちょうど上のコードを与えたときにログに記述録されるアクセス先のURLが空になってしまうことがある。
$ rackup test.ru [2008-09-19 11:36:25] INFO WEBrick 1.3.1 [2008-09-19 11:36:25] INFO ruby 1.8.7 (2008-08-11) [i486-linux] [2008-09-19 11:36:30] INFO WEBrick::HTTPServer#start: pid=25843 port=9292 127.0.0.1 - - [19/Sep/2008 11:36:33] "GET / HTTP/1.1" 200 3 0.0007 127.0.0.1 - - [19/Sep/2008 11:36:35] "GET HTTP/1.1" 200 7 0.0007
具体的には/以外のマップへのアクセスの際のログについてベース部分、つまりmapの引数に指定した部分がログの上では消えてしまう。アプリケーションはきちんと動くのですごく困るわけではなかったのだけど、Rackのコードを見るとこの部分(PATH_INFOの内容が出力されている)を意図して削っているため悩んでしまった。結果からいうとRack::Recursiveを使えばこの問題を回避できるようだ。Rack::Recursiveは内部回送をする際に使うものなので、今回は必要ないと思うのだが、このようなわけで入れておくことにした。
ところで、このPATH_INFOを削っているのが、実は、よくできているところの一つだと後で気付いた。というのは上のコードをCGIスクリプトとして実行した場合などでもCGIのPATH_INFOを見て調整してくれるようで、たとえば/cgi-bin/test.ru/fooにアクセスするとちゃんとApp_fooが応答する。
ちょっと困ったのはgonzui-serverが生成するリンクが絶対パスになっていること。gonzuircの:base_mount_pointの値(デフォルトは/)を使って生成されている。Rackにgonzuiを載せるときにもこの値を参照しておいたほうが良さそうなのだけど、WEBrickやMongrelとCGIやFastCGIで、このあたりの関係がややこしいことになる。
たとえば:base_mount_pointを/codesearchとしたとする。導かれる絶対パスは/codesearchとなり、下位のサーブレットへのパスは/codesearch/statなどとなる。このときWEBrickなどでは絶対パスでURLMapを作っておけば特に問題はない。CGIなどでは一般に/cgi-bin/gonzui.cgiがアプリケーションのトップになり、下位サーブレットへは/cgi-bin/gonzui.cgi/statなどでアクセスさせることになる。この場合、HTTPサーバのエイリアス機能によって/cgi-bin/gonzui.cgiを/codesearchに対応付けておかなければならないものの、ひとまずは問題ない。
ここでpublic_htmlに置くことを考える。こういう状況では前述のようなエイリアスを設定するのは難しいことが少なくない。この場合、:base_mount_pointを設定しないでおけばよさそうに思えるのだが、デフォルト値が/であるため/~foo/gonzui.cgiによって生成されるリンクがたとえば/statなどとなってしまう。では:base_mount_pointを/~foo/gonzui.cgiとするとどうか、というと、今度はURLMap側で問題が起きる。前述のように絶対パスでURLMapを作っている場合、この指定は/~foo/gonzui.cgi/~foo/gonzui.cgiがトップであることになってしまう。となるとURLMapを作るときに:base_mount_pointを参照しなければ良さそうなのだが、そうすると今度はWEBrickやMongrelでしぶいことになる。
そんなようなわけで、しょうがないのでadhocに:base_mount_pointを変更して動作するようなwrapperにした。
最後に、CGIやFastCGIでのRackの運用方法がもう一つよくわからなかった。FastCGIについては外部プロセスとして起動してしまうということはもちろん考えられるのだが、動的運用ではどうすると良いのか。rackupを実行するシェルスクリプトを置いておけば動くことは動く。でもなんとなく釈然としない。検索などしてもうまくヒットさせられなかったので、ひとまずはgist:28033のような構成とした。
gonzui-rack.rbの中ではRack::Builderを使ってmapなどしている。Rack::Builderはrackupの中でも使われているが、これは(他のRackミドルウェアと同様に)ネストできるので特に問題ないようだ。
ちなみにabでちょっとだけ比較してみたところ、こんな感じだった。まあ、あまり意味はないかも。
type conc Reqs per Sec Time per Req Time per Req (all) ------------ ---- ------------ ------------ ------------------ WEBrick 1 52.71 18.972[ms] 18.972[ms] WEBrick 3 46.95 63.902[ms] 21.301[ms] Rack/WEBrick 1 54.13 18.475[ms] 18.475[ms] Rack/WEBrick 3 45.85 65.425[ms] 21.808[ms] Rack/CGI 1 3.43 291.723[ms] 291.723[ms] Rack/CGI 3 6.37 470.852[ms] 156.951[ms] Rack/FCGI 1 50.93 19.635[ms] 19.635[ms] Rack/FCGI 3 95.80 31.316[ms] 10.439[ms]
URI::Parser
URIモジュールをいじくって、RFC的にはダメなんだけどちょっとだけ大目に見てよ、というようなことが出来るようにしてみた。というのは、その種のリクエストを何度か受け取ったことがあるため。リクエストというよりも、バグ報告として受け取ることが多かったと思うけど。
>> p = URI::Parser.new(:ESCAPED=>"(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
=> #<URI::Parser:0xb7872640>
>> u = p.parse("http://foo.bar.baz/%uABCD")
=> #<URI::HTTP:0xb78cf4f8 URL:http://foo.bar.baz/%uABCD>
>> URI.parse(u.to_s)
URI::InvalidURIError: bad URI(is not URI?): http://foo.bar.baz/%uABCD
from /.../lib/uri/common.rb:126:in `split'
from /.../lib/uri/common.rb:144:in `parse'
from /.../lib/uri/common.rb:592:in `parse'
from (irb):3
from /usr/bin/irb1.9:12:in `<main>'
>> s = "http://foo.bar.baz/ABCD"
=> "http://foo.bar.baz/ABCD"
>> u1 = p.parse(s)
=> #<URI::HTTP:0xb78c3220 URL:http://foo.bar.baz/ABCD>
>> u2 = URI.parse(s)
=> #<URI::HTTP:0xb78b6d54 URL:http://foo.bar.baz/ABCD>
>> u1 == u2
=> true
>> u1.eql?(u2)
=> false
あとでruby-devに投げてみよう。
Ruby/GD2でGIFアニメ
用事があってGIFのフォーマットだとかGIFアニメのことだとかを調べている。特許の話とかでてきて懐かしい。
GIFアニメについてはlibgd2の2.0.29で対応したようだ。が、言語バインディングではまだあまり対応していない様子。まだ、というか、libgd2の言語バインディングはあまりアクティブでないような感じもある。Ruby/GDもそんななかの一つ。今からコードを足すのもどうかなというところ。
そう思っていたらRuby/GD2という別の実装があることに気付いた。こちらもアクティブではないようだが、Ruby/DLで書かれているためちょっとだけ手を入れやすい。そういうわけで、関数をいくつか足し、Rubyレベルでのメソッドを三つほど足してGIFアニメを出力できるようにしてみた(差分)。
require 'gd2'
files = Dir.glob('/path/to/images/*.png')
prev = nil
open('/path/to/output.gif', 'w') do |io|
img = nil
files.each do |f|
img = GD2::Image.import(f, :format => :png)
img.gifanim_begin(io) if prev.nil?
img.gifanim_add(io, 50, prev, true)
prev = img
end
img.gifanim_end(io)
end
使っているlibgd2は2.0.36-RC1なのだけど、このバージョンのGIFアニメ機能にはバグがあるようだ。libgd2では、GIFアニメを構成するイメージの間での変更部分を抽出しようとするのだが、サイズが異なるイメージが与えられると、その処理の中でSEGVを起こす。ソース中のコメントには内部で調整するというようなことになっているのに、実際にはそういうことをやっているところがない。そのため、前述したメソッドでは前フレームのサイズに変換してしまうことにした。ま、そろえておけよっていうことではあるのだろうと思うのだけど。
追記(2008-10-04): 渡辺哲也さんの日記を見てパッチを置いていなかったことに(今になって)気付いた。それとも消してしまったんだったかな。
Rails 2.0.2とRuby 1.8.7のString#respond_to?
ところが(なのかな?)Rails 2.0.2にも同じバグがあって、これも顕在化した。よって、次のようになるのは1.8.7を1.8.7-p17にしても変わりない。
$ ./script/console
Loading development environment (Rails 2.0.2)
>> "" + ActiveSupport::Multibyte::Chars.new("")
ArgumentError: wrong number of arguments (2 for 1)
from (irb):1:in `respond_to?'
from (irb):1:in `+'
from (irb):1
>>
つまりこれはActiveSupport::Multibyte::Charsで定義されているrespond_to?が引数を一つしか取らないからで、これを二つ取るようにすればよい。というわけで、config/initializers/fix_multibyte_chars_respond_to.rbというようなファイルを以下の内容で作ることで回避できるような気がする。
if defined?(ActiveSupport) &&
defined?(ActiveSupport::Multibyte) &&
defined?(ActiveSupport::Multibyte::Chars)
mc = ActiveSupport::Multibyte::Chars.new("")
begin
"" + mc
rescue ArgumentError
raise unless mc.method(:respond_to?).arity == 1
class ActiveSupport::Multibyte::Chars
def respond_to?(method, ip = false)
super || @string.respond_to?(method, ip) || handler.respond_to?(method, ip) ||
(method.to_s =~ /(.*)!/ && handler.respond_to?($1, ip)) || false
end
end
end
end
Ruby Enterprise Edition 1.8.6-20080507をbuildしてみた
Ruby Enterprise Editionをbuildしてみた。
tarballをとってきて、展開して、installスクリプトを実行すれば良い、はずなのだが、手元の環境ではうまくいかなかった。どうやら(環境によって)google-perftools-0.97に含まれるlibtcmallocを使おうとして、先にインストールし、それを参照させるべくccにインクルードパスやライブラリパスを伝えようとしているようだ。ライブラリパスについてはRuby本体のbuildのときに-Lなどを使って指定しているのだが、インクルードパスにいついては環境変数C_INCLUDE_PATHやCPLUS_INCLUDE_PATHに値を設定することで指定しようとしている。で、このC_INCLUDE_PATHなどへの設定内容がどうもまずい。実環境に合っていないと必要なヘッダファイルなどを見付けられないことになる。
/bin/sh ./libtool --tag=CXX --mode=compile g++ -DHAVE_CONFIG_H -I. -I. \
-I./src -I./src -pthread -DNDEBUG -Wall -Wwrite-strings \
-Woverloaded-virtual -Wno-sign-compare -g -O2 -c -o \
libtcmalloc_minimal_la-system-alloc.lo `test -f 'src/system-alloc.cc' || \
echo './'`src/system-alloc.cc g++ -DHAVE_CONFIG_H -I. -I. -I./src -I./src \
-pthread -DNDEBUG -Wall -Wwrite-strings -Woverloaded-virtual \
-Wno-sign-compare -g -O2 -c src/system-alloc.cc -fPIC -DPIC -o \
.libs/libtcmalloc_minimal_la-system-alloc.o
In file included from /usr/include/c++/4.3/bits/postypes.h:47,
from /usr/include/c++/4.3/bits/char_traits.h:47,
from /usr/include/c++/4.3/string:47,
from ./src/base/commandlineflags.h:52,
from src/base/logging.h:48,
from src/system-alloc.cc:51:
/usr/include/c++/4.3/cwchar:52:24: error: wchar.h: No such file or directory
なんだ? と思って問題が起きているsource/vendor/google-perftools-0.97でmakeをたたくと処理がもう少し進んでしまったりもするのだが、実はconfigure中に同じエラーが起きているため、しばらくするとまたコケる。
installスクリプトの中でやっていることをよく見てみると、インクルードパスを指定する必要はどうもなさそうに思える。というわけで、以下の変更を加えてinstallスクリプトを動かしてみたところbuildが異常終了しなくなった。
--- ruby-enterprise-1.8.6-20080507.orig/installer.rb 2008-05-26 17:51:36.000000000 +0900
+++ ruby-enterprise-1.8.6-20080507/installer.rb 2008-06-12 12:06:48.000000000 +0900
@@ -123,8 +123,10 @@
File.open("source/.prefix.txt", "w") do |f|
f.write(@prefix)
end
+=begin
ENV['C_INCLUDE_PATH'] = "#{@prefix}/include:/usr/include:/usr/local/include:#{ENV['C_INCLUDE_PATH']}"
ENV['CPLUS_INCLUDE_PATH'] = "#{@prefix}/include:/usr/include:/usr/local/include:#{ENV['CPLUS_INCLUDE_PATH']}"
+=end
ENV['LD_LIBRARY_PATH'] = "#{@prefix}/lib:#{ENV['LD_LIBRARY_PATH']}"
end
installスクリプトによらず、source/vendor/google-perftools-0.97でconfigure --prefix=<インストールパス> --disable-dependency-trackingを実行の上、make libtcmalloc_minimal.la && mkdir -p <インストールパス>/lib && cp .libs/libtcmalloc_minimal.so* <インストールパス>/lib/としておくというのでも回避できるだろうと思う(その後でinstallスクリプトを実行する)。
これでPassengerを動かしてみたいところだが、また後で。
$ ./ruby -v ruby 1.8.6 (2008-03-03 patchlevel 114) [i686-linux] $ ./ruby -e 'p GC.copy_on_write_friendly?' true
Rails 2.0.2とRuby 1.8.7のString#chars
Ruby 1.8.7からはString#charsが定義されるようになった。これがRails 2.0.2なんかではまずくって、たとえば"hello".firstでNoMethodError: undefined method `[]' for #<Enumerable::Enumerator:0xb6f3f5b4>などとなる。これはRailsのように積極的に組み込みクラスを改変しているものの定めといえる。
Rails 2.1.0を見てみると以下のようになっていて、Ruby 1.8.7のString#charsを削除し、Rails(ActiveSupport)が定義しているcharsを使おうとしている。
module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module String #:nodoc:
unless '1.9'.respond_to?(:force_encoding)
# Define methods for handling unicode data.
module Unicode
def self.append_features(base)
if '1.8.7'.respond_to?(:chars)
base.class_eval { remove_method :chars }
end
super
end
[...]
Rails 2.0.2でも同じようにするには、Rails側に手を入れたほうがてっとり早いとは思うのだが、ひとまずそれはしないことにするならばconfig/initializers/remove_string_chars.rbを以下の内容で作っておくというやり方はどうだろう。
unless '1.9'.respond_to?(:force_encoding)
String.class_eval do
begin
remove_method :chars
rescue NameError
# OK
end
end
end
script/consoleやscript/server(アプリケーションはTracks-1.6)で簡単な動作確認をしてみたところ、ひとまず前述の例外を回避できているようだ(Ruby 1.8.7、1.8.7-p17および1.8.6で確認した)。ただしそれ以外の環境やアプリケーションでどうなるかは確認していない。

