Railsアプリがあり、並行Rubygemによって提供されるマルチスレッド関数を使用するRakeタスクがあります。
時々、Circular dependency detected while autoloading constant
エラーが発生します。
少しグーグルした後、これはロードRails定数と組み合わせてスレッドを使用することに関連していることがわかりました。
次のGitHubの問題に遭遇しました: https://github.com/Ruby-concurrency/concurrent-Ruby/issues/585 および https://github.com/Rails/rails/issues/26847
ここで説明したように、新しいスレッドから呼び出されたコードは、Rails.application.reloader.wrap do
またはRails.application.executor.wrap do
ブロックでラップする必要があります。これは私が行ったことです。ただし、これはデッドロックにつながります。
次に、ActiveSupport::Dependencies.interlock.permit_concurrent_loads
を使用して、メインスレッドで別のブロッキング呼び出しをラップすることをお勧めします。ただし、これでどのコードをラップする必要があるのかわかりません。
これが私が試したものですが、これでもデッドロックが発生します。
@beanstalk = Beaneater.new("#{ENV.fetch("Host", "Host")}:#{ENV.fetch("BEANSTALK_PORT", "11300")}")
tube_name = ENV.fetch("BEANSTALK_QUEUE_NAME", "queue")
pool = Concurrent::FixedThreadPool.new(Concurrent.processor_count * 2)
# Process jobs from tube, the body of this block gets executed on each message received
@beanstalk.jobs.register(tube_name) do |job|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@logger.info "Received job: #{job.id}"
Concurrent::Future.execute(executor: pool) do
Rails.application.reloader.wrap do
# Stuff that references Rails constants etc
process_beanstalk_message(job.body)
end
end
end
end
@beanstalk.jobs.process!(reserve_timeout: 10)
誰かが私がこれをどのように解決すべきかについて光を当てることができますか?奇妙なことに、私は本番環境でこれに遭遇しますが、このトピックに関する他の情報は、通常は開発中にのみ発生するはずであることを示唆しているようです。
本番環境では、次の設定を使用します。
config.eager_load = true
config.cache_classes = true
。
すべての環境の自動ロードパスはRailsデフォルトと2つの特定のフォルダー( "モデル/バリデーター"と "ジョブ/懸念事項")です。
eager_load_paths
はどの設定でも変更または設定されていないため、Railsデフォルトと同じである必要があります。
私はRails 5を使用しているので、本番環境ではenable_dependency_loading
はfalse
と等しくなるはずです。
エラーを引き起こしているクラスまたはモジュールへのパスを含めるために、eager_load_paths
を変更する必要がある可能性があります。 eager_load_paths
は文書化されています Railsガイド 。
あなたが遭遇している問題は、アプリの起動時に Railsがこれらの定数をロードしていない ということです。それらが他のコードによって呼び出されると、自動的にロードされます。マルチスレッドRailsアプリでは、2つのスレッドがこれらの定数を読み込もうとすると、競合状態になる可能性があります。
Railsにこれらの定数を熱心にロードするように指示すると、Railsアプリの起動時に1回ロードされることを意味します。eager_load = true
;と言うだけでは不十分です。クラスまたはモジュール定義へのパスも指定する必要があります。Railsアプリケーション構成では、これはeager_load_paths
の下のArray
です。たとえば、熱心にload ActiveJob
classes:
config.eager_load_paths += ["#{config.root}/app/jobs"]
または、lib/
からカスタムモジュールをロードするには:
config.eager_load_paths += ["#{config.root}/lib/custom_module"]
熱心な負荷設定を変更すると、Railsの動作に影響します。たとえば、Rails development
環境では、おそらくRails server
を1回実行することに慣れており、エンドポイントの1つをリロードするたびに、行ったコードへの変更。クラスは起動時に一度読み込まれるため、config.eager_load = true
では機能しません。したがって、通常はproduction
のeager_load
設定のみを変更します。 。
更新
eager_load_paths
から既存のRails console
を確認できます。たとえば、これらは新しいRails 5アプリのデフォルト値です。ご覧のとおり、app/**/*.rb
は読み込まれません。Railsは知っていることが期待されています。
Rails.application.config.eager_load_paths
=> ["/app/assets",
"/app/channels",
"/app/controllers",
"/app/controllers/concerns",
"/app/helpers",
"/app/jobs",
"/app/mailers",
"/app/models",
"/app/models/concerns"]
私の宝石(つまり、plezi
とiodine
)では、主にif
ステートメントでこれを解決します。
次のようなコードがあります。
require 'uri' unless defined?(::URI)
または
begin
require 'rack/handler' unless defined?(Rack::Handler)
Rack::Handler::WEBrick = ::Iodine::Rack # Rack::Handler.get(:iodine)
rescue Exception
end
Circular dependency detected
の警告とエラーのため、これらのスニペットを使用しました。
これが役立つかどうかはわかりませんが、試してみてはいかがでしょうか。