Rails 4.2自动加载不是线程安全的

Ben*_*t B 9 multithreading eager-loading ruby-on-rails-4

我有以下型号:

class User < ActiveRecord::Base
  def send_message(content)
    MessagePoro.new(content).deliver!
  end

  def self.send_to_all(content)
    threads = []
    all.each do |user|
      threads << Thread.new do
        user.send_message(content)
      end
    end
    threads.each(&:join)
  end
end
Run Code Online (Sandbox Code Playgroud)

MessagePoro模型可以很简单,例如app/models/message_poro.rb:

class MessagePoro
  def initialize(content)
    # ...
  end

  def deliver!
    # ...
  end
end
Run Code Online (Sandbox Code Playgroud)

现在,当我有100个用户,并且我正在运行User.send_to_all("test")时,我有时会遇到更严重的错误:

RuntimeError: Circular dependency detected while autoloading constant MessagePoro
Run Code Online (Sandbox Code Playgroud)

要么:

wrong number of arguments (1 for 0)
Run Code Online (Sandbox Code Playgroud)

我认为它必须是因为没有加载MessagePoro并且所有线程都试图同时加载它,或类似的东西.由于这些错误有时只会发生,我很确定只有当存在"竞争条件"或者有线程时才会出现这种情况.我已经尝试在启动Threads之前初始化MessagePoro,并且我已经使用了eager_loading,但问题似乎仍然存在.还有什么可以尝试缓解这个问题?

nuc*_*eon 8

我最近在尝试使用放在[rails_root]/lib目录中的额外自定义库时遇到了一个非常类似的问题.

TL; DR:

您可以使用急切加载来解决问题,因为这可以确保在任何实际代码运行之前所有常量/模块/类都在内存中.但是为了这个工作:

  1. 您必须config.eager_load = true在Rails配置中设置(这在生产环境中默认完成)
  2. 您要加载的类所在的文件必须位于config.eager_load_paths,而不是config.autoload_paths.

要么

您可以使用requirerequire_dependency(另一个ActiveSupport功能)确保在Rails自动加载之前显式加载您需要的代码.

更多信息

正如digidigo在他的回复中提到的,循环依赖性错误来自ActiveSupport::Dependencies模块,或者更一般的Rails自动加载器.此代码不是线程安全的,因为它使用该类/模块变量来存储它正在加载的文件.如果两个线程最终同时自动加载同一个东西,那么其中一个线程可能会因为看到要在该类变量中加载的文件并引发"循环依赖"错误而被误导.

我在使用(线程)Puma网络服务器的生产模式下运行Rails时遇到了这个问题.我们lib在Rails根目录中添加了一个小型库,最初添加libconfig.autoload_once_paths.在开发中一切都很好,但是在生产中(有config.eager_loadconfig.cache_classes启用),偶尔我们会在几乎同时发出的请求中得到这些相同的循环依赖问题.经过几个小时的调试后,我最终看到了眼前的非线程安全问题,当围绕循环依赖关系逐步执行ActiveSupport代码并看到不同的线程在代码中的不同点处获取时.第一个线程将添加文件加载到loading数组中,然后第二个线程将在那里找到它并引发循环依赖性错误.

原来添加的东西autoload_pathsautoload_once_paths也意味着它会立即加载得到回升.但事实恰恰相反 - eager_load_paths如果禁用eager_load,则添加到的路径将被视为自动加载(有关详细信息,请参阅此文章).eager_load_paths到目前为止,我们已经转而没有进一步的问题.

有趣的是,就在轨道4测试版之前,自动加载在默认情况下,生产环境,这意味着像这样的问题,会造成硬故障的100%的时间,而不是一个古怪的穿线禁用失败的5%的时间.然而,这是在4.0测试版发布时及时恢复的 - 你可以在这里看到一些(热情的)讨论(包括选择短语'老实说,你告诉我去自己做什么?').从那时起,该恢复已经在Rails 5.0.0beta1之前被恢复,所以希望将来会有更少的人不得不再次处理这个问题的头痛.

额外说明:

Rails自动加载器完全独立于Ruby自动加载器 - 这似乎是因为Rails在尝试自动加载常量时对目录结构做了更多的推断.

从Ruby 2.0开始,Ruby的自动加载似乎已成为线程安全,但这与Rails自动加载代码无关.如前所述,Rails的自动加载器似乎绝对不是线程安全的.


dre*_*-hh 0

经过一些研究发现,自动加载现在是线程安全的。所以这很可能是一种回归。使用适用于 Ruby 的 AWS 开发工具包检查线程。该补丁由 Charles Nutter 在 ruby​​ 2.0.0 autoload 中引入,不是线程安全的

无论如何,如果只是这个类,您可以通过手动请求来避免自动加载它。只需手动要求即可。

require 'message_poro'
class User
def self.send_to_all(content) 
  ... 
end
Run Code Online (Sandbox Code Playgroud)