Ruby/Rails - Whenever gem - Loop cron tasks - ruby-on-rails

I would like to control my cron jobs through my administration page.
Basically I have my cronjobs in my database, and I would like to create my crontab "on the fly".
Here's an example:
require "#{RAILS_ROOT}/config/environment.rb"
Cron.all.each do |cron|
if cron.at.blank?
every eval(cron.run_interval) do
cron.cmd
end
else
every eval(cron.run_interval), :at => cron.time do
cron.cmd
end
end
end
every 1.day do
command "whenever --update-crontab"
end
But Whenever doesn't output any of the tasks inside the loop, only the "static" one.
0 0 * * * whenever --update-crontab
How can I make Whenever 'understand' my loop?

You probably need to move your eval statement higher up, for example:
eval <<-EVAL
every #{cron.run_interval} do
#{cron.cmd}
end
EVAL
Assuming that your cron.cmd is something like 'command "ls -a"'.

Related

How to run a cron job every 10 seconds in ruby on rails

I am trying to run a cron job in every 10 seconds that runs a piece of code. I have used an approach which requires running a code and making it sleep for 10 seconds, but it seems to make drastically degrading the app performance. I am using whenever gem, which run every minute and sleeps for 10 seconds. How can I achieve the same w/o using sleep method. Following is my code.
every 1.minute do
runner "DailyNotificationChecker.send_notifications"
end
class DailyNotificationChecker
def self.send_notifications
puts "Triggered send_notifications"
expiry_time = Time.now + 57
while (Time.now < expiry_time)
if RUN_SCHEDULER == "true" || RUN_SCHEDULER == true
process_notes
end
sleep 10 #seconds
end
def self.process_notes
notes = nil
time = Benchmark.measure do
Note.uncached do
notes = Note.where(status: false)
notes.update_all(status: true)
end
end
puts "time #{time}"
end
end
Objective of my code is to change the boolean status of objects to true which gets checked every 10 seconds. This table has 2 million records.
I suggest using a Sidekiq background jobs for this. With the sidekiq-scheduler gem you can run ordinary sidekiq jobs schedules in whatever internal you need. Bonus points for having a web-interface to handle and monitor the jobs via the Sidekiq gem.
You would use the clockwork gem. It runs in a separate process. The configuration is pretty simple.
require 'clockwork'
include Clockwork
every(10.seconds, 'frequent.job') { DailyNotificationChecker.process_notes }

Machine goes hang while running cron job with 1 minutes duration

I am new to Rails, I am using whenever to gem to run cron job.In my application, I have some jobs that can be performed by drivers.The jobs has start and end time and it can be assigned to drivers.By cron job I need to check some conditions, like a
1. job is assigned and start within 30 minutes.
2. job is unassigned and start within 30 minutes.
3. job is assigned and start within 15 minutes.
I added the conditions to .rake file and mentioned the jobs in schedule file, below I paste my code snippets.
schedule.rb
every 1.minutes do
rake "jobs:unassigned_job_start_in_30"
end
every 1.minutes do
rake "jobs:assigned_job_start_in_30"
end
every 1.minutes do
rake "jobs:assigned_job_start_in_15"
end
and my rake file
auto_expire.rake
desc "assigned job starts in 30 mins."
task :assigned_job_start_in_30 => :environment do
start_range = Time.now.utc.to_time.to_i+(CONFIG[:job_starts_in_time_30]*60)
end_range = start_range + (1*60)
jobs = Job.where("start_at >= :start_range AND start_at <= :end_range AND status =:status", {start_range: start_range.to_s, end_range: end_range.to_s, status: 2000} )
jobs.each do |job|
job.add_event_log(nil, 2054)
create_and_send_notification(2054, job.id, 0, nil)
end
end
desc "assigned job starts in 15 mins."
task :assigned_job_start_in_15 => :environment do
start_range = Time.now.utc.to_time.to_i+ (CONFIG[:job_starts_in_time_15]*60)
end_range = start_range + (1*60)
jobs = Job.where("start_at >= :start_range AND start_at <= :end_range AND status =:status", {start_range: start_range.to_s, end_range: end_range.to_s, status: 2000} )
jobs.each do |job|
job.add_event_log(nil, 2055)
create_and_send_notification(2055, job.id, 0, nil)
end
end
As the cron jobs performed with 1 minutes duration, so my machine goes hang.Please suggest me what is the best way to check the conditions and performed cron jobs.
The cron jobs should run in the background, so your machine shouldn't hang while running it, there might be other reasons this is happening.
An alternative, is to just have one single task that runs, that checks and creates the tasks for a separate system that runs it. One example would be Sidekiq.

How to make Rufus-scheduler reflect file changes

I'm using Rufus-scheduler gem in my ROR application to send emails in the background. My setup is like:
# config/initializers/rufus_scheduler.rb
scheduler = Rufus::Scheduler.new(lockfile: '.rufus-scheduler.lock')
scheduler.cron '0 2 * * fri' do
UserMailer.send_some_emails
end
Any changes I make in the .send_some_email class method isn't reflected in the Rufus-scheduler task, how can I fix this? I don't want to restart the server every time I make a change!
Let's assume UserMailer.send_some_emails is defined in whatever/user_mailer.rb
scheduler = Rufus::Scheduler.new(:lockfile => '.rufus-scheduler.lock')
scheduler.cron '0 2 * * fri' do
load 'whatever/user_mailer.rb'
UserMailer.send_some_emails
end

Dynamic Ruby Daemon Management

I have a Ruby process that listens on a given device. I would like to spin up/down instances of it for different devices with a rails app. Everything I can find for Ruby daemons seems to be based around a set number of daemons running or background processing with message queues.
Should I just be doing this with Kernel.spawn and storing the PIDs in the database? It seems a bit hacky but if there isn't an existing framework that allows me to bring up/down daemons it seems I may not have much choice.
Instead of spawning another script and keeping the PIDs in the database, you can do it all within the same script, using fork, and keeping PIDs in memory. Here's a sample script - you add and delete "worker instances" by typing commands "add" and "del" in console, exiting with "quit":
#pids = []
#counter = 0
def add_process
#pids.push(Process.fork {
loop do
puts "Hello from worker ##{#counter}"
sleep 1
end
})
#counter += 1
end
def del_process
return false if #pids.empty?
pid = #pids.pop
Process.kill('SIGTERM', pid)
true
end
def kill_all
while del_process
end
end
while cmd = gets.chomp
case cmd.downcase
when 'quit'
kill_all
exit
when 'add'
add_process
when 'del'
del_process
end
end
Of course, this is just an example, and for sending comands and/or monitoring instances you can replace this simple gets loop with a small Sinatra app, or socket interface, or named pipes etc.

Working with Starling and multiple instances of Mongrel through Mongrel Cluster

Situation:
In a typical cluster setup, I have a 5 instances of mongrel running behind Apache 2.
In one of my initializer files, I schedule a cron task using Rufus::Scheduler which basically sends out a couple of emails.
Problem:
The task runs 5 times, once for each mongrel instance and each recipient ends up getting 5 mails (despite the fact I store logs of each sent mail and check the log before sending). Is it possible that since all 5 instances run the task at exact same time, they end up reading the email logs before they are written?
I am looking for a solution that will make the tasks run only once. I also have a Starling daemon up and running which can be utilized.
The rooster rails plugin specifically addresses your issue. It uses rufus-scheduler and ensures the environment is loaded only once.
The way I am doing it right now:
Try to open a file in exclusive locked mode
When lock is acquired, check for messages in Starling
If message exists, other process has already scheduled the job
Set the message again to the queue and exit.
If message is not found, schedule the job, set the message and exit
Here is the code that does it:
starling = MemCache.new("#{Settings[:starling][:host]}:#{Settings[:starling][:port]}")
mutex_filename = "#{RAILS_ROOT}/config/file.lock"
scheduler = Rufus::Scheduler.start_new
# The filelock method, taken from Ruby Cookbook
# This will ensure unblocking of the files
def flock(file, mode)
success = file.flock(mode)
if success
begin
yield file
ensure
file.flock(File::LOCK_UN)
end
end
return success
end
# open_lock method, taken from Ruby Cookbook
# This will create and hold the locks
def open_lock(filename, openmode = "r", lockmode = nil)
if openmode == 'r' || openmode == 'rb'
lockmode ||= File::LOCK_SH
else
lockmode ||= File::LOCK_EX
end
value = nil
# Kernerl's open method, gives IO Object, in our case, a file
open(filename, openmode) do |f|
flock(f, lockmode) do
begin
value = yield f
ensure
f.flock(File::LOCK_UN) # Comment this line out on Windows.
end
end
return value
end
end
# The actual scheduler
open_lock(mutex_filename, 'r+') do |f|
puts f.read
digest_schedule_message = starling.get("digest_scheduler")
if digest_schedule_message
puts "Found digest message in Starling. Releasing lock. '#{Time.now}'"
puts "Message: #{digest_schedule_message.inspect}"
# Read the message and set it back, so that other processes can read it too
starling.set "digest_scheduler", digest_schedule_message
else
# Schedule job
puts "Scheduling digest emails now. '#{Time.now}'"
scheduler.cron("0 9 * * *") do
puts "Begin sending digests..."
WeeklyDigest.new.send_digest!
puts "Done sending digests."
end
# Add message in queue
puts "Done Scheduling. Sending the message to Starling. '#{Time.now}'"
starling.set "digest_scheduler", :date => Date.today
end
end
# Sleep will ensure all instances have gone thorugh their wait-acquire lock-schedule(or not) cycle
# This will ensure that on next reboot, starling won't have any stale messages
puts "Waiting to clear digest messages from Starling."
sleep(20)
puts "All digest messages cleared, proceeding with boot."
starling.get("digest_scheduler")
Why dont you use mod_passenger (phusion)? I moved from mongrel to phusion and it worked perfect (with a timeamount of < 5 minutes)!

Resources