Manually Retry Job in Delayed_job - ruby-on-rails

Delayed::Job's auto-retry feature is great, but there's a job that I want to manually retry now. Is there a method I can call on the job itself like...
Delayed::Job.all[0].perform
or run, or something. I tried a few things, and combed the documentation, but couldn't figure out how to execute a manual retry of a job.

To manually call a job
Delayed::Job.find(10).invoke_job # 10 is the job.id
This does not remove the job if it is run successfully. You need to remove it manually:
Delayed::Job.find(10).destroy

Delayed::Worker.new.run(Delayed::Job.last)
This will remove the job after it is done.

You can do it exactly the way you said, by finding the job and running perform.
However, what I generally do is just set the run_at back so the job processor picks it up again.

I have a method in a controller for testing purposes that just resets all delayed jobs when I hit a URL. Not super elegant but works great for me:
# For testing purposes
def reset_all_jobs
Delayed::Job.all.each do |dj|
dj.run_at = Time.now - 1.day
dj.locked_at = nil
dj.locked_by = nil
dj.attempts = 0
dj.last_error = nil
dj.save
end
head :ok
end

Prior answers above might be out of date. I found I needed to set failed_at, locked_by, and locked_at to nil:
(for each job you want to retry):
d.last_error = nil
d.run_at = Time.now
d.failed_at = nil
d.locked_at = nil
d.locked_by = nil
d.attempts = 0
d.failed_at = nil # needed in Rails 5 / delayed_job (4.1.2)
d.save!

if you have failed delayed job which you need to re-run, then you will need to only select them and set everything refer to failed retry to null:
Delayed::Job.where("last_error is not null").each do |dj|
dj.run_at = Time.now.advance(seconds: 5)
dj.locked_at = nil
dj.locked_by = nil
dj.attempts = 0
dj.last_error = nil
dj.failed_at = nil
dj.save
end

In a development environment, through rails console, following Joe Martinez's suggestion, a good way to retry all your delayed jobs is:
Delayed::Job.all.each{|d| d.run_at = Time.now; d.save!}

Delayed::Job.all.each(&:invoke_job)

Put this in an initializer file!
module Delayed
module Backend
module ActiveRecord
class Job
def retry!
self.run_at = Time.now - 1.day
self.locked_at = nil
self.locked_by = nil
self.attempts = 0
self.last_error = nil
self.failed_at = nil
self.save!
end
end
end
end
end
Then you can run Delayed::Job.find(1234).retry!
This will basically stick the job back into the queue and process it normally.

Related

How to check status with a rake task

i'm trying to make a rake task to run it with scheduler on heroku, but first im testing locally so i have a method where i check the status of polls like this
def check_status
if Date.today.between?(self.start_date, self.expiration_date)
self.poll_active = true
else
self.poll_active = false
end
end
and its working great but now i want this exact method to run it with a task.
i create my task file
namespace :change_poll_status do
task :poll_status => :environment do
if Date.today.between?(Poll.start_date, Poll.expiration_date)
Poll.poll_active = true
puts "It works"
else
Poll.poll_active = false
puts "no"
end
end
end
but when i run rake change_poll_status:poll_status
nothing happens it just skip like there is nothing to run, no errors, nothing.
The error is in this line:
if Date.today.between?(Poll.start_date, Poll.expiration_date)
You're trying to compare today's date with two class methods, start_date and expiration_date. These methods don't exist on the Poll class.
To fix this, you need to first retrieve an instance of the Poll class, and then call the methods on that instance. For example:
poll = Poll.first
if Date.today.between?(poll.start_date, poll.expiration_date)
poll.poll_active = true
puts "It works"
else
poll.poll_active = false
puts "no"
end

Rails set(wait: 2.minutes) method for active job does not works

Creating an background job with the resque_scheduler gem on Redis server.
class Estamps::OrderAssignment < ActiveRecord::Base
after_save :enqueue_check_status
def enqueue_check_status
AutoRejectionJob.set(wait: 2.minutes).perform_later(self.id)
end
end
class AutoRejectionJob < ActiveJob::Base
queue_as :default
def perform(*args)
order_assignment_id = args[0]
order_assignment = Estamps::OrderAssignment.find(order_assignment_id)
if order_assignment.status_id == 1 || order_assignment.status_id == nil
order_assignment.status_id = 3
order_assignment.save!
end
end
end
On creation of OrderAssignment record or when updated after 2 minutes it should run AutoRejection Job. Here the prob is the set(wait: 2.minutes) does not seem to run, i.e.
AutoRejectionJob.perform_later(self.id)
works perfectly fine, but
AutoRejectionJob.set(wait: 2.minutes).perform_later(self.id)
does nothing. Haven't been able to rectify the issue. Newbie to Rails so please help.
I see no problem with your code.
I checked : .set(wait: 2.minutes) works as expected in rails 5.0.2 on top of ruby 2.4.0
So does your call of the job.
The way I see it, you're trying to set a status used elsewhere.
Probably, the error is due to the OrderAssignment being manipulated in an outside treatment (destroyed ?)
Since you said you're new to rails (I suppose that's what "newbie" means) I'm going to make some suggestions. Disregard them if you're past that ...
There also are some great debugging tools out there to help you find what's going on : byebug, better_errors, pry and of course, the rails console.
Do yourself a favor : try them.
When I can't find my way around some behavior that goes beyond my grasp, I use some "puts", and some "try / catch errors" structures (begin rescue ensure in ruby)... :
def perform(*args)
puts "####### JOB TRIGGERED ######"
begin
order_assignment_id = args[0]
order_assignment = Estamps::OrderAssignment.find(order_assignment_id)
puts "#{order_assignment.inspect}"
if order_assignment.status_id == 1 || order_assignment.status_id == nil
order_assignment.status_id = 3
order_assignment.save!
end
puts "####### JOB DONE ######"
rescue StandardError => e
# ... display e.message ...
ensure
#...
end
end
check your rails version.
check your rails logs ( log folder, all the jobs will write message to log files when performed )

how to delete a job in sidekiq

I am using sidekiq in my rails app. Users of my app create reports that start a sidekiq job. However, sometimes users want to be able to cancel "processing" reports. Deleting the report is easy but I also need to be able to delete the sidekiq job as well.
So far I have been able to get a list of workers like so:
workers = Sidekiq::Workers.new
and each worker has args that include a report_id so I can identify which job belongs to which report. However, I'm not sure how to actually delete the job. It should be noted that I want to delete the job whether it is currently busy, or set in retry.
According to this Sidekiq documentation page to delete a job with a single id you need to iterate the queue and call .delete on it.
queue = Sidekiq::Queue.new("mailer")
queue.each do |job|
job.klass # => 'MyWorker'
job.args # => [1, 2, 3]
job.delete if job.jid == 'abcdef1234567890'
end
There is also a plugin called sidekiq-status that provides you the ability to cancel a single job
scheduled_job_id = MyJob.perform_in 3600
Sidekiq::Status.cancel scheduled_job_id #=> true
The simplest way I found to do this is:
job = Sidekiq::ScheduledSet.new.find_job([job_id])
where [job_id] is the JID that pertains to the report. Followed by:
job.delete
I found no need to iterate through the entire queue as described by other answers here.
I had the same problem, but the difference is that I needed to cancel a scheduled job, and my solution is:
Sidekiq::ScheduledSet.new.each do |_job|
next unless [online_jid, offline_jid].include? _job.jid
status = _job.delete
end
If you want to cancel a scheduled job, I'm not sure about #KimiGao's answer, but this is what I adapted from Sidekiq's current API documentation:
jid = MyCustomWorker.perform_async
r = Sidekiq::ScheduledSet.new
jobs = r.select{|job| job.jid == jid }
jobs.each(&:delete)
Hope it helps.
You can delete sidekiq job filtering by worker class and args:
class UserReportsWorker
include Sidekiq::Worker
def perform(report_id)
# ...
end
end
jobs = Sidekiq::ScheduledSet.new.select do |retri|
retri.klass == "UserReportsWorker" && retri.args == [42]
end
jobs.each(&:delete)
I had the same problem.
I solved it by registering the job id when I initialize it and by creating another function cancel! to delete it.
Here is the code:
after_enqueue do |job|
sidekiq_job = nil
queue = Sidekiq::Queue.new
sidekiq_job = queue.detect do |j|
j.item['args'][0]['job_id'] == job.job_id
end
if sidekiq_job.nil?
scheduled = Sidekiq::ScheduledSet.new
sidekiq_job = scheduled.detect do |j|
j.item['args'][0]['job_id'] == job.job_id
end
end
if sidekiq_job.present?
booking = job.arguments.first
booking.close_comments_jid = sidekiq_job.jid
booking.save
end
end
def perform(booking)
# do something
end
def self.cancel!(booking)
queue = Sidekiq::Queue.new
sidekiq_job = queue.find_job(booking.close_comments_jid)
if sidekiq_job.nil?
scheduled = Sidekiq::ScheduledSet.new
sidekiq_job = scheduled.find_job(booking.close_comments_jid)
end
if sidekiq_job.nil?
# Report bug in my Bug Tracking tool
else
sidekiq_job.delete
end
end
There is simple way of deleting a job if you know the job_id:
job = Sidekiq::ScheduledSet.new.find_job(job_id)
begin
job.delete
rescue
Rails.logger.error "Job: (job_id: #{job_id}) not found while deleting jobs."
end
Or you can use sidekiq page on rails server.
For example, http://localhost:3000/sidekiq, you can stop/remove the sidekiq jobs.
Before that, you have to updates the routes.rb.
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'

Check whether I am in a delayed_job process or not

I have a Rails app in which I use delayed_job. I want to detect whether I am in a delayed_job process or not; something like
if in_delayed_job?
# do something only if it is a delayed_job process...
else
# do something only if it is not a delayed_job process...
end
But I can't figure out how. This is what I'm using now:
IN_DELAYED_JOB = begin
basename = File.basename $0
arguments = $*
rake_args_regex = /\Ajobs:/
( basename == 'delayed_job' ) ||
( basename == 'rake' && arguments.find{ |v| v =~ rake_args_regex } )
end
Another solution is, as #MrDanA said:
$ DELAYED_JOB=true script/delayed_job start
# And in the app:
IN_DELAYED_JOB = ENV['DELAYED_JOB'].present?
but they are IMHO weak solutions. Can anyone suggest a better solution?
The way that I handle these is through a Paranoid worker. I use delayed_job for video transcoding that was uploaded to my site. Within the model of the video, I have a field called video_processing which is set to 0/null by default. Whenever the video is being transcoded by the delayed_job (whether on create or update of the video file), it will use the hooks from delayed_job and will update the video_processing whenever the job starts. Once the job is completed, the completed hook will update the field to 0.
In my view/controller I can do video.video_processing? ? "Video Transcoding in Progress" : "Video Fished Transcoding"
Maybe something like this. Add a field to your class and set it when your invoke the method that does all your work from delayed job:
class User < ActiveRecord::Base
attr_accessor :in_delayed_job
def queue_calculation_request
Delayed::Job.enqueue(CalculationRequest.new(self.id))
end
def do_the_work
if (in_delayed_job)
puts "Im in delayed job"
else
puts "I was called directly"
end
end
class CalculationRequest < Struct.new(:id)
def perform
user = User.find(id)
user.in_delayed_job = true
user.do_the_work
end
def display_name
"Perform the needeful user Calculations"
end
end
end
Here is how it looks:
From Delayed Job:
Worker(host:Johns-MacBook-Pro.local pid:67020)] Starting job worker
Im in delayed job
[Worker(host:Johns-MacBook-Pro.local pid:67020)] Perform the needeful user Calculations completed after 0.2787
[Worker(host:Johns-MacBook-Pro.local pid:67020)] 1 jobs processed at 1.5578 j/s, 0 failed ...
From the console
user = User.first.do_the_work
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 101]]
I was called directly
This works for me:
def delayed_job_worker?
(ENV["_"].include? "delayed_job")
end
Unix will set the "_" environment variable to the current command.
It'll be wrong if you have a bin script called "not_a_delayed_job", but don't do that.
How about ENV['PROC_TYPE']
Speaking only of heroku... but when you're a worker dyno, this is set to 'worker'
I use it as my "I'm in a DJ"
You can create a plugin for delayed job, e.g. create the file is_dj_job_plugin.rb in the config/initializers directory.
class IsDjJobPlugin < Delayed::Plugin
callbacks do |lifecycle|
lifecycle.around(:invoke_job) do |job, *args, &block|
begin
old_is_dj_job = Thread.current[:is_dj_job]
Thread.current[:is_dj_job] = true
block.call(job, *args) # Forward the call to the next callback in the callback chain
Thread.current[:is_dj_job] = old_is_dj_job
end
end
end
def self.is_dj_job?
Thread.current[:is_dj_job] == true
end
end
Delayed::Worker.plugins << IsDjJobPlugin
You can then test in the following way:
class PrintDelayedStatus
def run
puts IsDjJobPlugin.is_dj_job? ? 'delayed' : 'not delayed'
end
end
PrintDelayedStatus.new.run
PrintDelayedStatus.new.delay.run

Ruby on Rails - weird behaviour logic by delayed job

I am doing the delayed_job by tobi and when I run the delayed_job but the fbLikes count is all wrong and it seems to increment each time I add one more company. Not sure wheres the logic wrong. The fbLikes method I tested before and it work(before I changed to delayed_job)
not sure where the "1" come from...
[output]
coca-cola
http://www.cocacola.com
Likes: 1 <--- Not sure why the fbLikes is 1 and it increment with second company fbLikes is 2 and so on...
.
[Worker(host:aname.local pid:1400)] Starting job worker
[Worker(host:aname.local pid:1400)] CountJob completed after 0.7893
[Worker(host:aname.local pid:1400)] 1 jobs processed at 1.1885 j/s, 0 failed ...
I am running the delayed_job in Model and trying to run the job of
counting the facebook likes
here is my code.
[lib/count_rb.job]
require 'net/http'
class CountJob< Struct.new(:fbid)
def perform
uri = URI("http://graph.facebook.com/#{fbid}")
data = Net::HTTP.get(uri)
return JSON.parse(data)['likes']
end
end
[Company model]
before_save :fb_likes
def fb_likes
self.fbLikes = Delayed::Job.enqueue(CountJob.new(self.fbId))
end
the issue is coming from
before_save :fb_likes
def fb_likes
self.fbLikes = Delayed::Job.enqueue(CountJob.new(self.fbId))
end
the enqueue method will not return the results of running the CountJob. I believe it will return whether the job successfully enqueued or not and when you are saving this to the fb_likes value it will evaluate to 1 when the job is enqueued successfully.
You should be setting fbLikes inside the job that is being run by delayed_job not as a result of the enqueue call.
before_save :enqueue_fb_likes
def fb_likes
Delayed::Job.enqueue(CountJob.new(self.fbId))
end
Your perform method in the CountJob class should probably take the model id for you to look up and have access to the fbId and the fbLikes attributes instead of just taking the fbId.
class CountJob< Struct.new(:id)
def perform
company = Company.find(id)
uri = URI("http://graph.facebook.com/#{company.fbid}")
data = Net::HTTP.get(uri)
company.fbLikes = JSON.parse(data)['likes']
company.save
end

Resources