QA@IT

Unicornがたまにconfig/initializers/constants.rbを読み込まなくなる問題だと思っていたら、そうではなく、rails-i18n gemのja.ymlをうまく読み込めていなかった問題

5998 PV

状況

  • Unicorn
  • nginx
  • Rails 3.2.13
  • Ruby 2.0.0-p0
  • rails-i18n

でWebアプリケーションを稼働させています。基本的に

で説明された設定を行っています。

アプリケーション内共通の定数をconfig/initializers/constants.rbに入れています。

MUSIC_UPLOAD_SUM_LIMIT_BYTE =  52428800

というようになっています。
デプロイして2日ほど(総アクセス数400前後)は問題ありませんでした。

が、

3日目に問題が発生しました。

問題の内容

http://denkinovel.com/tutorials/how_to_utilize_audios
にアクセスしたところ、

 (ActionView::Template::Error) 
no implicit conversion of nil into String
  actionpack (3.2.13) lib/action_view/helpers/number_helper.rb:355:in `escape'

というエラーが発生しました。exception_notificationでエラー発生時にメールを送るようにしていたので、感知することができました。

問題発生箇所を探ると、viewの、

<%= number_to_human_size(MUSIC_UPLOAD_SUM_LIMIT_BYTE, presicion: 2) %>

となっている部分で、MUSIC_UPLOAD_SUM_LIMIT_BYTEがnilになってしまったのが原因。だと思いました。(以下追記)でも実際は違いました。

エラーメッセージを読むと、

  actionpack (3.2.13) lib/action_view/helpers/number_helper.rb:355:in `escape'
  actionpack (3.2.13) lib/action_view/helpers/number_helper.rb:355:in `number_with_precision'
  actionpack (3.2.13) lib/action_view/helpers/number_helper.rb:449:in `number_to_human_size'

となっています。エラーが発生しているのはRailsのaction_viewの内部です。そこのコードを読むと、原因がわかってきました。

https://github.com/rails/rails/blob/v3.2.13/actionpack/lib/action_view/helpers/number_helper.rb

の355行目。

escaped_separator = Regexp.escape(options[:separator])

で、options[:separator]がnilになっていたのです。

number_helper.rbのコードをすこし遡って、

defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})

で、localeファイルからseparatorの設定を取ってきています。options[:separator]にデフォルトの値を入れる処理はここのようです。

rails-i18n というgem( https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml )をlocale設定に使っているのですが、そこのja.number.format.separatorの値が取れていなかったのが原因のようです。

ではどうしてja.number.format.separatorの値が取れないのか?

それが謎です。(以上追記)

問題の不思議な点

http://denkinovel.com/tutorials/how_to_utilize_audios でリロードすると、エラーが発生するときと、エラーが発生せずに正常にレンダリングされる場合があります。エラー発生の割合は半々くらいです。

Unicornを再起動すると、いったんはエラーが発生しなくなるのですが、数日経つとまたエラーが発生するようになります。

なお、2013年5月15日現在、http://denkinovel.com/tutorials/how_to_utilize_audios では修正を加え

<%= number_to_human_size(MUSIC_UPLOAD_SUM_LIMIT_BYTE, presicion: 2, separator: ".") %>

と、separatorを指定しています。

エラー発生の原因がわからず、悩んでいます。どうか、助けていただけませんでしょうか。

設定ファイル

working_directory "<%= current_path %>"
pid "<%= unicorn_pid %>"
stderr_path "<%= unicorn_log %>"
stdout_path "<%= unicorn_log %>"

listen "/tmp/unicorn.<%= application %>.sock"
worker_processes <%= unicorn_workers %>
timeout 30

preload_app false

before_fork do |server, worker|
  # Disconnect since the database connection will not carry over
  if defined? ActiveRecord::Base
    ActiveRecord::Base.connection.disconnect!
  end

  # Quit the old unicorn process
  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # Start up the database connection again in the worker
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
  child_pid = server.config[:pid].sub(".pid", ".#{worker.nr}.pid")
  system("echo #{Process.pid} > #{child_pid}")
end

before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = "<%= current_path %>/Gemfile"
end
upstream unicorn {
  server unix:/tmp/unicorn.<%= application %>.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  # server_name example.com;
  root <%= current_path %>/public;

  if (-f $document_root/system/maintenance.html) {
  return 503;
  }
  error_page 503 @maintenance;
  location @maintenance {
  rewrite  ^(.*)$  /system/maintenance.html last;
  break;
  }

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}

回答

エラーがuninitialized constantならUnicornのpreload_app false設定とかinitializerから出してapplication.rbからrequire Rails.root.join('config/constants')するとか試せるんですが、nilになってるって変ですね。。

MUSIC_UPLOAD_SUM_LIMIT_BYTEに実際nilが代入されてしまうバグがどこかに混入しているということはないですか?

編集 履歴 (0)
  • おおおありがとうございます……!
    ぼくの勘違いがありました。
    MUSIC_UPLOAD_SUM_LIMIT_BYTEがnilになっていたのではありませんでした。
    エラーメッセージをちゃんと読むと、
    -
  • number_to_human_sizeメソッドで使われるseparatorがnilになっていたのがエラーの原因でした。質問文を編集し、そのことを書いています。「たしかにuninitialized constantにならないのは変だなー」と思い、エラーメッセージを読み直したらわかったことです。ありがとうございました! -
ウォッチ

この質問への回答やコメントをメールでお知らせします。