I'm well aware why this is happening (two ruby runtimes) and that this is a common problem for people who have not read the RS FAQ or searched on SO for this before, but I've spent a couple days trying many prescribed solutions yet my rufus-scheduler continues to invoke twice.
This occurs on production only, running Rails 5.0.6, Puma server, on Heroku.
This is my scheduler.rb:
require 'rufus-scheduler'
a_scheduler = Rufus::Scheduler.new(:lockfile => ".rufus-scheduler-a.lock")
b_scheduler = Rufus::Scheduler.new(:lockfile => ".rufus-scheduler-b.lock")
unless defined?(Rails::Console) || File.split($0).last == 'rake' || !Rails.env.production?
a_scheduler.cron '0 21 * * *', overlap: false, blocking: true do
MySidekiqWorker.perform_async unless a_scheduler.down?
end
b_scheduler.every '1h', overlap: false, blocking: true do
MyOtherSidekiqWorker.perform_async unless b_scheduler.down?
end
end
I've tried lockfiles, configuring my own scheduler_lock, different parameters for .every
and .cron
. Moreover, it seems even though I have overlap: false
and blocking: true
, new instances of MyOtherSidekiqWorker
will still be invoked while one is still running.
I must be missing something obvious, thanks for your help.
So, Heroku dynos not sharing the file system
The .rufus-scheduler-a.lock
seen on dyno d0 is not the .rufus-scheduler-a.lock
seen on dyno d1.
Your Heroku dynos do not share the same filesystem and also they do not share the same Ruby process and thus not the same rufus-scheduler instance. So overlap: false
, blocking: true
will not have any effect from dyno d0 to dyno d1.
You could implement a custom locking mechanism for rufus-scheduler taking inspiration from https://github.com/jmettraux/rufus-scheduler#advanced-lock-schemes (probably via the database because it's shared by your Ruby processes) but that will not help with overlap: false
and blocking: true
.
If you still want to have overlap: false
and blocking: true
, you could look at https://devcenter.heroku.com/articles/scheduled-jobs-custom-clock-processes and have the scheduling happening in a dedicated process/dyno with rufus-scheduler or Clockwork and have no need for a schedule lock.
The rest of my answer is about your code, not about the double scheduling you are experiencing.
scheduler.down?
b_scheduler.every '1h', overlap: false, blocking: true do
MyOtherSidekiqWorker.perform_async unless b_scheduler.down?
end
Why do you have this unless b_scheduler.down?
if the b_scheduler
is down the block will not be executed at all.
This is sufficient:
b_scheduler.every '1h', overlap: false, blocking: true do
MyOtherSidekiqWorker.perform_async
end
a_scheduler vs b_scheduler
You do not need one scheduler for each job. You can simply write:
require 'rufus-scheduler'
#scheduler = Rufus::Scheduler.new(lockfile: '.rufus-scheduler.lock')
scheduler = Rufus::Scheduler.new
unless defined?(Rails::Console) || File.split($0).last == 'rake' || !Rails.env.production?
scheduler.cron '0 21 * * *', overlap: false, blocking: true do
MySidekiqWorker.perform_async
end
scheduler.every '1h', overlap: false, blocking: true do
MyOtherSidekiqWorker.perform_async
end
end