2015-04-03 47 views
7

我有以下型號:的Rails 4.2自動加載不是線程安全

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 

MessagePoro模型可以是簡單的東西,例如app/models/message_poro.rb:

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

    def deliver! 
    # ... 
    end 
end 

現在,當我有例如100個用戶,並且我跑User.send_to_all( 「測試」),我有時得到錯誤thoser:

RuntimeError: Circular dependency detected while autoloading constant MessagePoro 

或:

wrong number of arguments (1 for 0) 

我想這一定是因爲MessagePoro ISN不會加載,所有線程都會嘗試同時加載它,或類似的東西。由於這些錯誤有時只會發生,所以我非常確定只有在出現「競爭條件」或與Threading有關的情況時纔會這樣做。我已經嘗試在啓動線程之前初始化MessagePoro,並且已經玩過eager_loading,但問題似乎仍然存在。 我還可以嘗試緩解這個問題嗎?

+0

嘗試在自動加載前手動要求 – 2015-04-04 16:52:01

+0

請您詳細說明一下嗎?就像在線程啓動之前調用MessagePoro.new一樣? 如果這是修復它,我仍然好奇底層問題 – 2015-04-04 16:53:27

+0

沒有。目前您擁有基本的Rails自動加載設置。如果您在代碼中首先調用MessagePoro,那麼它會根據一些約定(例如,MyModule :: MessagePoro應該在autoload_path/my_module/message_poro.rb中)要求它。但是你可以嘗試手動要求'path/to/message_poro'。 – 2015-04-04 16:57:11

回答

0

經過一番研究發現,自動加載現在是線程安全的。所以這可能是一種迴歸。結帳Threading with the AWS SDK for Ruby。這個補丁是由Charles Nutter在ruby中引入的2.0.0 autoload is not thread-safe

無論如何,如果它只是這個類,你可以通過手動要求自動加載它。 只需要手動。

require 'message_poro' 
class User 
def self.send_to_all(content) 
    ... 
end 
+0

感謝您的意見。我知道你的修復可以治癒這些症狀,但是我覺得這不能解決潛在的問題 – 2015-04-04 19:08:41

+0

它確實會在自動加載時自動加載:),這是潛在的問題 – 2015-04-04 19:33:25

+0

Plz發佈你的ruby版本並讓我們提交一個Bug請求給鐵軌或紅寶石。如果你可以創建一個Demo github Repo來重新編譯bug – 2015-04-04 19:35:07

1

這不是一個真正的答案,但我確實有更多的信息。拋出的錯誤來自ActiveSupport

if file_path 
    expanded = File.expand_path(file_path) 
    expanded.sub!(/\.rb\z/, '') 

    if loading.include?(expanded) 
     raise "Circular dependency detected while autoloading constant #{qualified_name}" 
    else 
     require_or_load(expanded, qualified_name) 
     raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) 
     return from_mod.const_get(const_name) 
    end 
    elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) 
    return mod 
    elsif 

經過進一步的研究,我們可以看到,加載是一個類變量。

# Stack of files being loaded. 
mattr_accessor :loading 
self.loading = [] 

兩個線程檢查同一個文件:

第一線打這個代碼,並把道路入裝載

 loading << expanded 

然後第二個線程去檢查所代表的路徑擴大,命中

if loading.include?(expanded) 
     raise "Circular dependency detected while autoloading constant #{qualified_name}" 

我錯過了什麼? ActiveSupport ::依賴關係不是線程安全的?

4

我試圖在[rails_root]/lib目錄中使用額外的自定義庫時,最近遇到了一個非常類似的問題。

TL; DR:

您可以使用預先加載來解決這個問題,因爲這可以確保所有的常量/模塊/類在內存中的任何實際的代碼運行之前。然而,對於這個工作:

  1. 您必須在Rails的配置config.eager_load = true集(這是在默認情況下在生產環境中完成)
  2. 的文件,你的類將要急切加載是必須在config.eager_load_paths之內,而不是config.autoload_paths

OR

您可以使用requirerequire_dependency(其它的ActiveSupport功能),以確保您需要明確加載之前,否則會得到由Rails的自動加載的代碼。

更多信息

正如他在答覆中提到digidigo,循環依賴錯誤來自ActiveSupport::Dependencies模塊,或者更一般地說的Rails的自動加載。此代碼是非線程安全,因爲它使用該類/模塊變量來存儲正在加載的文件。如果兩個線程同時自動加載相同的東西,其中一個會看到文件加載到該類變量中,並引發「循環依賴」錯誤,從而導致誤導。

我在(線程)Puma網絡服務器的生產模式下運行Rails時遇到了這個問題。我們在我們的Rails根目錄中的lib目錄中添加了一個小型庫,並且最初將lib添加到config.autoload_once_paths。在開發中一切都很好,但在生產中(啓用了config.eager_loadconfig.cache_classes),偶爾我們會在近乎同步的請求中獲得相同的循環依賴問題。稍後幾小時的調試結束後,我終於看到圍繞循環依賴性的ActiveSupport代碼,並看到代碼中不同點處的不同線程時,眼前出現了非線程安全問題。第一個線程將添加文件以加載到數組中,然後第二個線程將在那裏找到它並引發循環依賴性錯誤。

事實證明,增加一些東西給autoload_pathsautoload_once_paths確實不是也意味着它會被渴望加載拿起。然而情況正好相反 - 如果禁用了eager_load,則將添加到eager_load_paths的路徑用於自動加載(有關更多信息,請參閱this article)。我們切換到eager_load_paths,到目前爲止沒有進一步的問題。有趣的是,就在Rails 4測試版之前,默認情況下,自動加載在生產環境中被禁用,這意味着像這樣的問題會導致100%的時間內嚴重失敗,而不是一個古怪的線程失敗5 % 的時間。然而,4.0版本的發佈會及時恢復 - 你可以看到一些關於它的熱烈討論here(包括「誠實地說,你告訴我自己去f ***的選擇」)。但從那以後,這種回覆已經在Rails 5.0.0beta1之前得到了回覆,所以希望更少的人在將來再次處理這個問題。

附加說明:

Rails的自動加載磁帶機是從Ruby的自動加載磁帶機完全獨立 - 這似乎是由於Rails試圖自動加載的常量時,不會在目錄結構中更多的推論。

從Ruby 2開始,Ruby的自動加載似乎已經成爲線程安全。0,但這與Rails自動加載代碼無關。如前所述,Rails的自動加載器似乎絕對是而不是線程安全。

相關問題