サーバープロセスにおけるRDフィルタ処理の同時実行問題

昨日から一時的に、記事とあまり関係なさそうな画像や他の記事の文章が脈絡もなく挿入されてカオスなことになる場合があった。アクセスログを見るには、たぶん、私以外の閲覧者の誰かがこの問題に遭遇している。うーん、お恥ずかしい。

これは自家製ブログシステム(rhianoletheと呼んでいる)のバグで、現在は解決している。サーバープロセスでRDtoolを用いてRDを整形するときにfilterの処理がプロセス間で衝突する問題だった。

事象

私は RD が好きだ。というわけで、このブログの原稿はRDで入力して、HTMLに変換している。ただし、古い記事の中にはHTMLでベタうちしてあるものもあるので、それらをRDtool経由で処理するためにRD::INCLUDE_FILTERを使ったりしている。また、幾つかの特殊なfilterを独自定義してRDの拡張として利用している。

ここで、 RDtool はfilterを処理するために一時ファイルを生成する。一時ファイルのファイル名にはプロセスIDを含めることで一意性を確保しているのだけれども、確保できていなかった。 rdblockparser.tab.rb から該当部分を抜粋すると、こんな感じ。

class RDParser < Racc::Parser
module_eval <<'..end lib/rd/rdblockparser.ry modeval..idc4b57748f0', 'lib/rd/rdblockparser.ry', 231
include ParserUtility

TMPFILE = ["rdtmp", $$, 0]

....

      else # if output is target formated
        basename = TMPFILE.join('.')
        TMPFILE[-1] += 1
        tmpfile = open(@tree.tmp_dir + "/" + basename + ".#{@in_part}", "w")
        tmpfile.print(part_out)
        tmpfile.close
        subtree = parse_subtree(["=begin\n", "<<< #{basename}\n", "=end\n"])
      end
      .....

TMPFILE 内に埋め込まれた $$ の値は TMPFILE 定義時に固定される。従って、 rdblockparser.tab.rb を読み込んだ後にプロセスがforkして、かつそれらの子プロセスが並行して動くと一時ファイル名が衝突することがあり得る。こうして、子プロセス間でfilterの処理結果が上書きし合う結果になる。

影響される製品

この問題はRDtook付属の rd2 コマンドでは発生しないと考えられる。基本的には、 rd2 コマンド実行中にforkが発生することはないと思われるためだ。一方、rhianoletheや Redmine rd formatter のようにサーバープロセス中でRD parserを呼んでいる場合は問題になり得る。幸いなことに、redmine_rd_formatterやその原型である Redmine for ruby はfilterを使用していないので、バグは発現しない。

対応

この問題が、RD parserのバグなのかどうかは分からない。現在、作者のmoonwolfさんに問い合わせている。

とりあえずRD parserを使用する側で RDTree#tmpdir をプロセスごとに一意になるように設定して対応した。もしかすると、プロセスがforkする場合はRD parserはこう使うのが本当で、RD parserのバグではないのかもしない。