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
Related
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 )
I have the following worker:
class ImageWorker
include Sidekiq::Worker
def perform(tenant_id, id, key)
tenant = Tenant.find(tenant_id)
tenant.scope_schema do
image = Image.find(id)
unless image.image_processed?
image.key = key
image.remote_image_url = image.image.direct_fog_url(with_path: true)
image.save!
image.update_column(:image_processed, true)
end
end
end
end
The Tenant#scope_schema method looks like this:
def scope_schema(*paths)
original_search_path = ActiveRecord::Base.connection.schema_search_path
paths << "extensions"
ActiveRecord::Base.connection.schema_search_path = ["tenant#{id}", *paths].join(",")
yield
ensure
ActiveRecord::Base.connection.schema_search_path = original_search_path
end
When the ImageWorker job runs, it tells me that it can't find an Image with id=7 so the scope_schema doesn't appear to be working although I can take the same code outside of a Sidekiq worker class and it runs just fine.
Use after_commit to ensure the database record is there when the job executes.
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'
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
I have the following script which runs once a day on cron on heroku.
However, I realize that I would like the option for the user to be able to press a button from a web page to initiate this same process.
Is there a way to create a 'subroutine' that either cron can call or from a web request? I don't want to use a separate service that runs jobs.
I've just put a snippet to illustrate.....
letter_todos = Todo.current_date_lte(Date.today).asset_is("Letter").done_date_null
unless letter_todos.blank? #check if there any ToDos
# group by asset_id so that each batch is specific to the asset_id
letter_todos.group_by(&:asset_id).each do |asset_id, letter_todos|
# pdf = Prawn::Document.new(:margin => 100) #format the PDF document
html_file = ''
letter_todos.each do |todo| #loop through all Letter_Todos
contact = Contact.find(todo.contact_id) #get associated contact
letter = Letter.find(todo.asset_id) #get associated Letter
redcloth_contact_letter = RedCloth.new(letter.substituted_message(contact, [])).to_html
html_file = html_file + redcloth_contact_letter
html_file = html_file + "<p style='display: none; page-break-after: always'><center> ... </center> </p>"
end
kit = PDFKit.new(html_file)
kit.stylesheets << "#{RAILS_ROOT}/public/stylesheets/compiled/pdf.css"
file = kit.to_pdf
letter = Letter.find(asset_id)
#OutboundMailer.deliver_pdf_email(file)
kit.to_file("#{RAILS_ROOT}/tmp/PDF-#{letter.title}-#{Date.today}.pdf")
# Create new BatchPrint record
batch = BatchPrint.new
batch.pdf = File.new("#{RAILS_ROOT}/tmp/PDF-#{letter.title}-#{Date.today}.pdf")
I've done this by putting the function in question in a file in lib (lib/tasks_n_stuff.rb, say):
module TasksNStuff
def self.do_something
# ...doing something...
end
end
Then I can call if from a Rake task:
desc 'Make sure we depend on :environment, so we can get to the Railsy stuff...'
task :do_something => :environment do
TasksNStuff.do_something
end
Or from a controller (or anywhere, really):
class WhateverController < ApplicationController
def do_something
TasksNStuff.do_something
end
end
And since you can run a rake task as a cron job (cd /my/rails/root; rake do_something), that should be all you need. Cheers!