MSYSのrxvtやCygwinでVagrantを実行した際に出力されるNIO4RのDLLエラーについて
Vagrantに内包されているMSYSのrxvtやCygwinで、Vagrantのコマンドを叩いた時に検出されたエラーについてメモしとく。
あらかじめ書いておくが、問題の回避はできたものの、根本的な原因の究明にまでは到っていない。
環境
VagrantはWindows版のパッケージを使っていて、vagrant-berkshelfはコマンドプロンプトからインストールしている。
事象
コマンドプロンプトからVagrantのコマンドを実行すると問題無く動いていたのだが、Vagrantに内包されているMSYSのrxvtや、Cygwinから実行すると以下のエラーが出力されて止まってしまった。
$ vagrant status Vagrant failed to initialize at a very early stage: The plugins failed to load properly. The error message given is shown below. 1114: ダイナミック リンク ライブラリ (DLL) 初期化ルーチンの実行に失敗しました。 - C:/Users/heroween/.vagrant.d/gems/gems/nio4r-1.0.0/lib/nio4r_ext.so
なんでかなーと原因を調べてみた。
原因を探る
まず、問題を起こしているNIO4Rについてだが、これはBerkshelfが依存しているGemであり、Vagrantのプラグインであるvagrant-berkshelfをインストールすると一緒に入ってくる。
このNIO4Rはクロスプラットフォームな作りとなっており、実行環境のRubyの処理系に応じて実装が変わる。
- MRI/YARV、Rubiniusの場合はlibevをベースとしてC言語で実装された拡張ライブラリが使われる。
- JRubyの場合はJava NIOをベースとしたJavaで実装された拡張ライブラリが使われる。
- 上記以外であればPure Ruby実装のライブラリが使われる。
と、READMEではこのように説明されている。
Pure Rubyだと処理速度が遅いので、CやJava実装の拡張ライブラリも使えるようになっているのだろう。
で、今回はVagrantに内包されている、Windows版Ruby(MRI)で実行したら問題が起きた。
上記の説明の通りであれば、MRIなのでC実装の拡張ライブラリが使われて、その際にDLLエラーが発生した…ものと思われるのだが、腑に落ちない点がある。
MSYSのrxvtやCygwinで出るエラーが、コマンドプロンプトでは出ずに、普通に動いているという点だ。
よく分からないので、実装の変わる条件がどうなっているのか、インストールされたNIO4Rのコード(nio.rb)を確認してみた。
if ENV["NIO4R_PURE"] || (ENV["OS"] =~ /Windows/i && !defined?(JRUBY_VERSION)) require 'nio/monitor' require 'nio/selector' NIO::ENGINE = 'select' else require 'nio4r_ext' if defined?(JRUBY_VERSION) require 'java' require 'jruby' org.nio4r.Nio4r.new.load(JRuby.runtime, false) NIO::ENGINE = 'java' else NIO::ENGINE = 'libev' end end
これを見ると、OSがWindowsだとJRubyで無い限りは、必ずPure RubyのNIO4Rが使われるようだ…。
コマンドプロンプトから実行した場合、エラーが出ずに正常動作するのは、C実装でなくPure RubyのNIO4Rが使われているからということになる。
それでは、MSYSのrxvtやCygwinで実行すると、何故、Pure RubyでなくC実装のNIO4Rが使われるのだろうか。
とりあえず、環境変数OSをコマンドプロンプトで確認してみると、値は以下のように表示された。
C:¥User¥heroween>echo %OS% Windows_NT
同じようにrxvtとCygwinでも確認すると、
$ echo $OS Windows_NT
やはり"Windows_NT"と表示される。
それでは、Vagrant実行時にどうなっているのかと、nio.rbに直接puts ENV["OS"]
と書き込んで実行したところ、
# コマンドプロンプト Windows_NT # MSYSのrxvt MINGW32_NT-6.1 # Cygwin CYGWIN_NT-6.1
と、ターミナル毎に異なった値が出力された。
そこで、VagrantのShell実装コマンドを見てみると、以下のように環境変数OSが上書きされていることが分かった。
# Determine the OS that we're on, which is used in some later checks. # It is very important we do this _before_ setting the PATH below # because uname dependencies can conflict on some platforms. OS=$(uname -s 2>/dev/null)
unameコマンドの値で環境変数OSから"Windows"が消えてしまったことで、C実装のNIO4Rが使われるようになっていた訳である。
ここまでで、コマンドプロンプトとMSYSのrxvtやCygwinで挙動が変わっていた原因は分かった。
それでは、DLLエラーを吐く根本的な原因はなんなのだろうか。
処理の実行に失敗しているnio4r_ext.soをobjdumpで確認してみると、
$ objdump -T nio4r_ext.so nio4r_ext.so: file format pei-i386 DYNAMIC SYMBOL TABLE: no symbols C:¥MinGW¥bin¥objdump.exe: nio4r_ext.so: not a dynamic object
と表示されたので、soファイル自体壊れている可能性もありそうなのだが、Windows環境とC言語に明るくない自分では、究明するまでにかなりハマりそうなので、ここで打ち切ることにした。
対応
対応として、環境変数NIO4R_PUREに真の値を設定しておけば、必ずPure RubyのNIO4Rを使うようになるので、この問題を回避することはできる。
nio.rbのコードを見ると分かるが、そもそも、OSがWindowsの場合はC実装のNIO4Rを使わないような条件になっているし、過去のIssueやPRを追ってみても、単純にサポートしてないだけのように思われる。
どうしてもWindowsでC実装のNIO4Rを使いたいという訳でも無いので、一先ずはここまでにしておく。