Hatena::ブログ(Diary)

Lazy Technology

2008-02-12

[]リンクするaprのバージョンを0.9xから1.2xに

WebDAV(=Apache)でリポジトリにアクセスすると、500が返ってきてログにも何も残らないと言う現象に遭遇する。で、色々調べていくうちのSubversionApacheのaprのバージョンの不整合が原因と言う事が分かった。

なお、Apache HTTP Server 2.0系で利用されているAPR 0.9系と、Apache HTTP Server 2.2系で利用されているAPR 1.2系では、互換性がないことに注意されたい。

http://builder.japan.zdnet.com/news/story/0,3800079086,20361867,00.htm

トラブルシューティングの手順を残しておく。


まず、Apacheのログに何も残っていないので、telnetから直接WebDAVのリクエストを投げる。参考: Subversionによるバージョン管理 (2/3):Apache 2.0でWebDAV(後編) - @IT

$ telnet localhost 80
OPTIONS /svn/ HTTP/1.1
Host: hostname
Authorization: Basic ===============

すると、xmlでエラーの詳細が返ってくる。

HTTP/1.1 500 Internal Server Error
Date: Fri, 08 Feb 2008 02:43:28 GMT
Server: Apache
Content-Length: 290
Connection: close
Content-Type: text/xml; charset="utf-8"

<?xml version="1.0" encoding="utf-8"?>
<D:error xmlns:D="DAV:" xmlns:m="http://apache.org/dav/xmlns" xmlns:C="svn:">
<C:error/>
<m:human-readable errcode="22">
Can't set position pointer in file '/path/to/repos/db/revs/2406': Invalid argument
</m:human-readable>
</D:error>
Connection closed by foreign host.

ポイントは以下。

Can't set position pointer in file '/path/to/repos/db/revs/2406': Invalid argument

これでググる401 Unauthorizedでaprのバージョンが原因と書いてある。

Subversionはそのままだと同梱のAPR(0.9.7)を使ってしまうので、Apache 2.2.0に同梱のAPR(1.0)を使わせるため「--with-apr」と「--with-apr-util」を指定する。

指定しなくてもコンパイルもできてApacheもしっかり起動するが、アクセスするときに

Can't set position pointer in file '/var/lib/svn-repos/db/revs/0': Invalid argument

みたいなこと言われてハマる

http://saikyoline.jp/wiki/index.php?%A5%E1%A5%E2%2FWebDAV%A4%C7Subversion


aprのバージョンを確認するにはlddを使う。まずはSubversion(mod_dav_svn)。

$ ldd /usr/local/apache2/modules/mod_dav_svn.so
        libaprutil-0.so.0 => /usr/lib/libaprutil-0.so.0 (0x008f9000)
        libapr-0.so.0 => /usr/lib/libapr-0.so.0 (0x00270000)
        (snip)

$ ll /usr/lib/libapr*.0
lrwxrwxrwx  1 root root 18  1月 25 14:57 /usr/lib/libapr-0.so.0 -> libapr-0.so.0.9.17
lrwxrwxrwx  1 root root 22  1月 25 14:57 /usr/lib/libaprutil-0.so.0 -> libaprutil-0.so.0.9.17


恐らく0.9.17。次にApache

$ ldd /usr/local/apache2/bin/httpd
        libaprutil-1.so.0 => /usr/local/apache228/lib/libaprutil-1.so.0 (0x0078a000)
        libapr-1.so.0 => /usr/local/apache228/lib/libapr-1.so.0 (0x00b5d000)


$ ll /usr/local/apache228/lib/libapr*.0
lrwxrwxrwx  1 root root 18  2月  6 16:03 /usr/local/apache228/lib/libapr-1.so.0 -> libapr-1.so.0.2.12
lrwxrwxrwx  1 root root 22  2月  6 16:03 /usr/local/apache228/lib/libaprutil-1.so.0 -> libaprutil-1.so.0.2.12


恐らく1.2.12。確かにバージョンが違う。

と言うわけで、再々度Subversionコンパイル。この際なので色々オプションを調べて自分なりのconfigureを作る事に。

./configure \
--with-apr=/usr/local/apache2 \
--with-apr-util=/usr/local/apache2 \
--with-apxs=/usr/local/apache2/bin/apxs \
--with-swig=/usr/local/bin/swig \
--with-neon=/usr/local/ \
PERL=/usr/local/bin/perl \
PYTHON=/usr/local/bin/python \
RUBY=/usr/local/bin/ruby \
--with-ssl \
--without-berkeley-db

これでインストールしたらWebDAVで無事アクセスできた。いい加減これで全部かなー。

2007-04-29

[][][]ARの実装とRuby処理系のTimeに関する実装でハマる

ActiveRecordSQLServerからMySQLにデータを移行するスクリプトを書いているときに躓く。

正直全部書ききれないので端折って結論だけ書いてしまう。

Windows処理系においてTime.localメソッドはGMTとの時刻差を前提に入れて書いておかないとハマる。

これ一つで半日くらい使ってしまった…。

具体的に言うと、Windowsruby 1.8.5の環境では

>Time.local(1970,1,1)

ArgumentError: time out of range

>Time.local(1970,1,1,9)

=> Thu Jan 01 09:00:00 +0900 1970

と言う結果になる。

RHEL4の環境で同じように試してみると

> Time.local(1970,1,1)

=> Thu Jan 01 00:00:00 +0900 1970

> Time.local(1970,1,1,9)

=> Thu Jan 01 09:00:00 +0900 1970

と言う結果に。


Rubyリファレンスマニュアルを見ると

Time オブジェクトは時刻を起算時からの経過秒数で保持しています。起算時は協定世界時(UTC、もしくはその旧称から GMT とも表記されます) の 1970年1月1日午前0時です。なお、うるう秒を勘定するかどうかはシステムによります。

http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=Time

と書かれているので、恐らくWindows処理系では先にGMTによる内部補正を実行してからシステム時間を取得(?)していると考えられる。RHEL4の方だと先にシステム時間を取ってきてから後でGMTの時刻を補正しているんだろう。超適当な推測…。

中身を見てないからWindows側の問題なのかRuby処理系側の問題なのかなんとも言えないけど、使う側から見るとRHEL4のように動いて欲しい。


で、なんでARの処理系なのかと言うと

ActiveRecordではDBMSを意識しないでデータを扱えるように各DBMS毎にラッパ(アダプタ?)が合る。

\lib\active_record\connection_adapters以下にあるファイル達がそれ。

SQLServer用のアダプタはsqlserver_adapter.rbである。

んで、この中で日付型の変換ロジックがある。

      def cast_to_time(value)
        return value if value.is_a?(Time)
        time_array = ParseDate.parsedate(value)
        Time.send(Base.default_timezone, *time_array) rescue nil
      end

SQLServerから取ってきた日付型の各要素を配列に分割してタイムゾーンに合わせてTimeクラスのメソッドを呼んでいる。

特に何もしていなければたぶんTime.localメソッドが実行される。

この時にWindowsだとかGMTだとかをまったく考慮していないので(当たり前だけど)、例えばSQLSever側に「1970-01-01 08:59:59」と言うようなデータがあるとTime.local(1970,1,1,8,59,59)のような形でメソッドが実行される。

この時にさっき書いたGMTの問題で、Windows系だと例外が起こる。

結果、nilが返される。


混乱に拍車をかけるARの実行結果

ARでカラムの値を取ってくる方法はAR#カラム名の他に、AR#attributesって言う方法もある。

取ってきたレコードの全部のカラムに共通処理(文字コード変換とか)を加えたい場合にイテレータっぽく使ってた。

今回のエラーが起こった時、この二つのメソッドでそれぞれ実行結果が異なると言うなんとも怪しい現象が。

>hoge = SQLServer.find_by_sql(sql)

>pp hoge

#<SQLServer:0x3b5e228

@attributes=

{"create_date"=>"1970/01/01 08:59:59"


>pp hoge[0].attributes

{"create_date"=>nil,


>pp hoge[0].create_date

nil


>pp hoge[0].create_date

=> #<DateTime: 210866792399/86400,0,2299161>


>pp hoge[0].create_date.to_s

=> "1970-01-01T08:59:59Z"


もう怪しさ100%。

特に怪しいのは、pp hogeとpp hoge[0].attributesで実行結果が違うこと、hoge[0].create_dateの一回目と二回目で実行結果が変わる事だろう。

前者だけ簡単に調べた。

hoge[0].attributesメソッドは単にArrayを返すだけでなく、上記の日付型変換処理を行っている。

変換処理をしない状態の値が欲しい場合用にAR#attributes_before_type_castと言うメソッドがあった。


後者については正直よく分からず…。method_missingを拾っているのだけれど、そこからの処理内容がよくわからじ。

時間があるときに調べたい。


結論

  • rescue nilは止めよう
  • 処理系による違いを意識しよう
  • 今回の件でActiveRecordの印象ややダウン