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/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)
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生产.
您可能需要更改自己eager_load_paths以包含引发错误的类或模块的路径.eager_load_paths被记录在Rails的指南.
你遇到的问题是当应用程序启动时Rails没有加载这些常量 ; 当它们被其他一些代码调用时,它会自动加载它们.在多线程Rails应用程序中,两个线程在尝试加载这些常量时可能会出现争用情况.
告诉Rails急切地加载这些常量意味着它们将在Rails应用程序启动时加载一次.这还不够eager_load = true; 您还必须指定类或模块定义的路径.在轨道应用程序配置,这是一个Array下eager_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_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"]
Run Code Online (Sandbox Code Playgroud)
在我的 gems 中(即 inplezi和iodine),我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警告和错误,我使用了这些片段。
我不知道这是否有帮助,但我想你可能想尝试一下。