resque/resque-scheduler

Circular dependency detected while autoloading constant

diei opened this issue · 9 comments

diei commented

Newly I have 12 static cron jobs scheduled with resque-scheduler starting at the same time. The scheduler always raises some exceptions during queuing the jobs the first time, at second time no errors are raised and the jobs are queued normally.

I use:

  • resque 1.27.4
  • resque-scheduler 4.3.1
  • rails 5.1.6
  • Ruby 2.5.1

I digged a little bit further and discovered that the issue is that the jobs started at the same time. And if I play with the Rails app config I get different errors.

With config.cache_classes = false I get only this error:

resque-scheduler: [ERROR] 2018-06-26T14:14:00+02:00: RuntimeError: Circular dependency detected while autoloading constant Core::Importer::Jobs::Job 
["/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:509:in `load_missing_constant'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:202:in `const_missing'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:28:in `block in constantize'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:22:in `each'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:22:in `constantize'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:231:in `enqueue_from_config'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:214:in `enqueue'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:426:in `enqueue_recurring'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:144:in `block (2 levels) in load_schedule_job'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:210:in `do_call'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:254:in `trigger_now'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:296:in `block (3 levels) in start_work_thread'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:299:in `block (2 levels) in start_work_thread'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:285:in `loop'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:285:in `block in start_work_thread'"]

With config.cache_classes = true I get the error above and:

resque-scheduler: [ERROR] 2018-06-26T14:14:00+02:00: ArgumentError: A copy of Core::Importer::Jobs has been removed from the module tree but is still active! 
["/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:496:in `load_missing_constant'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:202:in `const_missing'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:28:in `block in constantize'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:22:in `each'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler/util.rb:22:in `constantize'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:231:in `enqueue_from_config'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:214:in `enqueue'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:426:in `enqueue_recurring'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/resque-scheduler-4.3.1/lib/resque/scheduler.rb:144:in `block (2 levels) in load_schedule_job'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:210:in `do_call'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:254:in `trigger_now'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:296:in `block (3 levels) in start_work_thread'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:299:in `block (2 levels) in start_work_thread'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:285:in `loop'", 
"/Users/test_user/.rvm/gems/ruby-2.5.1@core/gems/rufus-scheduler-3.5.0/lib/rufus/scheduler/jobs.rb:285:in `block in start_work_thread'"]

The config.eager_load does not matter in this case. The result is the same with true and false. Parallelly class loading in different threads seems to be the problem.

A workaround is to change the cron expression. If I let start the 12 jobs sequentially with 5 seconds delay, no error is raised.

Does anyone know a fix for this?

This seems to be a bug in the application. A dependency of a class is referencing the same target class before the dependency is fully loaded, this is causing a circular autoloading. I'll have to make sure that all classes can be loaded without this circular autoloading.

diei commented

I'll have to make sure that all classes can be loaded without this circular autoloading.

Hello @rafaelfranca, have you created a new issue for this or why have you closed this one without doing anything?

Sorry, I made a typo in my message. I meant: You have to make sure all classes can be loaded without this circular autoloading. There is a bug in your application, it is not resque-schedule issue.

diei commented

But the classes can be loaded without errors, if I change the cron expression so that they are started one after another. So basically there are no problems loading the classes.
The issue comes up if resque-scheduler loads the same class multiple times "simultaneously".

Is resque-scheduler starting the cron jobs in different threads or sequentially in one thread? The real cause need not to be resque-scheduler but perhaps how Ruby or Rails do class loading (maybe not thread safe).

The classes can load fine in some ordering, but in others not. This is why you are getting this problem only occasionally. Sometimes the order your classes are loaded are different, and in those cases you get an exception. You need to make sure your classes can be loaded correctly in any order.

We just hit this problem this week after changing one of our items in scheduler.yml to be the same as one of the others.

You need to make sure your classes can be loaded correctly in any order.

I have no clue what this would mean in my case. I have a Rails app which is doing nothing out of the ordinary when it comes to loading the app.

Got the same issue out of the blue Yesterday. The bug was defining task 'resque:setup' => :environment twice in rake files. What I don't get, is why this started to reliably occur yesterday when it was working OK like this for 4 years and nothing related seems to be changed in the codebase.

diei commented

The usage of the zeitwerk mode introduced with Rails 6 seems to fix this problem as well.

In case it helps anyone, I think this is actually a bug in ActiveSupport that can occur anytime you have multiple threads autoloading a nested constant at the same time. This happens in resque-scheduler when you have multiple jobs configured to start at the same time. A separate thread is created to load each job class and call #enqueue and these threads can run simultaneously.

Related Rails issue: rails/rails#33209