Rails中的多线程:在自动加载常量时检测到循环依赖性

edw*_*dmp 14 ruby multithreading ruby-on-rails concurrent-ruby

我有一个Rails应用程序,其中我有一个Rake任务,它使用concurrent-ruby gem提供的多线程函数.

我不时会遇到Circular dependency detected while autoloading constant错误.

在谷歌搜索了一下后,我发现这与使用线程结合加载Rails常量有关.

我偶然发现了以下GitHub问题:https://github.com/ruby-concurrency/concurrent-ruby/issues/585https://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)
Run Code Online (Sandbox Code Playgroud)

任何人都可以阐明我应该如何解决这个问题?奇怪的是我在生产中遇到了这个,而关于这个主题的其他信息似乎暗示它通常只应该在开发中出现.

在生产中我使用以下设置:

config.eager_load = true

config.cache_classes = true.

所有环境的自动加载路径都是Rails默认加上两个特定文件夹("模型/验证器"和"作业/关注点").

eager_load_paths 未在我的任何配置中修改或设置,因此必须等于Rails默认值.

我正在使用Rails 5所以enable_dependency_loading应该等于false生产.

ano*_*rmh 8

您可能需要更改自己eager_load_paths以包含引发错误的类或模块的路径.eager_load_paths被记录在Rails的指南.

你遇到的问题是当应用程序启动时Rails没有加载这些常量 ; 当它们被其他一些代码调用时,它会自动加载它们.在多线程Rails应用程序中,两个线程在尝试加载这些常量时可能会出现争用情况.

告诉Rails急切地加载这些常量意味着它们将在Rails应用程序启动时加载一次.这还不够eager_load = true; 您还必须指定类或模块定义的路径.在轨道应用程序配置,这是一个Arrayeager_load_paths.例如,对于急切的加载ActiveJob类:

config.eager_load_paths += ["#{config.root}/app/jobs"]
Run Code Online (Sandbox Code Playgroud)

或者从以下位置加载自定义模块lib/:

config.eager_load_paths += ["#{config.root}/lib/custom_module"]
Run Code Online (Sandbox Code Playgroud)

更改您的预先加载设置将影响Rails的行为.例如,在Rails development环境中,您可能习惯于运行rails server一次,每次重新加载其中一个端点时,它都会反映您对代码所做的任何更改.这将无法使用config.eager_load = true,因为类在启动时加载一次.因此,您通常只会更改您的eager_load设置production.

更新

你可以检查你现有eager_load_pathsrails 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"]
Run Code Online (Sandbox Code Playgroud)


小智 5

我在尝试两个处理并行处理的 gem 时遇到了这个问题;

  1. 映射宝石
  2. 平行宝石

对于 pmap,我不断收到与 Celluloid::TaskTermulated 相关的错误,对于并行,当我使用超过 1 个线程运行它时,我在自动加载常量时检测到循环依赖项。我知道这个问题与我的类和模块如何急于加​​载并竞争放置在线程上有关。config.cache_classes = true我尝试在开发环境中将这两个配置都启用为 true config.eager_load = true,这对我来说很有效。


Mys*_*yst 4

在我的 gems 中(即 inpleziiodine),我if主要用语句来解决这个问题。

您会发现如下代码:

require 'uri' unless defined?(::URI)
Run Code Online (Sandbox Code Playgroud)

或者

begin
  require 'rack/handler' unless defined?(Rack::Handler)
  Rack::Handler::WEBrick = ::Iodine::Rack # Rack::Handler.get(:iodine)
rescue Exception

end
Run Code Online (Sandbox Code Playgroud)

由于Circular dependency detected警告和错误,我使用了这些片段。

我不知道这是否有帮助,但我想你可能想尝试一下。