使用线程时处理rails中的循环依赖

时间:2016-04-03 07:30:39

标签: ruby-on-rails ruby multithreading

以下是添加到my app的五个文件(没有数据库,不需要设置):

LIB /任务/ precomputation.rake

namespace :precomputation do
  desc "This fetches data for precomputation"
  task fetch_all: :environment do
      Precomputation.precompute_all_data
    # end
  end
end

应用程序/模型/ precomputation.rb

class Precomputation
  def self.precompute_all_data
    ad_accounts = [1,2,3]
    bgthread = BackgroundThread::BGThreadPool.new(1)
    tasks = []
    ad_accounts.each do |ad_account_id|
      p = Proc.new do
        begin
          MongoPipeline::FbAdCampaignMongoPipeline.new(ad_account_id).fetch_all
          false
        ensure
          GC.start
        end
      end
      tasks << [p, "test #{ad_account_id}"]
    end
    bgthread.add_randomized_tasks(tasks)
    bgthread.do_work
  end
end

应用程序/模型/ mongo_pipeline.rb

module MongoPipeline
  class Base
    def initialize(ad_account_id)
    end

    def insert_data
      puts 'inserting data'
    end

    def fetch_all
      extract_data # Child Class defines this method
      insert_data # Base class defines this method
    end
  end
end

应用程序/模型/ mongo_pipeline / fb_ad_campaign_mongo_pipeline.rb

module MongoPipeline
  class FbAdCampaignMongoPipeline < MongoPipeline::Base
    def extract_data
      puts 'here is campaign data'
    end
  end
end

和app / models / background_thread.rb

(注意:使用并行gem并且没有后台线程的替代实现也会崩溃并出现类似错误 - :https://github.com/pratik60/circular_dependency_havoc/tree/parallel

错误日志

rake aborted!
Circular dependency detected while autoloading constant MongoPipeline
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:492:in `load_missing_constant'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:184:in `const_missing'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:526:in `load_missing_constant'
/Users/pratikbothra/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:184:in `const_missing'
/webapps/circular_dependency_havoc/app/models/precomputation.rb:9:in `block (2 levels) in precompute_all_data'
/webapps/circular_dependency_havoc/app/models/background_thread.rb:91:in `call'
/webapps/circular_dependency_havoc/app/models/background_thread.rb:91:in `block in background'
Tasks: TOP => precomputation:fetch_all
(See full trace by running task with --trace)

关于我做错的任何想法?后台线程库已克隆并刚刚修改。如果您认为这是问题,请随意更换它。任何建议,任何想法都非常欢迎。

2 个答案:

答案 0 :(得分:3)

在rake任务中构建线程循环以轮询外部服务器时,我遇到了这个问题,得出的结论是Autoload&amp;不可预测的竞争条件相关。但是我不想简单地加载热切,所以有两种方法可以解决这个问题。一个是弄清楚我的任务中加载了哪些类,并在产生任何线程之前将它们加载到父级中:

# preload classes that showed circular dependencies just in this task
MediaFile
VideoPlatformIntegration
Tag

10.times.map{Thread.new{...}}

这就像一个魅力,但是放入生产代码有点奇怪,并且对任务的任何变化或它调用的任何方法都不健壮。

另一个更不完美,但实际上只是修改了开发环境中的行为,并且不依赖于了解可能导致这种情况的所有事情。由于它只是在开发中,我认为这是可以接受的:在每个初始线程开始时随机休息一段时间(0到1秒之间):

sleep(Random.rand) if Rails.env == "development"

此睡眠可防止线程同时启动,并大大降低加载类中任何竞争条件的可能性。我在这里使用一个简单的10个线程批次进行概念验证,运行脚本4次,没有睡眠4次,显示可靠的循环依赖性错误,没有睡眠:

[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{MediaFile.last rescue puts($!)}}'
Circular dependency detected while autoloading constant MediaFile
Circular dependency detected while autoloading constant MediaFile
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'
[video_platform_integration] andrew@~/3p/app3$ rails runner '10.times.map{Thread.new{sleep(Random.rand); MediaFile.last rescue puts($!)}}'

答案 1 :(得分:1)

我最初更改了此设置

config/environments/development.rb

config.eager_load = true

虽然这没有帮助!

我需要添加

config/initializers/eager_load.rb:

Rails.application.eager_load! unless Rails.env.test?

如果你正在使用一个文件夹,你还需要急切加载整个lib文件夹(自动加载不够)。

config/application.rb

config.eager_load_paths += Dir["#{Rails.root}/lib/**/"]

此外,请确保如果在初始化程序中使用了devise,则将其重命名为01_devise.rb或其他内容,因为初始化程序按字母顺序加载,而您的用户或管理员将引用它。

可能会建议在测试中跳过eager_load,因为它不是最理想的,如果你没有在内部使用线程,请完全跳过它!