gem 'sprockets'について
seeetsファイルをコンパイルするライブラリ。
ただし、rails 5.1 からはjavascriptコンパイラにはwebpacker
が推奨され、rails 6.0からはjavascriptコンパイラのデフォルトがwebpacker
に変更されている。
現時点ではCSSやimageのコンパイルのデフォルトに使用されている。
CSS, image -> sprockets
javascript -> webpacker
現象
アセットプリコンパイルで実行結果が返ってこなくなります。
解決方法
1. 並行処理を無効にする設定を行う。
以下どちらかの設定をアプリケーションの設定で行うことで、並行処理を無効にし問題発生を回避します。
Rails.application.config.assets.export_concurrent = false
Sprockets.export_concurrent = false
2.sprocketsのバージョン3系を使用する
gem 'sprockets'のメンテナンスは遅く、修正まで時間がかかることが予想されるので3系を使うのも1つです。
詳細
Sprockets 4.0ではコンパイルのパフォーマンス向上のためにマルチスレッドで並行実行される様にな離ました。
See: https://github.com/rails/sprockets/pull/469
並行処理はConcurrent::Promise
を使用し、その後promises.each(&:wait!)
でwait!
しています。
promises = args.flatten.map do |path|
Concurrent::Promise.execute(executor: executor) do
environment.find_all_linked_assets(path) do |asset|
yield asset
end
end
end
promises.each(&:wait!)
このとき、wait!
メソッドでは処理が終わるまでtimeoutの時間待機するのですが、引数を指定していないためnil
が入り、永遠に待ち続けます。
def wait!(timeout = nil)
wait(timeout).tap { raise self if rejected? }
end
並行処理の中でデッドロックが発生した場合、アセットプリコンパイル処理が永遠に終わらない問題が発生します。
並行処理している中で、複数のスレッドが同一のファイルをコンパイルしようとしてデッドロックしているようです。
ruby-concurrencyのバグ?の可能性があるとのことです。
https://github.com/ruby-concurrency/concurrent-ruby/issues/870
また類似のissueも多く回っています。
See: https://github.com/rails/sprockets/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+thread+OR+concurrent
参考
https://github.com/rails/sprockets/issues/640
https://github.com/rails/sprockets/issues/581
https://github.com/ruby-concurrency/concurrent-ruby/issues/870
https://github.com/ruby-concurrency/concurrent-ruby/blob/ffed3c3c0518030b0ed245637703089fa1f0eeee/lib/concurrent/synchronization/lockable_object.rb#L6-L20
その他
sprocketsについての議論は以下のWTFのページも勉強になります。
https://discuss.rubyonrails.org/t/sprockets-abandonment/74371/24