delayed-job-4.0.6/0000755000175000017500000000000012531115467015173 5ustar balasankarcbalasankarcdelayed-job-4.0.6/LICENSE.md0000644000175000017500000000204012531115467016573 0ustar balasankarcbalasankarcCopyright (c) 2005 Tobias Lütke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. delayed-job-4.0.6/delayed_job.gemspec0000644000175000017500000000210312531115467020775 0ustar balasankarcbalasankarcGem::Specification.new do |spec| spec.add_dependency 'activesupport', ['>= 3.0', '< 5.0'] spec.authors = ['Brandon Keepers', 'Brian Ryckbost', 'Chris Gaffney', 'David Genord II', 'Erik Michaels-Ober', 'Matt Griffin', 'Steve Richert', 'Tobias Lütke'] spec.description = 'Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.' spec.email = ['brian@collectiveidea.com'] spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job.gemspec] spec.files += Dir.glob('{contrib,lib,recipes,spec}/**/*') spec.homepage = 'http://github.com/collectiveidea/delayed_job' spec.licenses = ['MIT'] spec.name = 'delayed_job' spec.require_paths = ['lib'] spec.summary = 'Database-backed asynchronous priority queue system -- Extracted from Shopify' spec.test_files = Dir.glob('spec/**/*') spec.version = '4.0.6' end delayed-job-4.0.6/contrib/0000755000175000017500000000000012531115467016633 5ustar balasankarcbalasankarcdelayed-job-4.0.6/contrib/delayed_job_rails_4_multiple.monitrc0000644000175000017500000000310512531115467026020 0ustar balasankarcbalasankarc# an example Monit configuration file for delayed_job running multiple processes # # To use: # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc # 2. replace {app_name} as appropriate # you might also need to change the program strings to # "/bin/su - {username} -c '/usr/bin/env ...'" # to load your shell environment. # # 3. add this to your /etc/monit/monitrc # # include /var/www/apps/{app_name}/shared/delayed_job.monitrc # # The processes are grouped so that monit can act on them as a whole, e.g. # # monit -g delayed_job restart check process delayed_job_0 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.0.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 0" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 0" group delayed_job check process delayed_job_1 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.1.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 1" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 1" group delayed_job check process delayed_job_2 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.2.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 2" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 2" group delayed_job delayed-job-4.0.6/contrib/delayed_job_multiple.monitrc0000644000175000017500000000312712531115467024407 0ustar balasankarcbalasankarc# an example Monit configuration file for delayed_job running multiple processes # # To use: # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc # 2. replace {app_name} as appropriate # you might also need to change the program strings to # "/bin/su - {username} -c '/usr/bin/env ...'" # to load your shell environment. # # 3. add this to your /etc/monit/monitrc # # include /var/www/apps/{app_name}/shared/delayed_job.monitrc # # The processes are grouped so that monit can act on them as a whole, e.g. # # monit -g delayed_job restart check process delayed_job_0 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.0.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 0" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 0" group delayed_job check process delayed_job_1 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.1.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 1" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 1" group delayed_job check process delayed_job_2 with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.2.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 2" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 2" group delayed_job delayed-job-4.0.6/contrib/delayed_job.monitrc0000644000175000017500000000126112531115467022471 0ustar balasankarcbalasankarc# an example Monit configuration file for delayed_job # See: http://stackoverflow.com/questions/1226302/how-to-monitor-delayedjob-with-monit/1285611 # # To use: # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc # 2. replace {app_name} as appropriate # 3. add this to your /etc/monit/monitrc # # include /var/www/apps/{app_name}/shared/delayed_job.monitrc check process delayed_job with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop" delayed-job-4.0.6/contrib/delayed_job_rails_4.monitrc0000644000175000017500000000125312531115467024107 0ustar balasankarcbalasankarc# an example Monit configuration file for delayed_job # See: http://stackoverflow.com/questions/1226302/how-to-monitor-delayedjob-with-monit/1285611 # # To use: # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc # 2. replace {app_name} as appropriate # 3. add this to your /etc/monit/monitrc # # include /var/www/apps/{app_name}/shared/delayed_job.monitrc check process delayed_job with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start" stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop" delayed-job-4.0.6/Rakefile0000644000175000017500000000042312531115467016637 0ustar balasankarcbalasankarcrequire 'bundler/setup' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' desc 'Run the specs' RSpec::Core::RakeTask.new do |r| r.verbose = false end task :test => :spec require 'rubocop/rake_task' RuboCop::RakeTask.new task :default => [:spec, :rubocop] delayed-job-4.0.6/metadata.yml0000644000175000017500000000727412531115467017510 0ustar balasankarcbalasankarc--- !ruby/object:Gem::Specification name: delayed_job version: !ruby/object:Gem::Version version: 4.0.6 platform: ruby authors: - Brandon Keepers - Brian Ryckbost - Chris Gaffney - David Genord II - Erik Michaels-Ober - Matt Griffin - Steve Richert - Tobias Lütke autorequire: bindir: bin cert_chain: [] date: 2014-12-22 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '3.0' - - "<" - !ruby/object:Gem::Version version: '5.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '3.0' - - "<" - !ruby/object:Gem::Version version: '5.0' description: Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. email: - brian@collectiveidea.com executables: [] extensions: [] extra_rdoc_files: [] files: - CHANGELOG.md - CONTRIBUTING.md - LICENSE.md - README.md - Rakefile - contrib/delayed_job.monitrc - contrib/delayed_job_multiple.monitrc - contrib/delayed_job_rails_4.monitrc - contrib/delayed_job_rails_4_multiple.monitrc - delayed_job.gemspec - lib/delayed/backend/base.rb - lib/delayed/backend/shared_spec.rb - lib/delayed/command.rb - lib/delayed/compatibility.rb - lib/delayed/deserialization_error.rb - lib/delayed/exceptions.rb - lib/delayed/lifecycle.rb - lib/delayed/message_sending.rb - lib/delayed/performable_mailer.rb - lib/delayed/performable_method.rb - lib/delayed/plugin.rb - lib/delayed/plugins/clear_locks.rb - lib/delayed/psych_ext.rb - lib/delayed/railtie.rb - lib/delayed/recipes.rb - lib/delayed/serialization/active_record.rb - lib/delayed/syck_ext.rb - lib/delayed/tasks.rb - lib/delayed/worker.rb - lib/delayed/yaml_ext.rb - lib/delayed_job.rb - lib/generators/delayed_job/delayed_job_generator.rb - lib/generators/delayed_job/templates/script - recipes/delayed_job.rb - spec/autoloaded/clazz.rb - spec/autoloaded/instance_clazz.rb - spec/autoloaded/instance_struct.rb - spec/autoloaded/struct.rb - spec/delayed/backend/test.rb - spec/delayed/command_spec.rb - spec/delayed/serialization/test.rb - spec/helper.rb - spec/lifecycle_spec.rb - spec/message_sending_spec.rb - spec/performable_mailer_spec.rb - spec/performable_method_spec.rb - spec/psych_ext_spec.rb - spec/sample_jobs.rb - spec/test_backend_spec.rb - spec/worker_spec.rb - spec/yaml_ext_spec.rb homepage: http://github.com/collectiveidea/delayed_job licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.4 signing_key: specification_version: 4 summary: Database-backed asynchronous priority queue system -- Extracted from Shopify test_files: - spec/autoloaded/clazz.rb - spec/autoloaded/instance_clazz.rb - spec/autoloaded/instance_struct.rb - spec/autoloaded/struct.rb - spec/delayed/backend/test.rb - spec/delayed/command_spec.rb - spec/delayed/serialization/test.rb - spec/helper.rb - spec/lifecycle_spec.rb - spec/message_sending_spec.rb - spec/performable_mailer_spec.rb - spec/performable_method_spec.rb - spec/psych_ext_spec.rb - spec/sample_jobs.rb - spec/test_backend_spec.rb - spec/worker_spec.rb - spec/yaml_ext_spec.rb delayed-job-4.0.6/lib/0000755000175000017500000000000012531115467015741 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/delayed_job.rb0000644000175000017500000000117712531115467020535 0ustar balasankarcbalasankarcrequire 'active_support' require 'delayed/compatibility' require 'delayed/exceptions' require 'delayed/message_sending' require 'delayed/performable_method' if defined?(ActionMailer) require 'action_mailer/version' require 'delayed/performable_mailer' end require 'delayed/yaml_ext' require 'delayed/lifecycle' require 'delayed/plugin' require 'delayed/plugins/clear_locks' require 'delayed/backend/base' require 'delayed/worker' require 'delayed/deserialization_error' require 'delayed/railtie' if defined?(Rails::Railtie) Object.send(:include, Delayed::MessageSending) Module.send(:include, Delayed::MessageSending::ClassMethods) delayed-job-4.0.6/lib/delayed/0000755000175000017500000000000012531115467017350 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/delayed/railtie.rb0000644000175000017500000000072212531115467021327 0ustar balasankarcbalasankarcrequire 'delayed_job' require 'rails' module Delayed class Railtie < Rails::Railtie initializer :after_initialize do ActiveSupport.on_load(:action_mailer) do ActionMailer::Base.extend(Delayed::DelayMail) end Delayed::Worker.logger ||= if defined?(Rails) Rails.logger elsif defined?(RAILS_DEFAULT_LOGGER) RAILS_DEFAULT_LOGGER end end rake_tasks do load 'delayed/tasks.rb' end end end delayed-job-4.0.6/lib/delayed/syck_ext.rb0000644000175000017500000000207612531115467021533 0ustar balasankarcbalasankarcclass Module yaml_as 'tag:ruby.yaml.org,2002:module' def self.yaml_new(_klass, _tag, val) val.constantize end def to_yaml(options = {}) YAML.quick_emit(nil, options) do |out| out.scalar(taguri, name, :plain) end end def yaml_tag_read_class(name) # Constantize the object so that ActiveSupport can attempt # its auto loading magic. Will raise LoadError if not successful. name.constantize name end end class Class yaml_as 'tag:ruby.yaml.org,2002:class' remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml end class Struct def self.yaml_tag_read_class(name) # Constantize the object so that ActiveSupport can attempt # its auto loading magic. Will raise LoadError if not successful. name.constantize "Struct::#{ name }" end end module YAML def load_dj(yaml) # See https://github.com/dtao/safe_yaml # When the method is there, we need to load our YAML like this... respond_to?(:unsafe_load) ? load(yaml, :safe => false) : load(yaml) end end delayed-job-4.0.6/lib/delayed/command.rb0000644000175000017500000001206012531115467021312 0ustar balasankarcbalasankarcunless ENV['RAILS_ENV'] == 'test' begin require 'daemons' rescue LoadError raise "You need to add gem 'daemons' to your Gemfile if you wish to use it." end end require 'optparse' module Delayed class Command # rubocop:disable ClassLength attr_accessor :worker_count, :worker_pools def initialize(args) # rubocop:disable MethodLength @options = { :quiet => true, :pid_dir => "#{Rails.root}/tmp/pids" } @worker_count = 1 @monitor = false opts = OptionParser.new do |opt| opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] start|stop|restart|run" opt.on('-h', '--help', 'Show this message') do puts opt exit 1 end opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e| STDERR.puts 'The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7' end opt.on('--min-priority N', 'Minimum priority of jobs to run.') do |n| @options[:min_priority] = n end opt.on('--max-priority N', 'Maximum priority of jobs to run.') do |n| @options[:max_priority] = n end opt.on('-n', '--number_of_workers=workers', 'Number of unique workers to spawn') do |worker_count| @worker_count = worker_count.to_i rescue 1 # rubocop:disable RescueModifier end opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir| @options[:pid_dir] = dir end opt.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n| @options[:identifier] = n end opt.on('-m', '--monitor', 'Start monitor process.') do @monitor = true end opt.on('--sleep-delay N', 'Amount of time to sleep when no jobs are found') do |n| @options[:sleep_delay] = n.to_i end opt.on('--read-ahead N', 'Number of jobs from the queue to consider') do |n| @options[:read_ahead] = n end opt.on('-p', '--prefix NAME', 'String to be prefixed to worker process names') do |prefix| @options[:prefix] = prefix end opt.on('--queues=queues', 'Specify which queue DJ must look up for jobs') do |queues| @options[:queues] = queues.split(',') end opt.on('--queue=queue', 'Specify which queue DJ must look up for jobs') do |queue| @options[:queues] = queue.split(',') end opt.on('--pool=queue1[,queue2][:worker_count]', 'Specify queues and number of workers for a worker pool') do |pool| parse_worker_pool(pool) end opt.on('--exit-on-complete', 'Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.') do @options[:exit_on_complete] = true end end @args = opts.parse!(args) end def daemonize # rubocop:disable PerceivedComplexity dir = @options[:pid_dir] Dir.mkdir(dir) unless File.exist?(dir) if worker_pools setup_pools elsif @options[:identifier] if worker_count > 1 raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier' else run_process("delayed_job.#{@options[:identifier]}", @options) end else worker_count.times do |worker_index| process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}" run_process(process_name, @options) end end end def setup_pools worker_index = 0 @worker_pools.each do |queues, worker_count| options = @options.merge(:queues => queues) worker_count.times do process_name = "delayed_job.#{worker_index}" run_process(process_name, options) worker_index += 1 end end end def run_process(process_name, options = {}) Delayed::Worker.before_fork Daemons.run_proc(process_name, :dir => options[:pid_dir], :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*_args| $0 = File.join(options[:prefix], process_name) if @options[:prefix] run process_name, options end end def run(worker_name = nil, options = {}) Dir.chdir(Rails.root) Delayed::Worker.after_fork Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) worker = Delayed::Worker.new(options) worker.name_prefix = "#{worker_name} " worker.start rescue => e Rails.logger.fatal e STDERR.puts e.message exit 1 end private def parse_worker_pool(pool) @worker_pools ||= [] queues, worker_count = pool.split(':') if ['*', '', nil].include?(queues) queues = [] else queues = queues.split(',') end worker_count = (worker_count || 1).to_i rescue 1 @worker_pools << [queues, worker_count] end end end delayed-job-4.0.6/lib/delayed/performable_mailer.rb0000644000175000017500000000075212531115467023530 0ustar balasankarcbalasankarcrequire 'mail' module Delayed class PerformableMailer < PerformableMethod def perform mailer = object.send(method_name, *args) mailer.respond_to?(:deliver_now) ? mailer.deliver_now : mailer.deliver end end module DelayMail def delay(options = {}) DelayProxy.new(PerformableMailer, self, options) end end end Mail::Message.class_eval do def delay(*_args) raise 'Use MyMailer.delay.mailer_action(args) to delay sending of emails.' end end delayed-job-4.0.6/lib/delayed/exceptions.rb0000644000175000017500000000045512531115467022062 0ustar balasankarcbalasankarcrequire 'timeout' module Delayed class WorkerTimeout < Timeout::Error def message seconds = Delayed::Worker.max_run_time.to_i "#{super} (Delayed::Worker.max_run_time is only #{seconds} second#{seconds == 1 ? '' : 's'})" end end class FatalBackendError < Exception; end end delayed-job-4.0.6/lib/delayed/worker.rb0000644000175000017500000002257412531115467021220 0ustar balasankarcbalasankarcrequire 'timeout' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/kernel' require 'active_support/core_ext/enumerable' require 'logger' require 'benchmark' module Delayed class Worker # rubocop:disable ClassLength DEFAULT_LOG_LEVEL = 'info' DEFAULT_SLEEP_DELAY = 5 DEFAULT_MAX_ATTEMPTS = 25 DEFAULT_MAX_RUN_TIME = 4.hours DEFAULT_DEFAULT_PRIORITY = 0 DEFAULT_DELAY_JOBS = true DEFAULT_QUEUES = [] DEFAULT_READ_AHEAD = 5 cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :delay_jobs, :queues, :read_ahead, :plugins, :destroy_failed_jobs, :exit_on_complete, :default_log_level # Named queue into which jobs are enqueued by default cattr_accessor :default_queue_name cattr_reader :backend # name_prefix is ignored if name is set directly attr_accessor :name_prefix def self.reset self.default_log_level = DEFAULT_LOG_LEVEL self.sleep_delay = DEFAULT_SLEEP_DELAY self.max_attempts = DEFAULT_MAX_ATTEMPTS self.max_run_time = DEFAULT_MAX_RUN_TIME self.default_priority = DEFAULT_DEFAULT_PRIORITY self.delay_jobs = DEFAULT_DELAY_JOBS self.queues = DEFAULT_QUEUES self.read_ahead = DEFAULT_READ_AHEAD end reset # Add or remove plugins in this list before the worker is instantiated self.plugins = [Delayed::Plugins::ClearLocks] # By default failed jobs are destroyed after too many attempts. If you want to keep them around # (perhaps to inspect the reason for the failure), set this to false. self.destroy_failed_jobs = true # By default, Signals INT and TERM set @exit, and the worker exits upon completion of the current job. # If you would prefer to raise a SignalException and exit immediately you can use this. # Be aware daemons uses TERM to stop and restart # false - No exceptions will be raised # :term - Will only raise an exception on TERM signals but INT will wait for the current job to finish # true - Will raise an exception on TERM and INT cattr_accessor :raise_signal_exceptions self.raise_signal_exceptions = false def self.backend=(backend) if backend.is_a? Symbol require "delayed/serialization/#{backend}" require "delayed/backend/#{backend}" backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize end @@backend = backend # rubocop:disable ClassVars silence_warnings { ::Delayed.const_set(:Job, backend) } end def self.guess_backend warn '[DEPRECATION] guess_backend is deprecated. Please remove it from your code.' end def self.before_fork unless @files_to_reopen @files_to_reopen = [] ObjectSpace.each_object(File) do |file| @files_to_reopen << file unless file.closed? end end backend.before_fork end def self.after_fork # Re-open file handles @files_to_reopen.each do |file| begin file.reopen file.path, 'a+' file.sync = true rescue ::Exception # rubocop:disable HandleExceptions, RescueException end end backend.after_fork end def self.lifecycle @lifecycle ||= Delayed::Lifecycle.new end def self.reload_app? defined?(ActionDispatch::Reloader) && Rails.application.config.cache_classes == false end def initialize(options = {}) @quiet = options.key?(:quiet) ? options[:quiet] : true @failed_reserve_count = 0 [:min_priority, :max_priority, :sleep_delay, :read_ahead, :queues, :exit_on_complete].each do |option| self.class.send("#{option}=", options[option]) if options.key?(option) end plugins.each { |klass| klass.new } end # Every worker has a unique name which by default is the pid of the process. There are some # advantages to overriding this with something which survives worker restarts: Workers can # safely resume working on tasks which are locked by themselves. The worker will assume that # it crashed before. def name return @name unless @name.nil? "#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}" # rubocop:disable RescueModifier end # Sets the name of the worker. # Setting the name to nil will reset the default worker name attr_writer :name def start # rubocop:disable CyclomaticComplexity, PerceivedComplexity trap('TERM') do Thread.new { say 'Exiting...' } stop raise SignalException, 'TERM' if self.class.raise_signal_exceptions end trap('INT') do Thread.new { say 'Exiting...' } stop raise SignalException, 'INT' if self.class.raise_signal_exceptions && self.class.raise_signal_exceptions != :term end say 'Starting job worker' self.class.lifecycle.run_callbacks(:execute, self) do loop do self.class.lifecycle.run_callbacks(:loop, self) do @realtime = Benchmark.realtime do @result = work_off end end count = @result.sum if count.zero? if self.class.exit_on_complete say 'No more jobs available. Exiting' break elsif !stop? sleep(self.class.sleep_delay) reload! end else say format("#{count} jobs processed at %.4f j/s, %d failed", count / @realtime, @result.last) end break if stop? end end end def stop @exit = true end def stop? !!@exit end # Do num jobs and return stats on success/failure. # Exit early if interrupted. def work_off(num = 100) success, failure = 0, 0 num.times do case reserve_and_run_one_job when true success += 1 when false failure += 1 else break # leave if no work could be done end break if stop? # leave if we're exiting end [success, failure] end def run(job) job_say job, 'RUNNING' runtime = Benchmark.realtime do Timeout.timeout(max_run_time(job).to_i, WorkerTimeout) { job.invoke_job } job.destroy end job_say job, format('COMPLETED after %.4f', runtime) return true # did work rescue DeserializationError => error job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}" failed(job) rescue => error self.class.lifecycle.run_callbacks(:error, self, job) { handle_failed_job(job, error) } return false # work failed end # Reschedule the job in the future (when a job fails). # Uses an exponential scale depending on the number of failed attempts. def reschedule(job, time = nil) if (job.attempts += 1) < max_attempts(job) time ||= job.reschedule_at job.run_at = time job.unlock job.save! else job_say job, "REMOVED permanently because of #{job.attempts} consecutive failures", 'error' failed(job) end end def failed(job) self.class.lifecycle.run_callbacks(:failure, self, job) do begin job.hook(:failure) rescue => error say "Error when running failure callback: #{error}", 'error' say error.backtrace.join("\n"), 'error' ensure self.class.destroy_failed_jobs ? job.destroy : job.fail! end end end def job_say(job, text, level = default_log_level) text = "Job #{job.name} (id=#{job.id}) #{text}" say text, level end def say(text, level = default_log_level) text = "[Worker(#{name})] #{text}" puts text unless @quiet return unless logger # TODO: Deprecate use of Fixnum log levels unless level.is_a?(String) level = Logger::Severity.constants.detect { |i| Logger::Severity.const_get(i) == level }.to_s.downcase end logger.send(level, "#{Time.now.strftime('%FT%T%z')}: #{text}") end def max_attempts(job) job.max_attempts || self.class.max_attempts end def max_run_time(job) job.max_run_time || self.class.max_run_time end protected def handle_failed_job(job, error) job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}" job_say job, "FAILED (#{job.attempts} prior attempts) with #{error.class.name}: #{error.message}", 'error' reschedule(job) end # Run the next job we can get an exclusive lock on. # If no jobs are left we return nil def reserve_and_run_one_job job = reserve_job self.class.lifecycle.run_callbacks(:perform, self, job) { run(job) } if job end def reserve_job job = Delayed::Job.reserve(self) @failed_reserve_count = 0 job rescue ::Exception => error # rubocop:disable RescueException say "Error while reserving job: #{error}" Delayed::Job.recover_from(error) @failed_reserve_count += 1 raise FatalBackendError if @failed_reserve_count >= 10 nil end def reload! return unless self.class.reload_app? ActionDispatch::Reloader.cleanup! ActionDispatch::Reloader.prepare! end end end delayed-job-4.0.6/lib/delayed/tasks.rb0000644000175000017500000000251112531115467021021 0ustar balasankarcbalasankarcnamespace :jobs do desc 'Clear the delayed_job queue.' task :clear => :environment do Delayed::Job.delete_all end desc 'Start a delayed_job worker.' task :work => :environment_options do Delayed::Worker.new(@worker_options).start end desc 'Start a delayed_job worker and exit when all available jobs are complete.' task :workoff => :environment_options do Delayed::Worker.new(@worker_options.merge(:exit_on_complete => true)).start end task :environment_options => :environment do @worker_options = { :min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY'], :queues => (ENV['QUEUES'] || ENV['QUEUE'] || '').split(','), :quiet => false } @worker_options[:sleep_delay] = ENV['SLEEP_DELAY'].to_i if ENV['SLEEP_DELAY'] @worker_options[:read_ahead] = ENV['READ_AHEAD'].to_i if ENV['READ_AHEAD'] end desc "Exit with error status if any jobs older than max_age seconds haven't been attempted yet." task :check, [:max_age] => :environment do |_, args| args.with_defaults(:max_age => 300) unprocessed_jobs = Delayed::Job.where('attempts = 0 AND created_at < ?', Time.now - args[:max_age].to_i).count if unprocessed_jobs > 0 raise "#{unprocessed_jobs} jobs older than #{args[:max_age]} seconds have not been processed yet" end end end delayed-job-4.0.6/lib/delayed/deserialization_error.rb0000644000175000017500000000010612531115467024271 0ustar balasankarcbalasankarcmodule Delayed class DeserializationError < StandardError end end delayed-job-4.0.6/lib/delayed/message_sending.rb0000644000175000017500000000333512531115467023034 0ustar balasankarcbalasankarcrequire 'active_support/core_ext/module/aliasing' module Delayed class DelayProxy < Delayed::Compatibility.proxy_object_class def initialize(payload_class, target, options) @payload_class = payload_class @target = target @options = options end def method_missing(method, *args) Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options)) end end module MessageSending def delay(options = {}) DelayProxy.new(PerformableMethod, self, options) end alias_method :__delay__, :delay def send_later(method, *args) warn '[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method' __delay__.__send__(method, *args) end def send_at(time, method, *args) warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method' __delay__(:run_at => time).__send__(method, *args) end module ClassMethods def handle_asynchronously(method, opts = {}) aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable PerlBackrefs with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}" define_method(with_method) do |*args| curr_opts = opts.clone curr_opts.each_key do |key| next unless (val = curr_opts[key]).is_a?(Proc) curr_opts[key] = if val.arity == 1 val.call(self) else val.call end end delay(curr_opts).__send__(without_method, *args) end alias_method_chain method, :delay end end end end delayed-job-4.0.6/lib/delayed/recipes.rb0000644000175000017500000000307512531115467021334 0ustar balasankarcbalasankarc# Capistrano Recipes for managing delayed_job # # Add these callbacks to have the delayed_job process restart when the server # is restarted: # # after "deploy:stop", "delayed_job:stop" # after "deploy:start", "delayed_job:start" # after "deploy:restart", "delayed_job:restart" # # If you want to use command line options, for example to start multiple workers, # define a Capistrano variable delayed_job_args: # # set :delayed_job_args, "-n 2" # # If you've got delayed_job workers running on a servers, you can also specify # which servers have delayed_job running and should be restarted after deploy. # # set :delayed_job_server_role, :worker # Capistrano::Configuration.instance.load do namespace :delayed_job do def rails_env fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : '' end def args fetch(:delayed_job_args, '') end def roles fetch(:delayed_job_server_role, :app) end def delayed_job_command fetch(:delayed_job_command, 'script/delayed_job') end desc 'Stop the delayed_job process' task :stop, :roles => lambda { roles } do run "cd #{current_path};#{rails_env} #{delayed_job_command} stop #{args}" end desc 'Start the delayed_job process' task :start, :roles => lambda { roles } do run "cd #{current_path};#{rails_env} #{delayed_job_command} start #{args}" end desc 'Restart the delayed_job process' task :restart, :roles => lambda { roles } do run "cd #{current_path};#{rails_env} #{delayed_job_command} restart #{args}" end end end delayed-job-4.0.6/lib/delayed/lifecycle.rb0000644000175000017500000000415312531115467021637 0ustar balasankarcbalasankarcmodule Delayed class InvalidCallback < Exception; end class Lifecycle EVENTS = { :enqueue => [:job], :execute => [:worker], :loop => [:worker], :perform => [:worker, :job], :error => [:worker, :job], :failure => [:worker, :job], :invoke_job => [:job] } def initialize @callbacks = EVENTS.keys.each_with_object({}) do |e, hash| hash[e] = Callback.new end end def before(event, &block) add(:before, event, &block) end def after(event, &block) add(:after, event, &block) end def around(event, &block) add(:around, event, &block) end def run_callbacks(event, *args, &block) missing_callback(event) unless @callbacks.key?(event) unless EVENTS[event].size == args.size raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}" end @callbacks[event].execute(*args, &block) end private def add(type, event, &block) missing_callback(event) unless @callbacks.key?(event) @callbacks[event].add(type, &block) end def missing_callback(event) raise InvalidCallback, "Unknown callback event: #{event}" end end class Callback def initialize @before = [] @after = [] # Identity proc. Avoids special cases when there is no existing around chain. @around = lambda { |*args, &block| block.call(*args) } end def execute(*args, &block) @before.each { |c| c.call(*args) } result = @around.call(*args, &block) @after.each { |c| c.call(*args) } result end def add(type, &callback) case type when :before @before << callback when :after @after << callback when :around chain = @around # use a local variable so that the current chain is closed over in the following lambda @around = lambda { |*a, &block| chain.call(*a) { |*b| callback.call(*b, &block) } } else raise InvalidCallback, "Invalid callback type: #{type}" end end end end delayed-job-4.0.6/lib/delayed/backend/0000755000175000017500000000000012531115467020737 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/delayed/backend/shared_spec.rb0000644000175000017500000005264212531115467023555 0ustar balasankarcbalasankarcrequire File.expand_path('../../../../spec/sample_jobs', __FILE__) require 'active_support/core_ext' shared_examples_for 'a delayed_job backend' do let(:worker) { Delayed::Worker.new } def create_job(opts = {}) described_class.create(opts.merge(:payload_object => SimpleJob.new)) end before do Delayed::Worker.max_priority = nil Delayed::Worker.min_priority = nil Delayed::Worker.default_priority = 99 Delayed::Worker.delay_jobs = true Delayed::Worker.default_queue_name = 'default_tracking' SimpleJob.runs = 0 described_class.delete_all end after do Delayed::Worker.reset end it 'sets run_at automatically if not set' do expect(described_class.create(:payload_object => ErrorJob.new).run_at).not_to be_nil end it 'does not set run_at automatically if already set' do later = described_class.db_time_now + 5.minutes job = described_class.create(:payload_object => ErrorJob.new, :run_at => later) expect(job.run_at).to be_within(1).of(later) end describe '#reload' do it 'reloads the payload' do job = described_class.enqueue :payload_object => SimpleJob.new expect(job.payload_object.object_id).not_to eq(job.reload.payload_object.object_id) end end describe 'enqueue' do context 'with a hash' do it "raises ArgumentError when handler doesn't respond_to :perform" do expect { described_class.enqueue(:payload_object => Object.new) }.to raise_error(ArgumentError) end it 'is able to set priority' do job = described_class.enqueue :payload_object => SimpleJob.new, :priority => 5 expect(job.priority).to eq(5) end it 'uses default priority' do job = described_class.enqueue :payload_object => SimpleJob.new expect(job.priority).to eq(99) end it 'is able to set run_at' do later = described_class.db_time_now + 5.minutes job = described_class.enqueue :payload_object => SimpleJob.new, :run_at => later expect(job.run_at).to be_within(1).of(later) end it 'is able to set queue' do job = described_class.enqueue :payload_object => NamedQueueJob.new, :queue => 'tracking' expect(job.queue).to eq('tracking') end it 'uses default queue' do job = described_class.enqueue :payload_object => SimpleJob.new expect(job.queue).to eq(Delayed::Worker.default_queue_name) end it "uses the payload object's queue" do job = described_class.enqueue :payload_object => NamedQueueJob.new expect(job.queue).to eq(NamedQueueJob.new.queue_name) end end context 'with multiple arguments' do it "raises ArgumentError when handler doesn't respond_to :perform" do expect { described_class.enqueue(Object.new) }.to raise_error(ArgumentError) end it 'increases count after enqueuing items' do described_class.enqueue SimpleJob.new expect(described_class.count).to eq(1) end it 'is able to set priority [DEPRECATED]' do silence_warnings do job = described_class.enqueue SimpleJob.new, 5 expect(job.priority).to eq(5) end end it 'uses default priority when it is not set' do @job = described_class.enqueue SimpleJob.new expect(@job.priority).to eq(99) end it 'is able to set run_at [DEPRECATED]' do silence_warnings do later = described_class.db_time_now + 5.minutes @job = described_class.enqueue SimpleJob.new, 5, later expect(@job.run_at).to be_within(1).of(later) end end it 'works with jobs in modules' do M::ModuleJob.runs = 0 job = described_class.enqueue M::ModuleJob.new expect { job.invoke_job }.to change { M::ModuleJob.runs }.from(0).to(1) end end context 'with delay_jobs = false' do before(:each) do Delayed::Worker.delay_jobs = false end it 'does not increase count after enqueuing items' do described_class.enqueue SimpleJob.new expect(described_class.count).to eq(0) end it 'invokes the enqueued job' do job = SimpleJob.new expect(job).to receive(:perform) described_class.enqueue job end it 'returns a job, not the result of invocation' do expect(described_class.enqueue(SimpleJob.new)).to be_instance_of(described_class) end end end describe 'callbacks' do before(:each) do CallbackJob.messages = [] end %w[before success after].each do |callback| it "calls #{callback} with job" do job = described_class.enqueue(CallbackJob.new) expect(job.payload_object).to receive(callback).with(job) job.invoke_job end end it 'calls before and after callbacks' do job = described_class.enqueue(CallbackJob.new) expect(CallbackJob.messages).to eq(['enqueue']) job.invoke_job expect(CallbackJob.messages).to eq(%w[enqueue before perform success after]) end it 'calls the after callback with an error' do job = described_class.enqueue(CallbackJob.new) expect(job.payload_object).to receive(:perform).and_raise(RuntimeError.new('fail')) expect { job.invoke_job }.to raise_error expect(CallbackJob.messages).to eq(['enqueue', 'before', 'error: RuntimeError', 'after']) end it 'calls error when before raises an error' do job = described_class.enqueue(CallbackJob.new) expect(job.payload_object).to receive(:before).and_raise(RuntimeError.new('fail')) expect { job.invoke_job }.to raise_error(RuntimeError) expect(CallbackJob.messages).to eq(['enqueue', 'error: RuntimeError', 'after']) end end describe 'payload_object' do it 'raises a DeserializationError when the job class is totally unknown' do job = described_class.new :handler => '--- !ruby/object:JobThatDoesNotExist {}' expect { job.payload_object }.to raise_error(Delayed::DeserializationError) end it 'raises a DeserializationError when the job struct is totally unknown' do job = described_class.new :handler => '--- !ruby/struct:StructThatDoesNotExist {}' expect { job.payload_object }.to raise_error(Delayed::DeserializationError) end it 'raises a DeserializationError when the YAML.load raises argument error' do job = described_class.new :handler => '--- !ruby/struct:GoingToRaiseArgError {}' expect(YAML).to receive(:load_dj).and_raise(ArgumentError) expect { job.payload_object }.to raise_error(Delayed::DeserializationError) end it 'raises a DeserializationError when the YAML.load raises syntax error' do # only test with Psych since the other YAML parsers don't raise a SyntaxError if YAML.parser.class.name !~ /syck|yecht/i job = described_class.new :handler => 'message: "no ending quote' expect { job.payload_object }.to raise_error(Delayed::DeserializationError) end end end describe 'reserve' do before do Delayed::Worker.max_run_time = 2.minutes end after do Time.zone = nil end it 'does not reserve failed jobs' do create_job :attempts => 50, :failed_at => described_class.db_time_now expect(described_class.reserve(worker)).to be_nil end it 'does not reserve jobs scheduled for the future' do create_job :run_at => described_class.db_time_now + 1.minute expect(described_class.reserve(worker)).to be_nil end it 'reserves jobs scheduled for the past' do job = create_job :run_at => described_class.db_time_now - 1.minute expect(described_class.reserve(worker)).to eq(job) end it 'reserves jobs scheduled for the past when time zones are involved' do Time.zone = 'US/Eastern' job = create_job :run_at => described_class.db_time_now - 1.minute expect(described_class.reserve(worker)).to eq(job) end it 'does not reserve jobs locked by other workers' do job = create_job other_worker = Delayed::Worker.new other_worker.name = 'other_worker' expect(described_class.reserve(other_worker)).to eq(job) expect(described_class.reserve(worker)).to be_nil end it 'reserves open jobs' do job = create_job expect(described_class.reserve(worker)).to eq(job) end it 'reserves expired jobs' do job = create_job(:locked_by => 'some other worker', :locked_at => described_class.db_time_now - Delayed::Worker.max_run_time - 1.minute) expect(described_class.reserve(worker)).to eq(job) end it 'reserves own jobs' do job = create_job(:locked_by => worker.name, :locked_at => (described_class.db_time_now - 1.minutes)) expect(described_class.reserve(worker)).to eq(job) end end context '#name' do it 'is the class name of the job that was enqueued' do expect(described_class.create(:payload_object => ErrorJob.new).name).to eq('ErrorJob') end it 'is the method that will be called if its a performable method object' do job = described_class.new(:payload_object => NamedJob.new) expect(job.name).to eq('named_job') end it 'is the instance method that will be called if its a performable method object' do job = Story.create(:text => '...').delay.save expect(job.name).to eq('Story#save') end it 'parses from handler on deserialization error' do job = Story.create(:text => '...').delay.text job.payload_object.object.destroy expect(job.reload.name).to eq('Delayed::PerformableMethod') end end context 'worker prioritization' do after do Delayed::Worker.max_priority = nil Delayed::Worker.min_priority = nil end it 'fetches jobs ordered by priority' do 10.times { described_class.enqueue SimpleJob.new, :priority => rand(10) } jobs = [] 10.times { jobs << described_class.reserve(worker) } expect(jobs.size).to eq(10) jobs.each_cons(2) do |a, b| expect(a.priority).to be <= b.priority end end it 'only finds jobs greater than or equal to min priority' do min = 5 Delayed::Worker.min_priority = min [4, 5, 6].sort_by { |_i| rand }.each { |i| create_job :priority => i } 2.times do job = described_class.reserve(worker) expect(job.priority).to be >= min job.destroy end expect(described_class.reserve(worker)).to be_nil end it 'only finds jobs less than or equal to max priority' do max = 5 Delayed::Worker.max_priority = max [4, 5, 6].sort_by { |_i| rand }.each { |i| create_job :priority => i } 2.times do job = described_class.reserve(worker) expect(job.priority).to be <= max job.destroy end expect(described_class.reserve(worker)).to be_nil end end context 'clear_locks!' do before do @job = create_job(:locked_by => 'worker1', :locked_at => described_class.db_time_now) end it 'clears locks for the given worker' do described_class.clear_locks!('worker1') expect(described_class.reserve(worker)).to eq(@job) end it 'does not clear locks for other workers' do described_class.clear_locks!('different_worker') expect(described_class.reserve(worker)).not_to eq(@job) end end context 'unlock' do before do @job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now) end it 'clears locks' do @job.unlock expect(@job.locked_by).to be_nil expect(@job.locked_at).to be_nil end end context 'large handler' do before do text = 'Lorem ipsum dolor sit amet. ' * 1000 @job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {}) end it 'has an id' do expect(@job.id).not_to be_nil end end context 'named queues' do context 'when worker has one queue set' do before(:each) do worker.queues = ['large'] end it 'only works off jobs which are from its queue' do expect(SimpleJob.runs).to eq(0) create_job(:queue => 'large') create_job(:queue => 'small') worker.work_off expect(SimpleJob.runs).to eq(1) end end context 'when worker has two queue set' do before(:each) do worker.queues = %w[large small] end it 'only works off jobs which are from its queue' do expect(SimpleJob.runs).to eq(0) create_job(:queue => 'large') create_job(:queue => 'small') create_job(:queue => 'medium') create_job worker.work_off expect(SimpleJob.runs).to eq(2) end end context 'when worker does not have queue set' do before(:each) do worker.queues = [] end it 'works off all jobs' do expect(SimpleJob.runs).to eq(0) create_job(:queue => 'one') create_job(:queue => 'two') create_job worker.work_off expect(SimpleJob.runs).to eq(3) end end end context 'max_attempts' do before(:each) do @job = described_class.enqueue SimpleJob.new end it 'is not defined' do expect(@job.max_attempts).to be_nil end it 'uses the max_attempts value on the payload when defined' do expect(@job.payload_object).to receive(:max_attempts).and_return(99) expect(@job.max_attempts).to eq(99) end end describe '#max_run_time' do before(:each) { @job = described_class.enqueue SimpleJob.new } it 'is not defined' do expect(@job.max_run_time).to be_nil end it 'results in a default run time when not defined' do expect(worker.max_run_time(@job)).to eq(Delayed::Worker::DEFAULT_MAX_RUN_TIME) end it 'uses the max_run_time value on the payload when defined' do expect(@job.payload_object).to receive(:max_run_time).and_return(30.minutes) expect(@job.max_run_time).to eq(30.minutes) end it 'results in an overridden run time when defined' do expect(@job.payload_object).to receive(:max_run_time).and_return(45.minutes) expect(worker.max_run_time(@job)).to eq(45.minutes) end it 'job set max_run_time can not exceed default max run time' do expect(@job.payload_object).to receive(:max_run_time).and_return(Delayed::Worker::DEFAULT_MAX_RUN_TIME + 60) expect(worker.max_run_time(@job)).to eq(Delayed::Worker::DEFAULT_MAX_RUN_TIME) end end describe 'yaml serialization' do context 'when serializing jobs' do it 'raises error ArgumentError for new records' do story = Story.new(:text => 'hello') if story.respond_to?(:new_record?) expect { story.delay.tell }.to raise_error( ArgumentError, "job cannot be created for non-persisted record: #{story.inspect}" ) end end it 'raises error ArgumentError for destroyed records' do story = Story.create(:text => 'hello') story.destroy expect { story.delay.tell }.to raise_error( ArgumentError, "job cannot be created for non-persisted record: #{story.inspect}" ) end end context 'when reload jobs back' do it 'reloads changed attributes' do story = Story.create(:text => 'hello') job = story.delay.tell story.update_attributes :text => 'goodbye' expect(job.reload.payload_object.object.text).to eq('goodbye') end it 'raises deserialization error for destroyed records' do story = Story.create(:text => 'hello') job = story.delay.tell story.destroy expect { job.reload.payload_object }.to raise_error(Delayed::DeserializationError) end end end describe 'worker integration' do before do Delayed::Job.delete_all SimpleJob.runs = 0 end describe 'running a job' do it 'fails after Worker.max_run_time' do Delayed::Worker.max_run_time = 1.second job = Delayed::Job.create :payload_object => LongRunningJob.new worker.run(job) expect(job.reload.last_error).to match(/expired/) expect(job.reload.last_error).to match(/Delayed::Worker\.max_run_time is only 1 second/) expect(job.attempts).to eq(1) end context 'when the job raises a deserialization error' do after do Delayed::Worker.destroy_failed_jobs = true end it 'marks the job as failed' do Delayed::Worker.destroy_failed_jobs = false job = described_class.create! :handler => '--- !ruby/object:JobThatDoesNotExist {}' worker.work_off job.reload expect(job).to be_failed end end end describe 'failed jobs' do before do @job = Delayed::Job.enqueue(ErrorJob.new, :run_at => described_class.db_time_now - 1) end after do # reset default Delayed::Worker.destroy_failed_jobs = true end it 'records last_error when destroy_failed_jobs = false, max_attempts = 1' do Delayed::Worker.destroy_failed_jobs = false Delayed::Worker.max_attempts = 1 worker.run(@job) @job.reload expect(@job.last_error).to match(/did not work/) expect(@job.attempts).to eq(1) expect(@job).to be_failed end it 're-schedules jobs after failing' do worker.work_off @job.reload expect(@job.last_error).to match(/did not work/) expect(@job.last_error).to match(/sample_jobs.rb:\d+:in `perform'/) expect(@job.attempts).to eq(1) expect(@job.run_at).to be > Delayed::Job.db_time_now - 10.minutes expect(@job.run_at).to be < Delayed::Job.db_time_now + 10.minutes expect(@job.locked_by).to be_nil expect(@job.locked_at).to be_nil end it 're-schedules jobs with handler provided time if present' do job = Delayed::Job.enqueue(CustomRescheduleJob.new(99.minutes)) worker.run(job) job.reload expect((Delayed::Job.db_time_now + 99.minutes - job.run_at).abs).to be < 1 end it "does not fail when the triggered error doesn't have a message" do error_with_nil_message = StandardError.new expect(error_with_nil_message).to receive(:message).twice.and_return(nil) expect(@job).to receive(:invoke_job).and_raise error_with_nil_message expect { worker.run(@job) }.not_to raise_error end end context 'reschedule' do before do @job = Delayed::Job.create :payload_object => SimpleJob.new end shared_examples_for 'any failure more than Worker.max_attempts times' do context "when the job's payload has a #failure hook" do before do @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new expect(@job.payload_object).to respond_to(:failure) end it 'runs that hook' do expect(@job.payload_object).to receive(:failure) worker.reschedule(@job) end it 'handles error in hook' do Delayed::Worker.destroy_failed_jobs = false @job.payload_object.raise_error = true expect { worker.reschedule(@job) }.not_to raise_error expect(@job.failed_at).to_not be_nil end end context "when the job's payload has no #failure hook" do # It's a little tricky to test this in a straightforward way, # because putting a not_to receive expectation on # @job.payload_object.failure makes that object incorrectly return # true to payload_object.respond_to? :failure, which is what # reschedule uses to decide whether to call failure. So instead, we # just make sure that the payload_object as it already stands doesn't # respond_to? failure, then shove it through the iterated reschedule # loop and make sure we don't get a NoMethodError (caused by calling # that nonexistent failure method). before do expect(@job.payload_object).not_to respond_to(:failure) end it 'does not try to run that hook' do expect do Delayed::Worker.max_attempts.times { worker.reschedule(@job) } end.not_to raise_exception end end end context 'and we want to destroy jobs' do it_behaves_like 'any failure more than Worker.max_attempts times' it 'is destroyed if it failed more than Worker.max_attempts times' do expect(@job).to receive(:destroy) Delayed::Worker.max_attempts.times { worker.reschedule(@job) } end it 'is not destroyed if failed fewer than Worker.max_attempts times' do expect(@job).not_to receive(:destroy) (Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) } end end context "and we don't want to destroy jobs" do before do Delayed::Worker.destroy_failed_jobs = false end after do Delayed::Worker.destroy_failed_jobs = true end it_behaves_like 'any failure more than Worker.max_attempts times' it 'is failed if it failed more than Worker.max_attempts times' do expect(@job.reload).not_to be_failed Delayed::Worker.max_attempts.times { worker.reschedule(@job) } expect(@job.reload).to be_failed end it 'is not failed if it failed fewer than Worker.max_attempts times' do (Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) } expect(@job.reload).not_to be_failed end end end end end delayed-job-4.0.6/lib/delayed/backend/base.rb0000644000175000017500000001141612531115467022201 0ustar balasankarcbalasankarcmodule Delayed module Backend module Base def self.included(base) base.extend ClassMethods end module ClassMethods # Add a job to the queue def enqueue(*args) # rubocop:disable CyclomaticComplexity options = args.extract_options! options[:payload_object] ||= args.shift options[:priority] ||= Delayed::Worker.default_priority if options[:queue].nil? if options[:payload_object].respond_to?(:queue_name) options[:queue] = options[:payload_object].queue_name end options[:queue] ||= Delayed::Worker.default_queue_name end if args.size > 0 warn '[DEPRECATION] Passing multiple arguments to `#enqueue` is deprecated. Pass a hash with :priority and :run_at.' options[:priority] = args.first || options[:priority] options[:run_at] = args[1] end unless options[:payload_object].respond_to?(:perform) raise ArgumentError, 'Cannot enqueue items which do not respond to perform' end new(options).tap do |job| Delayed::Worker.lifecycle.run_callbacks(:enqueue, job) do job.hook(:enqueue) Delayed::Worker.delay_jobs ? job.save : job.invoke_job end end end def reserve(worker, max_run_time = Worker.max_run_time) # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next. # this leads to a more even distribution of jobs across the worker processes find_available(worker.name, worker.read_ahead, max_run_time).detect do |job| job.lock_exclusively!(max_run_time, worker.name) end end # Allow the backend to attempt recovery from reserve errors def recover_from(_error) end # Hook method that is called before a new worker is forked def before_fork end # Hook method that is called after a new worker is forked def after_fork end def work_off(num = 100) warn '[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead.' Delayed::Worker.new.work_off(num) end end def failed? !!failed_at end alias_method :failed, :failed? ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/ # rubocop:disable ConstantName def name @name ||= payload_object.respond_to?(:display_name) ? payload_object.display_name : payload_object.class.name rescue DeserializationError ParseObjectFromYaml.match(handler)[1] end def payload_object=(object) @payload_object = object self.handler = object.to_yaml end def payload_object @payload_object ||= YAML.load_dj(handler) rescue TypeError, LoadError, NameError, ArgumentError, SyntaxError, Psych::SyntaxError => e raise DeserializationError, "Job failed to load: #{e.message}. Handler: #{handler.inspect}" end def invoke_job Delayed::Worker.lifecycle.run_callbacks(:invoke_job, self) do begin hook :before payload_object.perform hook :success rescue => e hook :error, e raise e ensure hook :after end end end # Unlock this job (note: not saved to DB) def unlock self.locked_at = nil self.locked_by = nil end def hook(name, *args) if payload_object.respond_to?(name) method = payload_object.method(name) method.arity == 0 ? method.call : method.call(self, *args) end rescue DeserializationError # rubocop:disable HandleExceptions end def reschedule_at if payload_object.respond_to?(:reschedule_at) payload_object.reschedule_at(self.class.db_time_now, attempts) else self.class.db_time_now + (attempts**4) + 5 end end def max_attempts payload_object.max_attempts if payload_object.respond_to?(:max_attempts) end def max_run_time return unless payload_object.respond_to?(:max_run_time) return unless (run_time = payload_object.max_run_time) if run_time > Delayed::Worker.max_run_time Delayed::Worker.max_run_time else run_time end end def fail! update_attributes(:failed_at => self.class.db_time_now) end protected def set_default_run_at self.run_at ||= self.class.db_time_now end # Call during reload operation to clear out internal state def reset @payload_object = nil end end end end delayed-job-4.0.6/lib/delayed/yaml_ext.rb0000644000175000017500000000052412531115467021520 0ustar balasankarcbalasankarc# These extensions allow properly serializing and autoloading of # Classes, Modules and Structs require 'yaml' if YAML.parser.class.name =~ /syck|yecht/i require File.expand_path('../syck_ext', __FILE__) require File.expand_path('../serialization/active_record', __FILE__) else require File.expand_path('../psych_ext', __FILE__) end delayed-job-4.0.6/lib/delayed/compatibility.rb0000644000175000017500000000077412531115467022556 0ustar balasankarcbalasankarcrequire 'active_support/version' module Delayed module Compatibility if ActiveSupport::VERSION::MAJOR >= 4 require 'active_support/proxy_object' def self.executable_prefix 'bin' end def self.proxy_object_class ActiveSupport::ProxyObject end else require 'active_support/basic_object' def self.executable_prefix 'script' end def self.proxy_object_class ActiveSupport::BasicObject end end end end delayed-job-4.0.6/lib/delayed/serialization/0000755000175000017500000000000012531115467022225 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/delayed/serialization/active_record.rb0000644000175000017500000000077212531115467025371 0ustar balasankarcbalasankarcif defined?(ActiveRecord) module ActiveRecord class Base yaml_as 'tag:ruby.yaml.org,2002:ActiveRecord' def self.yaml_new(klass, _tag, val) klass.unscoped.find(val['attributes'][klass.primary_key]) rescue ActiveRecord::RecordNotFound raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass} , primary key: #{val['attributes'][klass.primary_key]}" end def to_yaml_properties ['@attributes'] end end end end delayed-job-4.0.6/lib/delayed/psych_ext.rb0000644000175000017500000000607612531115467021714 0ustar balasankarcbalasankarcmodule Delayed class PerformableMethod # serialize to YAML def encode_with(coder) coder.map = { 'object' => object, 'method_name' => method_name, 'args' => args } end end end module Psych def self.load_dj(yaml) result = parse(yaml) result ? Delayed::PsychExt::ToRuby.create.accept(result) : result end end module Delayed module PsychExt class ToRuby < Psych::Visitors::ToRuby unless respond_to?(:create) def self.create new end end def visit_Psych_Nodes_Mapping(object) # rubocop:disable CyclomaticComplexity, MethodName, PerceivedComplexity return revive(Psych.load_tags[object.tag], object) if Psych.load_tags[object.tag] case object.tag when /^!ruby\/object/ result = super if defined?(ActiveRecord::Base) && result.is_a?(ActiveRecord::Base) klass = result.class id = result[klass.primary_key] begin klass.find(id) rescue ActiveRecord::RecordNotFound => error # rubocop:disable BlockNesting raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})" end else result end when /^!ruby\/ActiveRecord:(.+)$/ klass = resolve_class(Regexp.last_match[1]) payload = Hash[*object.children.map { |c| accept c }] id = payload['attributes'][klass.primary_key] id = id.value if defined?(ActiveRecord::Attribute) && id.is_a?(ActiveRecord::Attribute) begin klass.unscoped.find(id) rescue ActiveRecord::RecordNotFound => error raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})" end when /^!ruby\/Mongoid:(.+)$/ klass = resolve_class(Regexp.last_match[1]) payload = Hash[*object.children.map { |c| accept c }] id = payload['attributes']['_id'] begin klass.find(id) rescue Mongoid::Errors::DocumentNotFound => error raise Delayed::DeserializationError, "Mongoid::Errors::DocumentNotFound, class: #{klass}, primary key: #{id} (#{error.message})" end when /^!ruby\/DataMapper:(.+)$/ klass = resolve_class(Regexp.last_match[1]) payload = Hash[*object.children.map { |c| accept c }] begin primary_keys = klass.properties.select(&:key?) key_names = primary_keys.map { |p| p.name.to_s } klass.get!(*key_names.map { |k| payload['attributes'][k] }) rescue DataMapper::ObjectNotFoundError => error raise Delayed::DeserializationError, "DataMapper::ObjectNotFoundError, class: #{klass} (#{error.message})" end else super end end def resolve_class(klass_name) return nil if !klass_name || klass_name.empty? klass_name.constantize rescue super end end end end delayed-job-4.0.6/lib/delayed/plugin.rb0000644000175000017500000000050212531115467021170 0ustar balasankarcbalasankarcrequire 'active_support/core_ext/class/attribute' module Delayed class Plugin class_attribute :callback_block def self.callbacks(&block) self.callback_block = block end def initialize self.class.callback_block.call(Delayed::Worker.lifecycle) if self.class.callback_block end end end delayed-job-4.0.6/lib/delayed/plugins/0000755000175000017500000000000012531115467021031 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/delayed/plugins/clear_locks.rb0000644000175000017500000000047512531115467023645 0ustar balasankarcbalasankarcmodule Delayed module Plugins class ClearLocks < Plugin callbacks do |lifecycle| lifecycle.around(:execute) do |worker, &block| begin block.call(worker) ensure Delayed::Job.clear_locks!(worker.name) end end end end end end delayed-job-4.0.6/lib/delayed/performable_method.rb0000644000175000017500000000207612531115467023540 0ustar balasankarcbalasankarcrequire 'active_support/core_ext/module/delegation' module Delayed class PerformableMethod attr_accessor :object, :method_name, :args delegate :method, :to => :object def initialize(object, method_name, args) raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true) if object.respond_to?(:persisted?) && !object.persisted? raise(ArgumentError, "job cannot be created for non-persisted record: #{object.inspect}") end self.object = object self.args = args self.method_name = method_name.to_sym end def display_name if object.is_a?(Class) "#{object}.#{method_name}" else "#{object.class}##{method_name}" end end def perform object.send(method_name, *args) if object end def method_missing(symbol, *args) object.send(symbol, *args) end def respond_to?(symbol, include_private = false) super || object.respond_to?(symbol, include_private) end end end delayed-job-4.0.6/lib/generators/0000755000175000017500000000000012531115467020112 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/generators/delayed_job/0000755000175000017500000000000012531115467022353 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/generators/delayed_job/delayed_job_generator.rb0000644000175000017500000000057112531115467027212 0ustar balasankarcbalasankarcrequire 'rails/generators/base' require 'delayed/compatibility' class DelayedJobGenerator < Rails::Generators::Base source_paths << File.join(File.dirname(__FILE__), 'templates') def create_executable_file template 'script', "#{Delayed::Compatibility.executable_prefix}/delayed_job" chmod "#{Delayed::Compatibility.executable_prefix}/delayed_job", 0755 end end delayed-job-4.0.6/lib/generators/delayed_job/templates/0000755000175000017500000000000012531115467024351 5ustar balasankarcbalasankarcdelayed-job-4.0.6/lib/generators/delayed_job/templates/script0000644000175000017500000000025712531115467025604 0ustar balasankarcbalasankarc#!/usr/bin/env ruby require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) require 'delayed/command' Delayed::Command.new(ARGV).daemonize delayed-job-4.0.6/recipes/0000755000175000017500000000000012531115467016625 5ustar balasankarcbalasankarcdelayed-job-4.0.6/recipes/delayed_job.rb0000644000175000017500000000013712531115467021414 0ustar balasankarcbalasankarcrequire File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes')) delayed-job-4.0.6/CONTRIBUTING.md0000644000175000017500000000223012531115467017421 0ustar balasankarcbalasankarcHow to contribute ================= If you find what looks like a bug: * Search the "mailing list":http://groups.google.com/group/delayed_job to see if anyone else had the same issue. * Check the "GitHub issue tracker":http://github.com/collectiveidea/delayed_job/issues/ to see if anyone else has reported issue. * Make sure you are using the latest version of delayed_job ![Gem Version](https://badge.fury.io/rb/delayed_job.png) * Make sure you are using the latest backend gem for delayed_job * Active Record ![Gem Version](https://badge.fury.io/rb/delayed_job_active_record.png) * Mongoid ![Gem Version](https://badge.fury.io/rb/delayed_job_mongoid.png) * If you are still having an issue, create an issue including: * Ruby version * Gemfile.lock contents or at least major gem versions, such as Rails version * Steps to reproduce the issue * Full backtrace for any errors encountered If you want to contribute an enhancement or a fix: * Fork the project on GitHub. * Make your changes with tests. * Commit the changes without making changes to the Rakefile or any other files that aren't related to your enhancement or fix. * Send a pull request. delayed-job-4.0.6/spec/0000755000175000017500000000000012531115467016125 5ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/lifecycle_spec.rb0000644000175000017500000000436312531115467021431 0ustar balasankarcbalasankarcrequire 'helper' describe Delayed::Lifecycle do let(:lifecycle) { Delayed::Lifecycle.new } let(:callback) { lambda { |*_args| } } let(:arguments) { [1] } let(:behavior) { double(Object, :before! => nil, :after! => nil, :inside! => nil) } let(:wrapped_block) { proc { behavior.inside! } } describe 'before callbacks' do before(:each) do lifecycle.before(:execute, &callback) end it 'executes before wrapped block' do expect(callback).to receive(:call).with(*arguments).ordered expect(behavior).to receive(:inside!).ordered lifecycle.run_callbacks :execute, *arguments, &wrapped_block end end describe 'after callbacks' do before(:each) do lifecycle.after(:execute, &callback) end it 'executes after wrapped block' do expect(behavior).to receive(:inside!).ordered expect(callback).to receive(:call).with(*arguments).ordered lifecycle.run_callbacks :execute, *arguments, &wrapped_block end end describe 'around callbacks' do before(:each) do lifecycle.around(:execute) do |*args, &block| behavior.before! block.call(*args) behavior.after! end end it 'wraps a block' do expect(behavior).to receive(:before!).ordered expect(behavior).to receive(:inside!).ordered expect(behavior).to receive(:after!).ordered lifecycle.run_callbacks :execute, *arguments, &wrapped_block end it 'executes multiple callbacks in order' do expect(behavior).to receive(:one).ordered expect(behavior).to receive(:two).ordered expect(behavior).to receive(:three).ordered lifecycle.around(:execute) do |*args, &block| behavior.one block.call(*args) end lifecycle.around(:execute) do |*args, &block| behavior.two block.call(*args) end lifecycle.around(:execute) do |*args, &block| behavior.three block.call(*args) end lifecycle.run_callbacks(:execute, *arguments, &wrapped_block) end end it 'raises if callback is executed with wrong number of parameters' do lifecycle.before(:execute, &callback) expect { lifecycle.run_callbacks(:execute, 1, 2, 3) {} }.to raise_error(ArgumentError, /1 parameter/) end end delayed-job-4.0.6/spec/performable_method_spec.rb0000644000175000017500000000633312531115467023327 0ustar balasankarcbalasankarcrequire 'helper' describe Delayed::PerformableMethod do describe 'perform' do before do @method = Delayed::PerformableMethod.new('foo', :count, ['o']) end context 'with the persisted record cannot be found' do before do @method.object = nil end it 'does nothing if object is nil' do expect { @method.perform }.not_to raise_error end end it 'calls the method on the object' do expect(@method.object).to receive(:count).with('o') @method.perform end end it "raises a NoMethodError if target method doesn't exist" do expect do Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, []) end.to raise_error(NoMethodError) end it 'does not raise NoMethodError if target method is private' do clazz = Class.new do def private_method end private :private_method end expect { Delayed::PerformableMethod.new(clazz.new, :private_method, []) }.not_to raise_error end describe 'display_name' do it 'returns class_name#method_name for instance methods' do expect(Delayed::PerformableMethod.new('foo', :count, ['o']).display_name).to eq('String#count') end it 'returns class_name.method_name for class methods' do expect(Delayed::PerformableMethod.new(Class, :inspect, []).display_name).to eq('Class.inspect') end end describe 'hooks' do %w[before after success].each do |hook| it "delegates #{hook} hook to object" do story = Story.create job = story.delay.tell expect(story).to receive(hook).with(job) job.invoke_job end end it 'delegates enqueue hook to object' do story = Story.create expect(story).to receive(:enqueue).with(an_instance_of(Delayed::Job)) story.delay.tell end it 'delegates error hook to object' do story = Story.create expect(story).to receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) expect(story).to receive(:tell).and_raise(RuntimeError) expect { story.delay.tell.invoke_job }.to raise_error end it 'delegates failure hook to object' do method = Delayed::PerformableMethod.new('object', :size, []) expect(method.object).to receive(:failure) method.failure end context 'with delay_job == false' do before do Delayed::Worker.delay_jobs = false end after do Delayed::Worker.delay_jobs = true end %w[before after success].each do |hook| it "delegates #{hook} hook to object" do story = Story.create expect(story).to receive(hook).with(an_instance_of(Delayed::Job)) story.delay.tell end end it 'delegates error hook to object' do story = Story.create expect(story).to receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) expect(story).to receive(:tell).and_raise(RuntimeError) expect { story.delay.tell }.to raise_error end it 'delegates failure hook to object' do method = Delayed::PerformableMethod.new('object', :size, []) expect(method.object).to receive(:failure) method.failure end end end end delayed-job-4.0.6/spec/psych_ext_spec.rb0000644000175000017500000000056312531115467021476 0ustar balasankarcbalasankarcrequire 'helper' describe 'Psych::Visitors::ToRuby', :if => defined?(Psych::Visitors::ToRuby) do context BigDecimal do it 'deserializes correctly' do deserialized = YAML.load("--- !ruby/object:BigDecimal 18:0.1337E2\n...\n") expect(deserialized).to be_an_instance_of(BigDecimal) expect(deserialized).to eq(BigDecimal('13.37')) end end end delayed-job-4.0.6/spec/helper.rb0000644000175000017500000000353412531115467017736 0ustar balasankarcbalasankarcrequire 'simplecov' require 'coveralls' SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] SimpleCov.start do add_filter '/spec/' # Each version of ruby and version of rails test different things # This should probably just be removed. minimum_coverage(85.0) end require 'logger' require 'rspec' require 'action_mailer' require 'active_support/dependencies' require 'active_record' require 'delayed_job' require 'delayed/backend/shared_spec' if ENV['DEBUG_LOGS'] Delayed::Worker.logger = Logger.new(STDOUT) else require 'tempfile' tf = Tempfile.new('dj.log') Delayed::Worker.logger = Logger.new(tf.path) tf.unlink end ENV['RAILS_ENV'] = 'test' # Trigger AR to initialize ActiveRecord::Base # rubocop:disable Void module Rails def self.root '.' end end Delayed::Worker.backend = :test # Add this directory so the ActiveSupport autoloading works ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__) # Add this to simulate Railtie initializer being executed ActionMailer::Base.extend(Delayed::DelayMail) # Used to test interactions between DJ and an ORM ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:' ActiveRecord::Base.logger = Delayed::Worker.logger ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table :stories, :primary_key => :story_id, :force => true do |table| table.string :text table.boolean :scoped, :default => true end end class Story < ActiveRecord::Base self.primary_key = 'story_id' def tell text end def whatever(n, _) tell * n end default_scope { where(:scoped => true) } handle_asynchronously :whatever end RSpec.configure do |config| config.after(:each) do Delayed::Worker.reset end config.expect_with :rspec do |c| c.syntax = :expect end end delayed-job-4.0.6/spec/worker_spec.rb0000644000175000017500000001113212531115467020773 0ustar balasankarcbalasankarcrequire 'helper' describe Delayed::Worker do describe 'backend=' do before do @clazz = Class.new Delayed::Worker.backend = @clazz end after do Delayed::Worker.backend = :test end it 'sets the Delayed::Job constant to the backend' do expect(Delayed::Job).to eq(@clazz) end it 'sets backend with a symbol' do Delayed::Worker.backend = :test expect(Delayed::Worker.backend).to eq(Delayed::Backend::Test::Job) end end describe 'job_say' do before do @worker = Delayed::Worker.new @job = double('job', :id => 123, :name => 'ExampleJob') end it 'logs with job name and id' do expect(@worker).to receive(:say). with('Job ExampleJob (id=123) message', Delayed::Worker.default_log_level) @worker.job_say(@job, 'message') end it 'has a configurable default log level' do Delayed::Worker.default_log_level = 'error' expect(@worker).to receive(:say). with('Job ExampleJob (id=123) message', 'error') @worker.job_say(@job, 'message') end end context 'worker read-ahead' do before do @read_ahead = Delayed::Worker.read_ahead end after do Delayed::Worker.read_ahead = @read_ahead end it 'reads five jobs' do expect(Delayed::Job).to receive(:find_available).with(anything, 5, anything).and_return([]) Delayed::Job.reserve(Delayed::Worker.new) end it 'reads a configurable number of jobs' do Delayed::Worker.read_ahead = 15 expect(Delayed::Job).to receive(:find_available).with(anything, Delayed::Worker.read_ahead, anything).and_return([]) Delayed::Job.reserve(Delayed::Worker.new) end end context 'worker exit on complete' do before do Delayed::Worker.exit_on_complete = true end after do Delayed::Worker.exit_on_complete = false end it 'exits the loop when no jobs are available' do worker = Delayed::Worker.new Timeout.timeout(2) do worker.start end end end context 'worker job reservation' do before do Delayed::Worker.exit_on_complete = true end after do Delayed::Worker.exit_on_complete = false end it 'handles error during job reservation' do expect(Delayed::Job).to receive(:reserve).and_raise(Exception) Delayed::Worker.new.work_off end it 'gives up after 10 backend failures' do expect(Delayed::Job).to receive(:reserve).exactly(10).times.and_raise(Exception) worker = Delayed::Worker.new 9.times { worker.work_off } expect(lambda { worker.work_off }).to raise_exception Delayed::FatalBackendError end it 'allows the backend to attempt recovery from reservation errors' do expect(Delayed::Job).to receive(:reserve).and_raise(Exception) expect(Delayed::Job).to receive(:recover_from).with(instance_of(Exception)) Delayed::Worker.new.work_off end end context '#say' do before(:each) do @worker = Delayed::Worker.new @worker.name = 'ExampleJob' @worker.logger = double('job') time = Time.now allow(Time).to receive(:now).and_return(time) @text = 'Job executed' @worker_name = '[Worker(ExampleJob)]' @expected_time = time.strftime('%FT%T%z') end after(:each) do @worker.logger = nil end shared_examples_for 'a worker which logs on the correct severity' do |severity| it "logs a message on the #{severity[:level].upcase} level given a string" do expect(@worker.logger).to receive(:send). with(severity[:level], "#{@expected_time}: #{@worker_name} #{@text}") @worker.say(@text, severity[:level]) end it "logs a message on the #{severity[:level].upcase} level given a fixnum" do expect(@worker.logger).to receive(:send). with(severity[:level], "#{@expected_time}: #{@worker_name} #{@text}") @worker.say(@text, severity[:index]) end end severities = [{:index => 0, :level => 'debug'}, {:index => 1, :level => 'info'}, {:index => 2, :level => 'warn'}, {:index => 3, :level => 'error'}, {:index => 4, :level => 'fatal'}, {:index => 5, :level => 'unknown'}] severities.each do |severity| it_behaves_like 'a worker which logs on the correct severity', severity end it 'logs a message on the default log\'s level' do expect(@worker.logger).to receive(:send). with('info', "#{@expected_time}: #{@worker_name} #{@text}") @worker.say(@text, Delayed::Worker.default_log_level) end end end delayed-job-4.0.6/spec/yaml_ext_spec.rb0000644000175000017500000000264712531115467021317 0ustar balasankarcbalasankarcrequire 'helper' describe 'YAML' do it 'autoloads classes' do expect do yaml = "--- !ruby/class Autoloaded::Clazz\n" expect(load_with_delayed_visitor(yaml)).to eq(Autoloaded::Clazz) end.not_to raise_error end it 'autoloads the class of a struct' do expect do yaml = "--- !ruby/class Autoloaded::Struct\n" expect(load_with_delayed_visitor(yaml)).to eq(Autoloaded::Struct) end.not_to raise_error end it 'autoloads the class for the instance of a struct' do expect do yaml = '--- !ruby/struct:Autoloaded::InstanceStruct {}' expect(load_with_delayed_visitor(yaml).class).to eq(Autoloaded::InstanceStruct) end.not_to raise_error end it 'autoloads the class of an anonymous struct' do expect do yaml = "--- !ruby/struct\nn: 1\n" object = YAML.load(yaml) expect(object).to be_kind_of(Struct) expect(object.n).to eq(1) end.not_to raise_error end it 'autoloads the class for the instance' do expect do yaml = "--- !ruby/object:Autoloaded::InstanceClazz {}\n" expect(load_with_delayed_visitor(yaml).class).to eq(Autoloaded::InstanceClazz) end.not_to raise_error end it 'does not throw an uninitialized constant Syck::Syck when using YAML.load with poorly formed yaml' do expect { YAML.load(YAML.dump('foo: *bar')) }.not_to raise_error end def load_with_delayed_visitor(yaml) YAML.load_dj(yaml) end end delayed-job-4.0.6/spec/message_sending_spec.rb0000644000175000017500000000663612531115467022632 0ustar balasankarcbalasankarcrequire 'helper' describe Delayed::MessageSending do describe 'handle_asynchronously' do class Story def tell!(_arg); end handle_asynchronously :tell! end it 'aliases original method' do expect(Story.new).to respond_to(:tell_without_delay!) expect(Story.new).to respond_to(:tell_with_delay!) end it 'creates a PerformableMethod' do story = Story.create expect do job = story.tell!(1) expect(job.payload_object.class).to eq(Delayed::PerformableMethod) expect(job.payload_object.method_name).to eq(:tell_without_delay!) expect(job.payload_object.args).to eq([1]) end.to change { Delayed::Job.count } end describe 'with options' do class Fable cattr_accessor :importance def tell; end handle_asynchronously :tell, :priority => proc { importance } end it 'sets the priority based on the Fable importance' do Fable.importance = 10 job = Fable.new.tell expect(job.priority).to eq(10) Fable.importance = 20 job = Fable.new.tell expect(job.priority).to eq(20) end describe 'using a proc with parameters' do class Yarn attr_accessor :importance def spin end handle_asynchronously :spin, :priority => proc { |y| y.importance } end it 'sets the priority based on the Fable importance' do job = Yarn.new.tap { |y| y.importance = 10 }.spin expect(job.priority).to eq(10) job = Yarn.new.tap { |y| y.importance = 20 }.spin expect(job.priority).to eq(20) end end end end context 'delay' do class FairyTail attr_accessor :happy_ending def self.princesses; end def tell @happy_ending = true end end after do Delayed::Worker.default_queue_name = nil end it 'creates a new PerformableMethod job' do expect do job = 'hello'.delay.count('l') expect(job.payload_object.class).to eq(Delayed::PerformableMethod) expect(job.payload_object.method_name).to eq(:count) expect(job.payload_object.args).to eq(['l']) end.to change { Delayed::Job.count }.by(1) end it 'sets default priority' do Delayed::Worker.default_priority = 99 job = FairyTail.delay.to_s expect(job.priority).to eq(99) end it 'sets default queue name' do Delayed::Worker.default_queue_name = 'abbazabba' job = FairyTail.delay.to_s expect(job.queue).to eq('abbazabba') end it 'sets job options' do run_at = Time.parse('2010-05-03 12:55 AM') job = FairyTail.delay(:priority => 20, :run_at => run_at).to_s expect(job.run_at).to eq(run_at) expect(job.priority).to eq(20) end it 'does not delay the job when delay_jobs is false' do Delayed::Worker.delay_jobs = false fairy_tail = FairyTail.new expect do expect do fairy_tail.delay.tell end.to change(fairy_tail, :happy_ending).from(nil).to(true) end.not_to change { Delayed::Job.count } end it 'does delay the job when delay_jobs is true' do Delayed::Worker.delay_jobs = true fairy_tail = FairyTail.new expect do expect do fairy_tail.delay.tell end.not_to change(fairy_tail, :happy_ending) end.to change { Delayed::Job.count }.by(1) end end end delayed-job-4.0.6/spec/autoloaded/0000755000175000017500000000000012531115467020246 5ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/autoloaded/clazz.rb0000644000175000017500000000016712531115467021722 0ustar balasankarcbalasankarc# Make sure this file does not get required manually module Autoloaded class Clazz def perform end end end delayed-job-4.0.6/spec/autoloaded/instance_struct.rb0000644000175000017500000000013712531115467024004 0ustar balasankarcbalasankarcmodule Autoloaded class InstanceStruct < ::Struct.new(nil) def perform end end end delayed-job-4.0.6/spec/autoloaded/struct.rb0000644000175000017500000000021412531115467022114 0ustar balasankarcbalasankarc# Make sure this file does not get required manually module Autoloaded class Struct < ::Struct.new(nil) def perform end end end delayed-job-4.0.6/spec/autoloaded/instance_clazz.rb0000644000175000017500000000011212531115467023574 0ustar balasankarcbalasankarcmodule Autoloaded class InstanceClazz def perform end end end delayed-job-4.0.6/spec/delayed/0000755000175000017500000000000012531115467017534 5ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/delayed/command_spec.rb0000644000175000017500000000375112531115467022517 0ustar balasankarcbalasankarcrequire 'helper' require 'delayed/command' describe Delayed::Command do describe 'parsing --pool argument' do it 'should parse --pool correctly' do command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2']) expect(command.worker_pools).to eq [ [[], 1], [['test_queue'], 4], [%w[mailers misc], 2] ] end it 'should allow * or blank to specify any pools' do command = Delayed::Command.new(['--pool=*:4']) expect(command.worker_pools).to eq [ [[], 4], ] command = Delayed::Command.new(['--pool=:4']) expect(command.worker_pools).to eq [ [[], 4], ] end it 'should default to one worker if not specified' do command = Delayed::Command.new(['--pool=mailers']) expect(command.worker_pools).to eq [ [['mailers'], 1], ] end end describe 'running worker pools defined by multiple --pool arguments' do it 'should run the correct worker processes' do command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2']) expect(Dir).to receive(:mkdir).with('./tmp/pids').once [ ['delayed_job.0', {:quiet => true, :pid_dir => './tmp/pids', :queues => []}], ['delayed_job.1', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}], ['delayed_job.2', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}], ['delayed_job.3', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}], ['delayed_job.4', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}], ['delayed_job.5', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}], ['delayed_job.6', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}] ].each do |args| expect(command).to receive(:run_process).with(*args).once end command.daemonize end end end delayed-job-4.0.6/spec/delayed/backend/0000755000175000017500000000000012531115467021123 5ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/delayed/backend/test.rb0000644000175000017500000000604312531115467022432 0ustar balasankarcbalasankarcrequire 'ostruct' # An in-memory backend suitable only for testing. Tries to behave as if it were an ORM. module Delayed module Backend module Test class Job attr_accessor :id attr_accessor :priority attr_accessor :attempts attr_accessor :handler attr_accessor :last_error attr_accessor :run_at attr_accessor :locked_at attr_accessor :locked_by attr_accessor :failed_at attr_accessor :queue include Delayed::Backend::Base cattr_accessor :id self.id = 0 def initialize(hash = {}) self.attempts = 0 self.priority = 0 self.id = (self.class.id += 1) hash.each { |k, v| send(:"#{k}=", v) } end def self.all @jobs ||= [] end def self.count all.size end def self.delete_all all.clear end def self.create(attrs = {}) new(attrs).tap do |o| o.save end end def self.create!(*args) create(*args) end def self.clear_locks!(worker_name) all.select { |j| j.locked_by == worker_name }.each do |j| j.locked_by = nil j.locked_at = nil end end # Find a few candidate jobs to run (in case some immediately get locked by others). def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) # rubocop:disable CyclomaticComplexity, PerceivedComplexity jobs = all.select do |j| j.run_at <= db_time_now && (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) && !j.failed? end jobs.select! { |j| j.priority <= Worker.max_priority } if Worker.max_priority jobs.select! { |j| j.priority >= Worker.min_priority } if Worker.min_priority jobs.select! { |j| Worker.queues.include?(j.queue) } if Worker.queues.any? jobs.sort_by! { |j| [j.priority, j.run_at] }[0..limit - 1] end # Lock this job for this worker. # Returns true if we have the lock, false otherwise. def lock_exclusively!(_max_run_time, worker) now = self.class.db_time_now if locked_by != worker # We don't own this job so we will update the locked_by name and the locked_at self.locked_at = now self.locked_by = worker end true end def self.db_time_now Time.current end def update_attributes(attrs = {}) attrs.each { |k, v| send(:"#{k}=", v) } save end def destroy self.class.all.delete(self) end def save self.run_at ||= Time.current self.class.all << self unless self.class.all.include?(self) true end def save! save end def reload reset self end end end end end delayed-job-4.0.6/spec/delayed/serialization/0000755000175000017500000000000012531115467022411 5ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/delayed/serialization/test.rb0000644000175000017500000000000012531115467023703 0ustar balasankarcbalasankarcdelayed-job-4.0.6/spec/performable_mailer_spec.rb0000644000175000017500000000251712531115467023320 0ustar balasankarcbalasankarcrequire 'helper' require 'action_mailer' class MyMailer < ActionMailer::Base def signup(email) mail :to => email, :subject => 'Delaying Emails', :from => 'delayedjob@example.com', :body => 'Delaying Emails Body' end end describe ActionMailer::Base do describe 'delay' do it 'enqueues a PerformableEmail job' do expect do job = MyMailer.delay.signup('john@example.com') expect(job.payload_object.class).to eq(Delayed::PerformableMailer) expect(job.payload_object.method_name).to eq(:signup) expect(job.payload_object.args).to eq(['john@example.com']) end.to change { Delayed::Job.count }.by(1) end end describe 'delay on a mail object' do it 'raises an exception' do expect do MyMailer.signup('john@example.com').delay end.to raise_error(RuntimeError) end end describe Delayed::PerformableMailer do describe 'perform' do it 'calls the method and #deliver on the mailer' do email = double('email', :deliver => true) mailer_class = double('MailerClass', :signup => email) mailer = Delayed::PerformableMailer.new(mailer_class, :signup, ['john@example.com']) expect(mailer_class).to receive(:signup).with('john@example.com') expect(email).to receive(:deliver) mailer.perform end end end end delayed-job-4.0.6/spec/test_backend_spec.rb0000644000175000017500000000051712531115467022115 0ustar balasankarcbalasankarcrequire 'helper' describe Delayed::Backend::Test::Job do it_should_behave_like 'a delayed_job backend' describe '#reload' do it 'causes the payload object to be reloaded' do job = 'foo'.delay.length o = job.payload_object expect(o.object_id).not_to eq(job.reload.payload_object.object_id) end end end delayed-job-4.0.6/spec/sample_jobs.rb0000644000175000017500000000303512531115467020751 0ustar balasankarcbalasankarcclass NamedJob < Struct.new(:perform) def display_name 'named_job' end end class SimpleJob cattr_accessor :runs @runs = 0 def perform self.class.runs += 1 end end class NamedQueueJob < SimpleJob def queue_name 'job_tracking' end end class ErrorJob cattr_accessor :runs @runs = 0 def perform raise 'did not work' end end class CustomRescheduleJob < Struct.new(:offset) cattr_accessor :runs @runs = 0 def perform raise 'did not work' end def reschedule_at(time, _attempts) time + offset end end class LongRunningJob def perform sleep 250 end end class OnPermanentFailureJob < SimpleJob attr_writer :raise_error def initialize @raise_error = false end def failure raise 'did not work' if @raise_error end def max_attempts 1 end end module M class ModuleJob cattr_accessor :runs @runs = 0 def perform self.class.runs += 1 end end end class CallbackJob cattr_accessor :messages def enqueue(_job) self.class.messages << 'enqueue' end def before(_job) self.class.messages << 'before' end def perform self.class.messages << 'perform' end def after(_job) self.class.messages << 'after' end def success(_job) self.class.messages << 'success' end def error(_job, error) self.class.messages << "error: #{error.class}" end def failure(_job) self.class.messages << 'failure' end end class EnqueueJobMod < SimpleJob def enqueue(job) job.run_at = 20.minutes.from_now end end delayed-job-4.0.6/README.md0000644000175000017500000003367312531115467016466 0ustar balasankarcbalasankarcDelayed::Job ============ [![Gem Version](https://badge.fury.io/rb/delayed_job.png)][gem] [![Build Status](https://travis-ci.org/collectiveidea/delayed_job.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/collectiveidea/delayed_job.png?travis)][gemnasium] [![Code Climate](https://codeclimate.com/github/collectiveidea/delayed_job.png)][codeclimate] [![Coverage Status](https://coveralls.io/repos/collectiveidea/delayed_job/badge.png?branch=master)][coveralls] [gem]: https://rubygems.org/gems/delayed_job [travis]: https://travis-ci.org/collectiveidea/delayed_job [gemnasium]: https://gemnasium.com/collectiveidea/delayed_job [codeclimate]: https://codeclimate.com/github/collectiveidea/delayed_job [coveralls]: https://coveralls.io/r/collectiveidea/delayed_job Delayed::Job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. Amongst those tasks are: * sending massive newsletters * image resizing * http downloads * updating smart collections * updating solr, our search server, after product changes * batch imports * spam checks [Follow us on Twitter][twitter] to get updates and notices about new releases. [twitter]: https://twitter.com/delayedjob Installation ============ delayed_job 3.0.0 only supports Rails 3.0+. See the [2.0 branch](https://github.com/collectiveidea/delayed_job/tree/v2.0) for Rails 2. delayed_job supports multiple backends for storing the job queue. [See the wiki for other backends](https://github.com/collectiveidea/delayed_job/wiki/Backends). If you plan to use delayed_job with Active Record, add `delayed_job_active_record` to your `Gemfile`. ```ruby gem 'delayed_job_active_record' ``` If you plan to use delayed_job with Mongoid, add `delayed_job_mongoid` to your `Gemfile`. ```ruby gem 'delayed_job_mongoid' ``` Run `bundle install` to install the backend and delayed_job gems. The Active Record backend requires a jobs table. You can create that table by running the following command: rails generate delayed_job:active_record rake db:migrate Development =========== In development mode, if you are using Rails 3.1+, your application code will automatically reload every 100 jobs or when the queue finishes. You no longer need to restart Delayed Job every time you update your code in development. Rails 4 ======= If you are using the protected_attributes gem, it must appear before delayed_job in your gemfile. Upgrading from 2.x to 3.0.0 on Active Record ============================================ Delayed Job 3.0.0 introduces a new column to the delayed_jobs table. If you're upgrading from Delayed Job 2.x, run the upgrade generator to create a migration to add the column. rails generate delayed_job:upgrade rake db:migrate Queuing Jobs ============ Call `.delay.method(params)` on any object and it will be processed in the background. ```ruby # without delayed_job @user.activate!(@device) # with delayed_job @user.delay.activate!(@device) ``` If a method should always be run in the background, you can call `#handle_asynchronously` after the method declaration: ```ruby class Device def deliver # long running method end handle_asynchronously :deliver end device = Device.new device.deliver ``` handle_asynchronously can take as options anything you can pass to delay. In addition, the values can be Proc objects allowing call time evaluation of the value. For some examples: ```ruby class LongTasks def send_mailer # Some other code end handle_asynchronously :send_mailer, :priority => 20 def in_the_future # Some other code end # 5.minutes.from_now will be evaluated when in_the_future is called handle_asynchronously :in_the_future, :run_at => Proc.new { 5.minutes.from_now } def self.when_to_run 2.hours.from_now end class << self def call_a_class_method # Some other code end handle_asynchronously :call_a_class_method, :run_at => Proc.new { when_to_run } end attr_reader :how_important def call_an_instance_method # Some other code end handle_asynchronously :call_an_instance_method, :priority => Proc.new {|i| i.how_important } end ``` If you ever want to call a `handle_asynchronously`'d method without Delayed Job, for instance while debugging something at the console, just add `_without_delay` to the method name. For instance, if your original method was `foo`, then call `foo_without_delay`. Rails 3 Mailers =============== Due to how mailers are implemented in Rails 3, we had to do a little work around to get delayed_job to work. ```ruby # without delayed_job Notifier.signup(@user).deliver # with delayed_job Notifier.delay.signup(@user) # with delayed_job running at a specific time Notifier.delay(run_at: 5.minutes.from_now).signup(@user) ``` Remove the `.deliver` method to make it work. It's not ideal, but it's the best we could do for now. Named Queues ============ DJ 3 introduces Resque-style named queues while still retaining DJ-style priority. The goal is to provide a system for grouping tasks to be worked by separate pools of workers, which may be scaled and controlled individually. Jobs can be assigned to a queue by setting the `queue` option: ```ruby object.delay(:queue => 'tracking').method Delayed::Job.enqueue job, :queue => 'tracking' handle_asynchronously :tweet_later, :queue => 'tweets' ``` Running Jobs ============ `script/delayed_job` can be used to manage a background process which will start working off jobs. To do so, add `gem "daemons"` to your `Gemfile` and make sure you've run `rails generate delayed_job`. You can then do the following: RAILS_ENV=production script/delayed_job start RAILS_ENV=production script/delayed_job stop # Runs two workers in separate processes. RAILS_ENV=production script/delayed_job -n 2 start RAILS_ENV=production script/delayed_job stop # Set the --queue or --queues option to work from a particular queue. RAILS_ENV=production script/delayed_job --queue=tracking start RAILS_ENV=production script/delayed_job --queues=mailers,tasks start # Use the --pool option to specify a worker pool. You can use this option multiple times to start different numbers of workers for different queues. # The following command will start 1 worker for the tracking queue, # 2 workers for the mailers and tasks queues, and 2 workers for any jobs: RAILS_ENV=production script/delayed_job --pool=tracking --pool=mailers,tasks:2 --pool=*:2 start # Runs all available jobs and then exits RAILS_ENV=production script/delayed_job start --exit-on-complete # or to run in the foreground RAILS_ENV=production script/delayed_job run --exit-on-complete **Rails 4:** *replace script/delayed_job with bin/delayed_job* Workers can be running on any computer, as long as they have access to the database and their clock is in sync. Keep in mind that each worker will check the database at least every 5 seconds. You can also invoke `rake jobs:work` which will start working off jobs. You can cancel the rake task with `CTRL-C`. If you want to just run all available jobs and exit you can use `rake jobs:workoff` Work off queues by setting the `QUEUE` or `QUEUES` environment variable. QUEUE=tracking rake jobs:work QUEUES=mailers,tasks rake jobs:work Restarting delayed_job ====================== The following syntax will restart delayed jobs: RAILS_ENV=production script/delayed_job restart To restart multiple delayed_job workers: RAILS_ENV=production script/delayed_job -n2 restart **Rails 4:** *replace script/delayed_job with bin/delayed_job* Custom Jobs =========== Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table. Job objects are serialized to yaml so that they can later be resurrected by the job runner. ```ruby NewsletterJob = Struct.new(:text, :emails) do def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end end Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.pluck(:email)) ``` To set a per-job max attempts that overrides the Delayed::Worker.max_attempts you can define a max_attempts method on the job ```ruby NewsletterJob = Struct.new(:text, :emails) do def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end def max_attempts 3 end end ``` To set a per-job max run time that overrides the Delayed::Worker.max_run_time you can define a max_run_time method on the job NOTE: this can ONLY be used to set a max_run_time that is lower than Delayed::Worker.max_run_time. Otherwise the lock on the job would expire and another worker would start the working on the in progress job. ```ruby NewsletterJob = Struct.new(:text, :emails) do def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end def max_run_time 120 # seconds end end ``` To set a default queue name for a custom job that overrides Delayed::Worker.default_queue_name, you can define a queue_name method on the job ```ruby NewsletterJob = Struct.new(:text, :emails) do def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end def queue_name 'newsletter_queue' end end ``` On error, the job is scheduled again in 5 seconds + N ** 4, where N is the number of attempts. You can define your own `reschedule_at` method to override this default behavior. ```ruby NewsletterJob = Struct.new(:text, :emails) do def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end def reschedule_at(current_time, attempts) current_time + 5.seconds end end ``` Hooks ===== You can define hooks on your job that will be called at different stages in the process: ```ruby class ParanoidNewsletterJob < NewsletterJob def enqueue(job) record_stat 'newsletter_job/enqueue' end def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end def before(job) record_stat 'newsletter_job/start' end def after(job) record_stat 'newsletter_job/after' end def success(job) record_stat 'newsletter_job/success' end def error(job, exception) Airbrake.notify(exception) end def failure(job) page_sysadmin_in_the_middle_of_the_night end end ``` Gory Details ============ The library revolves around a delayed_jobs table which looks as follows: ```ruby create_table :delayed_jobs, :force => true do |table| table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. table.text :handler # YAML-encoded string of the object that will do work table.text :last_error # reason for last failure (See Note below) table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. table.datetime :locked_at # Set when a client is working on this object table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) table.string :locked_by # Who is working on this object (if locked) table.string :queue # The name of the queue this job is in table.timestamps end ``` On error, the job is scheduled again in 5 seconds + N ** 4, where N is the number of attempts or using the job's defined `reschedule_at` method. The default `Worker.max_attempts` is 25. After this, the job either deleted (default), or left in the database with "failed_at" set. With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours. The default `Worker.max_run_time` is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take. By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set `Delayed::Worker.destroy_failed_jobs = false`. The failed jobs will be marked with non-null failed_at. By default all jobs are scheduled with `priority = 0`, which is top priority. You can change this by setting `Delayed::Worker.default_priority` to something else. Lower numbers have higher priority. The default behavior is to read 5 jobs from the queue when finding an available job. You can configure this by setting `Delayed::Worker.read_ahead`. By default all jobs will be queued without a named queue. A default named queue can be specified by using `Delayed::Worker.default_queue_name`. It is possible to disable delayed jobs for testing purposes. Set `Delayed::Worker.delay_jobs = false` to execute all jobs realtime. You may need to raise exceptions on SIGTERM signals, `Delayed::Worker.raise_signal_exceptions = :term` will cause the worker to raise a `SignalException` causing the running job to abort and be unlocked, which makes the job available to other workers. The default for this option is false. Here is an example of changing job parameters in Rails: ```ruby # config/initializers/delayed_job_config.rb Delayed::Worker.destroy_failed_jobs = false Delayed::Worker.sleep_delay = 60 Delayed::Worker.max_attempts = 3 Delayed::Worker.max_run_time = 5.minutes Delayed::Worker.read_ahead = 10 Delayed::Worker.default_queue_name = 'default' Delayed::Worker.delay_jobs = !Rails.env.test? Delayed::Worker.raise_signal_exceptions = :term Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) ``` Cleaning up =========== You can invoke `rake jobs:clear` to delete all jobs in the queue. Having problems? ================ Good places to get help are: * [Google Groups](http://groups.google.com/group/delayed_job) where you can join our mailing list. * [StackOverflow](http://stackoverflow.com/questions/tagged/delayed-job) delayed-job-4.0.6/CHANGELOG.md0000644000175000017500000002156112531115467017011 0ustar balasankarcbalasankarc4.0.6 - 2014-12-22 ================== * Revert removing test files from the gem 4.0.5 - 2014-12-22 ================== * Support for Rails 4.2 * Allow user to override where DJ writes log output * First attempt at automatic code reloading * Clearer error message when ActiveRecord object no longer exists * Various improvements to the README 4.0.4 - 2014-09-24 ================== * Fix using options passed into delayed_job command * Add the ability to set a default queue for a custom job * Add the ability to override the max_run_time on a custom job. MUST be lower than worker setting * Psych YAML overrides are now exclusively used only when loading a job payload * SLEEP_DELAY and READ_AHEAD can be set for the rake task * Some updates for Rails 4.2 support 4.0.3 - 2014-09-04 ================== * Added --pools option to delayed_job command * Removed a bunch of the Psych hacks * Improved deserialization error reporting * Misc bug fixes 4.0.2 - 2014-06-24 ================== * Add support for RSpec 3 4.0.1 - 2014-04-12 ================== * Update gemspec for Rails 4.1 * Make logger calls more universal * Check that records are persisted? instead of new_record? 4.0.0 - 2013-07-30 ================== * Rails 4 compatibility * Reverted threaded startup due to daemons incompatibilities * Attempt to recover from job reservation errors 4.0.0.beta2 - 2013-05-28 ======================== * Rails 4 compatibility * Threaded startup script for faster multi-worker startup * YAML compatibility changes * Added jobs:check rake task 4.0.0.beta1 - 2013-03-02 ======================== * Rails 4 compatibility 3.0.5 - 2013-01-28 ================== * Better job timeout error logging * psych support for delayed_job_data_mapper deserialization * User can configure the worker to raise a SignalException on TERM and/or INT * Add the ability to run all available jobs and exit when complete 3.0.4 - 2012-11-09 ================== * Allow the app to specify a default queue name * Capistrano script now allows user to specify the DJ command, allowing the user to add "bundle exec" if necessary * Persisted record check is now more general 3.0.3 - 2012-05-25 ================== * Fix a bug where the worker would not respect the exit condition * Properly handle sleep delay command line argument 3.0.2 - 2012-04-02 ================== * Fix deprecation warnings * Raise ArgumentError if attempting to enqueue a performable method on an object that hasn't been persisted yet * Allow the number of jobs read at a time to be configured from the command line using --read-ahead * Allow custom logger to be configured through Delayed::Worker.logger * Various documentation improvements 3.0.1 - 2012-01-24 ================== * Added RecordNotFound message to deserialization error * Direct JRuby's yecht parser to syck extensions * Updated psych extensions for better compatibility with ruby 1.9.2 * Updated syck extension for increased compatibility with class methods * Test grooming 3.0.0 - 2011-12-30 ================== * New: Named queues * New: Job/Worker lifecycle callbacks * Change: daemons is no longer a runtime dependency * Change: Active Record backend support is provided by a separate gem * Change: Enqueue hook is called before jobs are saved so that they may be modified * Fix problem deserializing models that use a custom primary key column * Fix deserializing AR models when the object isn't in the default scope * Fix hooks not getting called when delay_jobs is false 2.1.4 - 2011-02-11 ================== * Working around issues when psych is loaded, fixes issues with bundler 1.0.10 and Rails 3.0.4 * Added -p/--prefix option to help differentiate multiple delayed job workers on the same host. 2.1.3 - 2011-01-20 ================== * Revert worker contention fix due to regressions * Added Delayed::Worker.delay_jobs flag to support running jobs immediately 2.1.2 - 2010-12-01 ================== * Remove contention between multiple workers by performing an update to lock a job before fetching it * Job payloads may implement #max_attempts to control how many times it should be retried * Fix for loading ActionMailer extension * Added 'delayed_job_server_role' Capistrano variable to allow delayed_job to run on its own worker server set :delayed_job_server_role, :worker * Fix `rake jobs:work` so it outputs to the console 2.1.1 - 2010-11-14 ================== * Fix issue with worker name not getting properly set when locking a job * Fixes for YAML serialization 2.1.0 - 2010-11-14 ================== * Added enqueue, before, after, success, error, and failure. See the README * Remove Merb support * Remove all non Active Record backends into separate gems. See https://github.com/collectiveidea/delayed_job/wiki/Backends * remove rails 2 support. delayed_job 2.1 will only support Rails 3 * New pure-YAML serialization * Added Rails 3 railtie and generator * Changed @@sleep_delay to self.class.sleep_delay to be consistent with other class variable usage * Added --sleep-delay command line option 2.0.8 - Unreleased ================== * Backport fix for deserialization errors that bring down the daemon 2.0.7 - 2011-02-10 ================== * Fixed missing generators and recipes for Rails 2.x 2.0.6 - 2011-01-20 ================== * Revert worker contention fix due to regressions 2.0.5 - 2010-12-01 ================== * Added #reschedule_at hook on payload to determine when the job should be rescheduled [backported from 2.1] * Added --sleep-delay command line option [backported from 2.1] * Added 'delayed_job_server_role' Capistrano variable to allow delayed_job to run on its own worker server set :delayed_job_server_role, :worker * Changed AR backend to reserve jobs using an UPDATE query to reduce worker contention [backported from 2.1] 2.0.4 - 2010-11-14 ================== * Fix issue where dirty tracking prevented job from being properly unlocked * Add delayed_job_args variable for Capistrano recipe to allow configuration of started workers (e.g. "-n 2 --max-priority 10") * Added options to handle_asynchronously * Added Delayed::Worker.default_priority * Allow private methods to be delayed * Fixes for Ruby 1.9 * Added -m command line option to start a monitor process * normalize logging in worker * Deprecate #send_later and #send_at in favor of new #delay method * Added @#delay@ to Object that allows you to delay any method and pass options: options = {:priority => 19, :run_at => 5.minutes.from_now} UserMailer.delay(options).deliver_confirmation(@user) 2.0.3 - 2010-04-16 ================== * Fix initialization for Rails 2.x 2.0.2 - 2010-04-08 ================== * Fixes to Mongo Mapper backend [ "14be7a24":http://github.com/collectiveidea/delayed_job/commit/14be7a24, "dafd5f46":http://github.com/collectiveidea/delayed_job/commit/dafd5f46, "54d40913":http://github.com/collectiveidea/delayed_job/commit/54d40913 ] * DataMapper backend performance improvements [ "93833cce":http://github.com/collectiveidea/delayed_job/commit/93833cce, "e9b1573e":http://github.com/collectiveidea/delayed_job/commit/e9b1573e, "37a16d11":http://github.com/collectiveidea/delayed_job/commit/37a16d11, "803f2bfa":http://github.com/collectiveidea/delayed_job/commit/803f2bfa ] * Fixed Delayed::Command to create tmp/pids directory [ "8ec8ca41":http://github.com/collectiveidea/delayed_job/commit/8ec8ca41 ] * Railtie to perform Rails 3 initialization [ "3e0fc41f":http://github.com/collectiveidea/delayed_job/commit/3e0fc41f ] * Added on_permanent_failure hook [ "d2f14cd6":http://github.com/collectiveidea/delayed_job/commit/d2f14cd6 ] 2.0.1 - 2010-04-03 ================== * Bug fix for using ActiveRecord backend with daemon [martinbtt] 2.0.0 - 2010-04-03 ================== * Multiple backend support (See README for more details) * Added MongoMapper backend [zbelzer, moneypools] * Added DataMapper backend [lpetre] * Reverse priority so the jobs table can be indexed. Lower numbers have higher priority. The default priority is 0, so increase it for jobs that are not important. * Move most of the heavy lifting from Job to Worker (#work_off, #reschedule, #run, #min_priority, #max_priority, #max_run_time, #max_attempts, #worker_name) [albus522] * Remove EvaledJob. Implement your own if you need this functionality. * Only use Time.zone if it is set. Closes #20 * Fix for last_error recording when destroy_failed_jobs = false, max_attempts = 1 * Implemented worker name_prefix to maintain dynamic nature of pid detection * Some Rails 3 compatibility fixes [fredwu] 1.8.5 - 2010-03-15 ================== * Set auto_flushing=true on Rails logger to fix logging in production * Fix error message when trying to send_later on a method that doesn't exist * Don't use rails_env in capistrano if it's not set. closes #22 * Delayed job should append to delayed_job.log not overwrite * Version bump to 1.8.5 * fixing Time.now to be Time.zone.now if set to honor the app set local TimeZone * Replaced @Worker::SLEEP@, @Job::MAX_ATTEMPTS@, and @Job::MAX_RUN_TIME@ with class methods that can be overridden.