経緯
Vagrant で立てた VM を provision する際に Chef を用いて cpanm --installdeps .
を実行して依存する CPAN モジュールをインストールする Recipe を書いて実行するとたびたび NoMemoryError で Chef を実行するプロセスが殺されてしまう。
まず、デフォルトのログ出力レベルだと不親切すぎるので、`VAGRANT_LOG=debug vagrant provision` などとして詳細なログ出力を確認したほうがよい。 (参考: Vagrant Documentation)
NoMemoryError と言われてプロセスが殺されるのだからまず VM に割り当てるメモリを増やしてみることにする。これまで 512MB だったので増やしてみるが、1024MB でも 2048MB でも NoMemoryError が送出される。
cpanm --installdeps .
が 2GB ちかくもメモリを食い潰すのはおかしい。
Mixlib::Shellout
ところで、Chef は execute resource などで実行されるコマンドの終了ステータスが非ゼロの場合、stderr と stdout の内容を出力するようになっている。
- Mixlib::Shellout#format_for_exception
- Mixlib::Shellout::Unix#run_command
- Mixlib::Shellout::Unix#read_stdout_to_buffer
IO.select
して出力をバッファ *1 に溜めて、必要なときに出力するようになっている。
Module::AutoInstall
Module::AutoInstall は依存するモジュールを自動的に入れようとする Module::Install のプラグインだが、入力を与えないとずっとプロンプトが出て入力を求めてくる (PERL_AUTOINSTALL 環境変数に値を与えてもうまくいかなかった)。
これはタイムアウトするまで行われるので stderr への大量の出力が行われることになる。
(また < /dev/null
として標準入力を渡すこともできなかった)
推測
あるプロセスから大量の出力がある場合、かつそのプロセスが (いかなるステータスにせよ) 終了するまでの時間が長い場合、Mixlib::Shellout のバッファがなかなか flush されないのでバッファが肥大化しうる。
cpanm --installdeps .
はまさにそのような条件を再現しやすいのではないか。
ruby-prof を用いれば高級なプロファイリングが行えそうではあるが、Chef とどのように組み合わせたものかすこし悩ましいのでひとまず推測にとどめる。
対策
--quiet
オプションを cpanm
に渡す。こうすることで stderr あるいは stdout への出力が抑制される。
ログ自体は $HOME/.cpanm/build.log に変わらず出力されるのでデバッグが困難になることは少なさそう。
結論
対話的入力つらい。