web-dev-qa-db-ja.com

Railsでのマルチスレッド:定数の自動ロード中に循環依存が検出されました

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_loadingfalseと等しくなるはずです。

15
edwardmp

エラーを引き起こしているクラスまたはモジュールへのパスを含めるために、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では機能しません。したがって、通常はproductioneager_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"]
7
anothermh

私の宝石(つまり、pleziiodine)では、主に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の警告とエラーのため、これらのスニペットを使用しました。

これが役立つかどうかはわかりませんが、試してみてはいかがでしょうか。

4
Myst

並列処理を処理する2つのgemを試しているときに、この問題が発生しました。

  1. pmap gem
  2. 並列宝石

Pmapの場合、Celluloid :: TaskTerminatedに関連するエラーが発生し続け、並列の場合、複数のスレッドで実行したときに定数を自動ロードしているときに循環依存が検出されていました。この問題は、クラスとモジュールの読み込みとスレッドへの配置の競争に関連していることを私は知っていました。両方の構成をtrueに有効にしてみますconfig.cache_classes = trueおよびconfig.eager_load = true開発環境で、それが私にとってトリックでした。

4
supzann3