pockestrap

Programmer's memo

Ruby 2.6でunicornがrestartしない

あるRailsアプリケーションで使用しているRubyを2.5から2.6に上げたところ、unicornがrestartしてくれなくてハマった。 その時の問題と解決方法をメモ。

エラー内容

restartに失敗しているunicornのerror logを見てみると、次のようなエラーが出力されていた。

/path/to/config/unicorn/production.rb:29:in `block in reload': undefined method `chomp' for nil:NilClass (NoMethodError)

このconfig/unicorn/production.rbは次のようになっていた。

# snip

before_exec do |server|
  # snip

  env = ENV.to_hash
  %w(RUBYLIB RUBYOPT GEM_HOME).each do |key|
    env.delete(key)
  end
  rubylib = IO.popen([env, 'bundle', 'exec', 'env', unsetenv_others: true], &:read).slice(/^RUBYLIB=(.+)$/, 1).chomp
  ENV['RUBYLIB'] = rubylib
end

エラーが起きている29行目は、このコード片の下から3行目、IO.popenの結果をchompしているところである。

このコードは次のブログ記事のものを利用している。

eagletmt.hateblo.jp

エラーの原因

このエラーはRuby 2.6からBundlerがdefault gemとしてRuby本体に添付されたことに起因する。1

元の記事にもあるとおり、BundlerはRUBYLIB環境変数を使用して自身のコードを子プロセスのRubyに読ませようとする。 ところがRuby 2.6からはBundlerがdefault gemとして添付されたため、それを使う限りはRUBYLIB環境変数をセットしなくても子プロセスのRubyからBundlerのコードを読めるようになった。 そのため、BundlerはRUBYLIB環境変数をセットしないようになった。

先に示したコードではslice(/^RUBYLIB=(.+)$/, 1)を実行しているが、これはRUBYLIB環境変数が存在しない時にはnilを返す。 そのためこのコードでエラーが起きてしまっていた。

解決方法

次のようなパッチを当てた。

-  rubylib = IO.popen([env, 'bundle', 'exec', 'env', unsetenv_others: true], &:read).slice(/^RUBYLIB=(.+)$/, 1).chomp
-  ENV['RUBYLIB'] = rubylib
+  rubylib = IO.popen([env, 'bundle', 'exec', 'env', unsetenv_others: true], &:read).slice(/^RUBYLIB=(.+)$/, 1)&.chomp
+  ENV['RUBYLIB'] = rubylib if rubylib

String#chompの呼び出しにSafe Navigation Operatorを使用し、またRUBYLIB環境変数RUBYLIB環境変数がセットされている場合にのみ上書きするように変更した。

単にこのコードを削除することでも解決はするが、そうするとgem install bundlerした場合にうまく動かないように思えたので、今回の対応を行った。2


  1. Bundlerのコードを読んだわけではないので推測だが、おそらく正しい推測だと思う。

  2. 実はバッサリ消してしまっても良いかもしれない。自信はない。