delayed_job-4.1.9/0000755000004100000410000000000014000711134014005 5ustar www-datawww-datadelayed_job-4.1.9/recipes/0000755000004100000410000000000014000711134015437 5ustar www-datawww-datadelayed_job-4.1.9/recipes/delayed_job.rb0000644000004100000410000000013714000711134020226 0ustar www-datawww-datarequire File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes')) delayed_job-4.1.9/README.md0000644000004100000410000004072514000711134015274 0ustar www-datawww-data**If you're viewing this at https://github.com/collectiveidea/delayed_job, you're reading the documentation for the master branch. [View documentation for the latest release (4.1.9).](https://github.com/collectiveidea/delayed_job/tree/v4.1.9)** Delayed::Job ============ [![Gem Version](https://badge.fury.io/rb/delayed_job.svg)][gem] ![CI](https://github.com/collectiveidea/delayed_job/workflows/CI/badge.svg) [![Code Climate](https://codeclimate.com/github/collectiveidea/delayed_job.svg)][codeclimate] [![Coverage Status](https://coveralls.io/repos/collectiveidea/delayed_job/badge.svg?branch=master)][coveralls] [gem]: https://rubygems.org/gems/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+. 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 For Rails 4.2+, see [below](#active-job) 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. Active Job ========== In Rails 4.2+, set the queue_adapter in config/application.rb ```ruby config.active_job.queue_adapter = :delayed_job ``` See the [rails guide](http://guides.rubyonrails.org/active_job_basics.html#setting-the-backend) for more details. Rails 4.x ========= If you are using the protected_attributes gem, it must appear before delayed_job in your gemfile. If your jobs are failing with: ActiveRecord::StatementInvalid: PG::NotNullViolation: ERROR: null value in column "handler" violates not-null constraint then this is the fix you're looking for. 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 ``` ## Parameters `#handle_asynchronously` and `#delay` take these parameters: - `:priority` (number): lower numbers run first; default is 0 but can be reconfigured (see below) - `:run_at` (Time): run the job after this time (probably in the future) - `:queue` (string): named queue to put this job in, an alternative to priorities (see below) These params can be Proc objects, allowing call-time evaluation of the value. For example: ```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 Mailers ============= Delayed Job uses special syntax for Rails Mailers. Do not call the `.deliver` method when using `.delay`. ```ruby # without delayed_job Notifier.signup(@user).deliver # with delayed_job Notifier.delay.signup(@user) # delayed_job running at a specific time Notifier.delay(run_at: 5.minutes.from_now).signup(@user) # when using parameters, the .with method must be called before the .delay method Notifier.with(foo: 1, bar: 2).delay.signup(@user) ``` You may also wish to consider using [Active Job with Action Mailer](https://edgeguides.rubyonrails.org/active_job_basics.html#action-mailer) which provides convenient `.deliver_later` syntax that forwards to Delayed Job under-the-hood. 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' ``` You can configure default priorities for named queues: ```ruby Delayed::Worker.queue_attributes = { high_priority: { priority: -10 }, low_priority: { priority: 10 } } ``` Configured queue priorities can be overriden by passing priority to the delay method ```ruby object.delay(:queue => 'high_priority', priority: 0).method ``` You can start processes to only work certain queues with the `queue` and `queues` options defined below. Processes started without specifying a queue will run jobs from **any** queue. To effectively have a process that runs jobs where a queue is not specified, set a default queue name with `Delayed::Worker.default_queue_name` and have the processes run that queue. 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 per-job default for destroying failed jobs that overrides the Delayed::Worker.destroy_failed_jobs you can define a destroy_failed_jobs? 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 destroy_failed_jobs? false 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: **NOTE:** If you are using ActiveJob these hooks are **not** available to your jobs. You will need to use ActiveJob's callbacks. You can find details here https://guides.rubyonrails.org/active_job_basics.html#callbacks ```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 is 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`. If no jobs are found, the worker sleeps for the amount of time specified by the sleep delay option. Set `Delayed::Worker.sleep_delay = 60` for a 60 second sleep time. It is possible to disable delayed jobs for testing purposes. Set `Delayed::Worker.delay_jobs = false` to execute all jobs realtime. Or `Delayed::Worker.delay_jobs` can be a Proc that decides whether to execute jobs inline on a per-job basis: ```ruby Delayed::Worker.delay_jobs = ->(job) { job.queue != 'inline' } ``` 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.1.9/LICENSE.md0000644000004100000410000000204014000711134015405 0ustar www-datawww-dataCopyright (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.1.9/spec/0000755000004100000410000000000014000711134014737 5ustar www-datawww-datadelayed_job-4.1.9/spec/lifecycle_spec.rb0000644000004100000410000000436314000711134020243 0ustar www-datawww-datarequire '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.1.9/spec/delayed/0000755000004100000410000000000014000711134016346 5ustar www-datawww-datadelayed_job-4.1.9/spec/delayed/serialization/0000755000004100000410000000000014000711134021223 5ustar www-datawww-datadelayed_job-4.1.9/spec/delayed/serialization/test.rb0000644000004100000410000000000014000711134022515 0ustar www-datawww-datadelayed_job-4.1.9/spec/delayed/backend/0000755000004100000410000000000014000711134017735 5ustar www-datawww-datadelayed_job-4.1.9/spec/delayed/backend/test.rb0000644000004100000410000000565314000711134021252 0ustar www-datawww-datarequire '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 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.1.9/spec/delayed/command_spec.rb0000644000004100000410000001305314000711134021325 0ustar www-datawww-datarequire 'helper' require 'delayed/command' describe Delayed::Command do let(:options) { [] } let(:logger) { double('Logger') } subject { Delayed::Command.new options } before do allow(Delayed::Worker).to receive(:after_fork) allow(Dir).to receive(:chdir) allow(Logger).to receive(:new).and_return(logger) allow_any_instance_of(Delayed::Worker).to receive(:start) allow(Delayed::Worker).to receive(:logger=) allow(Delayed::Worker).to receive(:logger).and_return(nil, logger) end shared_examples_for 'uses --log-dir option' do context 'when --log-dir is specified' do let(:options) { ['--log-dir=/custom/log/dir'] } it 'creates the delayed_job.log in the specified directory' do expect(Logger).to receive(:new).with('/custom/log/dir/delayed_job.log') subject.run end end end describe 'run' do it 'sets the Delayed::Worker logger' do expect(Delayed::Worker).to receive(:logger=).with(logger) subject.run end context 'when Rails root is defined' do let(:rails_root) { Pathname.new '/rails/root' } let(:rails) { double('Rails', :root => rails_root) } before do stub_const('Rails', rails) end it 'runs the Delayed::Worker process in Rails.root' do expect(Dir).to receive(:chdir).with(rails_root) subject.run end context 'when --log-dir is not specified' do it 'creates the delayed_job.log in Rails.root/log' do expect(Logger).to receive(:new).with('/rails/root/log/delayed_job.log') subject.run end end include_examples 'uses --log-dir option' end context 'when Rails root is not defined' do let(:rails_without_root) { double('Rails') } before do stub_const('Rails', rails_without_root) end it 'runs the Delayed::Worker process in $PWD' do expect(Dir).to receive(:chdir).with(Delayed::Command::DIR_PWD) subject.run end context 'when --log-dir is not specified' do it 'creates the delayed_job.log in $PWD/log' do expect(Logger).to receive(:new).with("#{Delayed::Command::DIR_PWD}/log/delayed_job.log") subject.run end end include_examples 'uses --log-dir option' end context 'when an error is raised' do let(:test_error) { Class.new(StandardError) } before do allow(Delayed::Worker).to receive(:new).and_raise(test_error.new('An error')) allow(subject).to receive(:exit_with_error_status) allow(STDERR).to receive(:puts) end it 'prints the error message to STDERR' do expect(STDERR).to receive(:puts).with('An error') subject.run end it 'exits with an error status' do expect(subject).to receive(:exit_with_error_status) subject.run end context 'when Rails logger is not defined' do let(:rails) { double('Rails') } before do stub_const('Rails', rails) end it 'does not attempt to use the Rails logger' do subject.run end end context 'when Rails logger is defined' do let(:rails_logger) { double('Rails logger') } let(:rails) { double('Rails', :logger => rails_logger) } before do stub_const('Rails', rails) end it 'logs the error to the Rails logger' do expect(rails_logger).to receive(:fatal).with(test_error) subject.run end end end end 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(FileUtils).to receive(:mkdir_p).with('./tmp/pids').once [ ['delayed_job.0', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => []}], ['delayed_job.1', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => ['test_queue']}], ['delayed_job.2', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => ['test_queue']}], ['delayed_job.3', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => ['test_queue']}], ['delayed_job.4', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => ['test_queue']}], ['delayed_job.5', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :queues => %w[mailers misc]}], ['delayed_job.6', {:quiet => true, :pid_dir => './tmp/pids', :log_dir => './log', :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.1.9/spec/helper.rb0000644000004100000410000000342014000711134016542 0ustar www-datawww-datarequire 'simplecov' require 'simplecov-lcov' SimpleCov::Formatter::LcovFormatter.config do |c| c.report_with_single_file = true c.single_report_path = 'coverage/lcov.info' end SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new( [ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::LcovFormatter ] ) SimpleCov.start do add_filter '/spec/' end require 'logger' require 'rspec' require 'action_mailer' 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__) # 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.1.9/spec/performable_mailer_spec.rb0000644000004100000410000000456514000711134022137 0ustar www-datawww-datarequire 'helper' 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 if defined?(ActionMailer::Parameterized::Mailer) describe ActionMailer::Parameterized::Mailer do describe 'delay' do it 'enqueues a PerformableEmail job' do expect do job = MyMailer.with(:foo => 1, :bar => 2).delay.signup('john@example.com') expect(job.payload_object.class).to eq(Delayed::PerformableMailer) expect(job.payload_object.object.class).to eq(ActionMailer::Parameterized::Mailer) expect(job.payload_object.object.instance_variable_get('@mailer')).to eq(MyMailer) expect(job.payload_object.object.instance_variable_get('@params')).to eq(:foo => 1, :bar => 2) 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.with(:foo => 1, :bar => 2).signup('john@example.com').delay end.to raise_error(RuntimeError) end end end end delayed_job-4.1.9/spec/worker_spec.rb0000644000004100000410000001263514000711134017616 0ustar www-datawww-datarequire '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', :queue => nil) end it 'logs with job name and id' do expect(@job).to receive(:queue) expect(@worker).to receive(:say). with('Job ExampleJob (id=123) message', Delayed::Worker.default_log_level) @worker.job_say(@job, 'message') end it 'logs with job name, queue and id' do expect(@job).to receive(:queue).and_return('test') expect(@worker).to receive(:say). with('Job ExampleJob (id=123) (queue=test) 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 describe 'plugin registration' do it 'does not double-register plugins on worker instantiation' do performances = 0 plugin = Class.new(Delayed::Plugin) do callbacks do |lifecycle| lifecycle.before(:enqueue) { performances += 1 } end end Delayed::Worker.plugins << plugin Delayed::Worker.new Delayed::Worker.new Delayed::Worker.lifecycle.run_callbacks(:enqueue, nil) {} expect(performances).to eq(1) end end end delayed_job-4.1.9/spec/yaml_ext_spec.rb0000644000004100000410000000264714000711134020131 0ustar www-datawww-datarequire '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.1.9/spec/psych_ext_spec.rb0000644000004100000410000000236414000711134020311 0ustar www-datawww-datarequire 'helper' describe 'Psych::Visitors::ToRuby', :if => defined?(Psych::Visitors::ToRuby) do context BigDecimal do it 'deserializes correctly' do deserialized = YAML.load_dj("--- !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 context 'load_tag handling' do # This only broadly works in ruby 2.0 but will cleanly work through load_dj # here because this class is so simple it only touches our extention YAML.load_tags['!ruby/object:RenamedClass'] = SimpleJob # This is how ruby 2.1 and newer works throughout the yaml handling YAML.load_tags['!ruby/object:RenamedString'] = 'SimpleJob' it 'deserializes class tag' do deserialized = YAML.load_dj("--- !ruby/object:RenamedClass\ncheck: 12\n") expect(deserialized).to be_an_instance_of(SimpleJob) expect(deserialized.instance_variable_get(:@check)).to eq(12) end it 'deserializes string tag' do deserialized = YAML.load_dj("--- !ruby/object:RenamedString\ncheck: 12\n") expect(deserialized).to be_an_instance_of(SimpleJob) expect(deserialized.instance_variable_get(:@check)).to eq(12) end end end delayed_job-4.1.9/spec/performable_method_spec.rb0000644000004100000410000000636214000711134022143 0ustar www-datawww-datarequire '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(RuntimeError) 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(RuntimeError) 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.1.9/spec/sample_jobs.rb0000644000004100000410000000310514000711134017561 0ustar www-datawww-dataNamedJob = Struct.new(:perform) class NamedJob 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 Exception, 'did not work' end end CustomRescheduleJob = Struct.new(:offset) class CustomRescheduleJob 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.1.9/spec/daemons.rb0000644000004100000410000000025014000711134016707 0ustar www-datawww-data# Fake "daemons" file on the spec load path to allow spec/delayed/command_spec.rb # to test the Delayed::Command class without actually adding daemons as a dependency. delayed_job-4.1.9/spec/message_sending_spec.rb0000644000004100000410000001036714000711134021440 0ustar www-datawww-datarequire 'helper' describe Delayed::MessageSending do it 'does not include ClassMethods along with MessageSending' do expect { ClassMethods }.to raise_error(NameError) expect(defined?(String::ClassMethods)).to eq(nil) end 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 it 'does delay when delay_jobs is a proc returning true' do Delayed::Worker.delay_jobs = ->(_job) { 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 it 'does not delay the job when delay_jobs is a proc returning false' do Delayed::Worker.delay_jobs = ->(_job) { 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 end end delayed_job-4.1.9/spec/autoloaded/0000755000004100000410000000000014000711134017060 5ustar www-datawww-datadelayed_job-4.1.9/spec/autoloaded/instance_struct.rb0000644000004100000410000000015514000711134022616 0ustar www-datawww-datamodule Autoloaded InstanceStruct = ::Struct.new(nil) class InstanceStruct def perform; end end end delayed_job-4.1.9/spec/autoloaded/struct.rb0000644000004100000410000000022214000711134020725 0ustar www-datawww-data# Make sure this file does not get required manually module Autoloaded Struct = ::Struct.new(nil) class Struct def perform; end end end delayed_job-4.1.9/spec/autoloaded/clazz.rb0000644000004100000410000000016414000711134020531 0ustar www-datawww-data# Make sure this file does not get required manually module Autoloaded class Clazz def perform; end end end delayed_job-4.1.9/spec/autoloaded/instance_clazz.rb0000644000004100000410000000010714000711134022412 0ustar www-datawww-datamodule Autoloaded class InstanceClazz def perform; end end end delayed_job-4.1.9/spec/test_backend_spec.rb0000644000004100000410000000051714000711134020727 0ustar www-datawww-datarequire '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.1.9/CHANGELOG.md0000644000004100000410000002511414000711134015621 0ustar www-datawww-data4.1.9 - 2020-12-09 ================== * Support for Rails 6.1 * Add support for parameterized mailers via delay call (#1121) 4.1.8 - 2019-08-16 ================== * Support for Rails 6.0.0 4.1.7 - 2019-06-20 ================== * Fix loading Delayed::PerformableMailer when ActionMailer isn't loaded yet 4.1.6 - 2019-06-19 ================== * Properly initialize ActionMailer outside railties (#1077) * Fix Psych load_tags support (#1093) * Replace REMOVED with FAILED in log message (#1048) * Misc doc updates (#1052, #1074, #1064, #1063) 4.1.5 - 2018-04-13 ================== * Allow Rails 5.2 4.1.4 - 2017-12-29 ================== * Use `yaml_tag` instead of deprecated `yaml_as` (#996) * Support ruby 2.5.0 4.1.3 - 2017-05-26 ================== * Don't mutate the options hash (#877) * Log an error message when a deserialization error occurs (#894) * Adding the queue name to the log output (#917) * Don't include ClassMethods with MessageSending (#924) * Fix YAML deserialization error if original object is soft-deleted (#947) * Add support for Rails 5.1 (#982) 4.1.2 - 2016-05-16 ================== * Added Delayed::Worker.queue_attributes * Limit what we require in ActiveSupport * Fix pid file creation when there is no tmp directory * Rails 5 support 4.1.1 - 2015-09-24 ================== * Fix shared specs for back-ends that reload objects 4.1.0 - 2015-09-22 ================== * Alter `Delayed::Command` to work with or without Rails * Allow `Delayed::Worker.delay_jobs` configuration to be a proc * Add ability to set destroy failed jobs on a per job basis * Make `Delayed::Worker.new` idempotent * Set quiet from the environment * Rescue `Exception` instead of `StandardError` in worker * Fix worker crash on serialization error 4.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. delayed_job-4.1.9/contrib/0000755000004100000410000000000014000711134015445 5ustar www-datawww-datadelayed_job-4.1.9/contrib/delayed_job.monitrc0000644000004100000410000000126114000711134021303 0ustar www-datawww-data# 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.1.9/contrib/delayed_job_rails_4_multiple.monitrc0000644000004100000410000000310514000711134024632 0ustar www-datawww-data# 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.1.9/contrib/delayed_job_rails_4.monitrc0000644000004100000410000000125314000711134022721 0ustar www-datawww-data# 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.1.9/contrib/delayed_job_multiple.monitrc0000644000004100000410000000312714000711134023221 0ustar www-datawww-data# 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.1.9/Rakefile0000644000004100000410000000042314000711134015451 0ustar www-datawww-datarequire '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.1.9/lib/0000755000004100000410000000000014000711134014553 5ustar www-datawww-datadelayed_job-4.1.9/lib/delayed/0000755000004100000410000000000014000711134016162 5ustar www-datawww-datadelayed_job-4.1.9/lib/delayed/message_sending.rb0000644000004100000410000000404014000711134021640 0ustar www-datawww-datamodule Delayed class DelayProxy < Delayed::Compatibility.proxy_object_class def initialize(payload_class, target, options) @payload_class = payload_class @target = target @options = options end # rubocop:disable MethodMissing def method_missing(method, *args) Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options)) end # rubocop:enable MethodMissing 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 end module MessageSendingClassMethods def handle_asynchronously(method, opts = {}) # rubocop:disable PerceivedComplexity aliased_method = method.to_s.sub(/([?!=])$/, '') punctuation = $1 # rubocop:disable PerlBackrefs with_method = "#{aliased_method}_with_delay#{punctuation}" without_method = "#{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 without_method, method alias_method method, with_method if public_method_defined?(without_method) public method elsif protected_method_defined?(without_method) protected method elsif private_method_defined?(without_method) private method end end end end delayed_job-4.1.9/lib/delayed/recipes.rb0000644000004100000410000000310614000711134020141 0ustar www-datawww-data# 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.1.9/lib/delayed/worker.rb0000644000004100000410000002476314000711134020034 0ustar www-datawww-datarequire 'timeout' require 'active_support/dependencies' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/hash/indifferent_access' require 'logger' require 'benchmark' module Delayed class Worker # rubocop:disable ClassLength DEFAULT_LOG_LEVEL = 'info'.freeze DEFAULT_SLEEP_DELAY = 5 DEFAULT_MAX_ATTEMPTS = 25 DEFAULT_MAX_RUN_TIME = 4.hours DEFAULT_DEFAULT_PRIORITY = 0 DEFAULT_DELAY_JOBS = true DEFAULT_QUEUES = [].freeze DEFAULT_QUEUE_ATTRIBUTES = HashWithIndifferentAccess.new.freeze 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, :queue_attributes # 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.queue_attributes = DEFAULT_QUEUE_ATTRIBUTES self.read_ahead = DEFAULT_READ_AHEAD @lifecycle = nil end # 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 # rubocop:disable ClassVars def self.queue_attributes=(val) @@queue_attributes = val.with_indifferent_access 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 # In case a worker has not been set up, job enqueueing needs a lifecycle. setup_lifecycle unless @lifecycle @lifecycle end def self.setup_lifecycle @lifecycle = Delayed::Lifecycle.new plugins.each { |klass| klass.new } end def self.reload_app? defined?(ActionDispatch::Reloader) && Rails.application.config.cache_classes == false end def self.delay_job?(job) if delay_jobs.is_a?(Proc) delay_jobs.arity == 1 ? delay_jobs.call(job) : delay_jobs.call else delay_jobs end 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 # Reset lifecycle on the offhand chance that something lazily # triggered its creation before all plugins had been registered. self.class.setup_lifecycle 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}" 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[0] + @result[1] 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 = 0 failure = 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_say job, "FAILED permanently with #{error.class.name}: #{error.message}", 'error' job.error = error failed(job) rescue Exception => error # rubocop:disable RescueException 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, "FAILED 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 job.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})#{say_queue(job.queue)} #{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 say_queue(queue) " (queue=#{queue})" if queue end def handle_failed_job(job, error) job.error = error 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? if defined?(ActiveSupport::Reloader) Rails.application.reloader.reload! else ActionDispatch::Reloader.cleanup! ActionDispatch::Reloader.prepare! end end end end Delayed::Worker.reset delayed_job-4.1.9/lib/delayed/syck_ext.rb0000644000004100000410000000207614000711134020345 0ustar www-datawww-dataclass Module yaml_tag '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_tag '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.1.9/lib/delayed/plugin.rb0000644000004100000410000000050214000711134020002 0ustar www-datawww-datarequire '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.1.9/lib/delayed/exceptions.rb0000644000004100000410000000046014000711134020670 0ustar www-datawww-datarequire '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 < RuntimeError; end end delayed_job-4.1.9/lib/delayed/railtie.rb0000644000004100000410000000054214000711134020141 0ustar www-datawww-datarequire 'delayed_job' require 'rails' module Delayed class Railtie < Rails::Railtie initializer :after_initialize do 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.1.9/lib/delayed/serialization/0000755000004100000410000000000014000711134021037 5ustar www-datawww-datadelayed_job-4.1.9/lib/delayed/serialization/active_record.rb0000644000004100000410000000077314000711134024204 0ustar www-datawww-dataif defined?(ActiveRecord) module ActiveRecord class Base yaml_tag '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.1.9/lib/delayed/deserialization_error.rb0000644000004100000410000000010614000711134023103 0ustar www-datawww-datamodule Delayed class DeserializationError < StandardError end end delayed_job-4.1.9/lib/delayed/lifecycle.rb0000644000004100000410000000416514000711134020454 0ustar www-datawww-datamodule Delayed class InvalidCallback < RuntimeError; end class Lifecycle EVENTS = { :enqueue => [:job], :execute => [:worker], :loop => [:worker], :perform => [:worker, :job], :error => [:worker, :job], :failure => [:worker, :job], :invoke_job => [:job] }.freeze 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.1.9/lib/delayed/yaml_ext.rb0000644000004100000410000000052414000711134020332 0ustar www-datawww-data# 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.1.9/lib/delayed/backend/0000755000004100000410000000000014000711134017551 5ustar www-datawww-datadelayed_job-4.1.9/lib/delayed/backend/job_preparer.rb0000644000004100000410000000271514000711134022555 0ustar www-datawww-datamodule Delayed module Backend class JobPreparer attr_reader :options, :args def initialize(*args) @options = args.extract_options!.dup @args = args end def prepare set_payload set_queue_name set_priority handle_deprecation options end private def set_payload options[:payload_object] ||= args.shift end def set_queue_name if options[:queue].nil? && options[:payload_object].respond_to?(:queue_name) options[:queue] = options[:payload_object].queue_name else options[:queue] ||= Delayed::Worker.default_queue_name end end def set_priority queue_attribute = Delayed::Worker.queue_attributes[options[:queue]] options[:priority] ||= (queue_attribute && queue_attribute[:priority]) || Delayed::Worker.default_priority end def handle_deprecation 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 # rubocop:disable GuardClause unless options[:payload_object].respond_to?(:perform) raise ArgumentError, 'Cannot enqueue items which do not respond to perform' end # rubocop:enabled GuardClause end end end end delayed_job-4.1.9/lib/delayed/backend/base.rb0000644000004100000410000001075414000711134021017 0ustar www-datawww-datamodule Delayed module Backend module Base def self.included(base) base.extend ClassMethods end module ClassMethods # Add a job to the queue def enqueue(*args) job_options = Delayed::Backend::JobPreparer.new(*args).prepare enqueue_job(job_options) end def enqueue_job(options) new(options).tap do |job| Delayed::Worker.lifecycle.run_callbacks(:enqueue, job) do job.hook(:enqueue) Delayed::Worker.delay_job?(job) ? 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 attr_reader :error def error=(error) @error = error self.last_error = "#{error.message}\n#{error.backtrace.join("\n")}" if respond_to?(:last_error=) end def failed? !!failed_at end alias_method :failed, :failed? ParseObjectFromYaml = %r{\!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 Exception => e # rubocop:disable RescueException 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.zero? ? 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 destroy_failed_jobs? payload_object.respond_to?(:destroy_failed_jobs?) ? payload_object.destroy_failed_jobs? : Delayed::Worker.destroy_failed_jobs rescue DeserializationError Delayed::Worker.destroy_failed_jobs end def fail! self.failed_at = self.class.db_time_now save! 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.1.9/lib/delayed/backend/shared_spec.rb0000644000004100000410000006070614000711134022367 0ustar www-datawww-datarequire File.expand_path('../../../../spec/sample_jobs', __FILE__) require 'active_support/core_ext/numeric/time' 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 it 'does not mutate the options hash' do options = {:priority => 1} described_class.enqueue SimpleJob.new, options expect(options).to eq(:priority => 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(RuntimeError) 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 Delayed::Worker.queue_attributes = {} 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 it 'sets job priority based on queue_attributes configuration' do Delayed::Worker.queue_attributes = {'job_tracking' => {:priority => 4}} job = described_class.enqueue :payload_object => NamedQueueJob.new expect(job.priority).to eq(4) end it 'sets job priority based on the passed in priority overrideing queue_attributes configuration' do Delayed::Worker.queue_attributes = {'job_tracking' => {:priority => 4}} job = described_class.enqueue :payload_object => NamedQueueJob.new, :priority => 10 expect(job.priority).to eq(10) 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 'destroy_failed_jobs' do context 'with a SimpleJob' do before(:each) do @job = described_class.enqueue SimpleJob.new end it 'is not defined' do expect(@job.destroy_failed_jobs?).to be true end it 'uses the destroy failed jobs value on the payload when defined' do expect(@job.payload_object).to receive(:destroy_failed_jobs?).and_return(false) expect(@job.destroy_failed_jobs?).to be false end end context 'with a job that raises DserializationError' do before(:each) do @job = described_class.new :handler => '--- !ruby/struct:GoingToRaiseArgError {}' end it 'falls back reasonably' do expect(YAML).to receive(:load_dj).and_raise(ArgumentError) expect(@job.destroy_failed_jobs?).to be true end 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.text = 'goodbye' story.save! 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.error).to_not be_nil 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 {}' expect_any_instance_of(described_class).to receive(:destroy_failed_jobs?).and_return(false) 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.error).to_not be_nil 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 after do Delayed::Worker.destroy_failed_jobs = true end 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 destroyed if the job has destroy failed jobs set' do Delayed::Worker.destroy_failed_jobs = false expect(@job).to receive(:destroy_failed_jobs?).and_return(true) 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' context 'and destroy failed jobs is false' do 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 context 'and destroy failed jobs for job is false' do before do Delayed::Worker.destroy_failed_jobs = true end it 'is failed if it failed more than Worker.max_attempts times' do expect(@job).to receive(:destroy_failed_jobs?).and_return(false) 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 end delayed_job-4.1.9/lib/delayed/command.rb0000644000004100000410000001352414000711134020132 0ustar www-datawww-dataunless 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 'fileutils' require 'optparse' require 'pathname' module Delayed class Command # rubocop:disable ClassLength attr_accessor :worker_count, :worker_pools DIR_PWD = Pathname.new Dir.pwd def initialize(args) # rubocop:disable MethodLength @options = { :quiet => true, :pid_dir => "#{root}/tmp/pids", :log_dir => "#{root}/log" } @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/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 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('--log-dir=DIR', 'Specifies an alternate directory in which to store the delayed_job log.') do |dir| @options[:log_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 opt.on('--daemon-options a, b, c', Array, 'options to be passed through to daemons gem') do |daemon_options| @daemon_options = daemon_options end end @args = opts.parse!(args) + (@daemon_options || []) end def daemonize # rubocop:disable PerceivedComplexity dir = @options[:pid_dir] FileUtils.mkdir_p(dir) unless File.exist?(dir) if worker_pools setup_pools elsif @options[:identifier] # rubocop:disable GuardClause if worker_count > 1 raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier' else run_process("delayed_job.#{@options[:identifier]}", @options) end # rubocop:enable GuardClause 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(root) Delayed::Worker.after_fork Delayed::Worker.logger ||= Logger.new(File.join(@options[:log_dir], 'delayed_job.log')) worker = Delayed::Worker.new(options) worker.name_prefix = "#{worker_name} " worker.start rescue => e STDERR.puts e.message STDERR.puts e.backtrace ::Rails.logger.fatal(e) if rails_logger_defined? exit_with_error_status end private def parse_worker_pool(pool) @worker_pools ||= [] queues, worker_count = pool.split(':') queues = ['*', '', nil].include?(queues) ? [] : queues.split(',') worker_count = (worker_count || 1).to_i rescue 1 @worker_pools << [queues, worker_count] end def root @root ||= rails_root_defined? ? ::Rails.root : DIR_PWD end def rails_root_defined? defined?(::Rails.root) end def rails_logger_defined? defined?(::Rails.logger) end def exit_with_error_status exit 1 end end end delayed_job-4.1.9/lib/delayed/performable_method.rb0000644000004100000410000000214014000711134022342 0ustar www-datawww-datamodule Delayed class PerformableMethod attr_accessor :object, :method_name, :args 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(sym) object.method(sym) end # rubocop:disable MethodMissing def method_missing(symbol, *args) object.send(symbol, *args) end # rubocop:enable MethodMissing def respond_to?(symbol, include_private = false) super || object.respond_to?(symbol, include_private) end end end delayed_job-4.1.9/lib/delayed/compatibility.rb0000644000004100000410000000077414000711134021370 0ustar www-datawww-datarequire '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.1.9/lib/delayed/tasks.rb0000644000004100000410000000252014000711134017633 0ustar www-datawww-datanamespace :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 => ENV['QUIET'] } @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.1.9/lib/delayed/performable_mailer.rb0000644000004100000410000000075214000711134022342 0ustar www-datawww-datarequire '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.1.9/lib/delayed/psych_ext.rb0000644000004100000410000000715314000711134020523 0ustar www-datawww-datamodule 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 klass = Psych.load_tags[object.tag] if klass # Implementation changed here https://github.com/ruby/psych/commit/2c644e184192975b261a81f486a04defa3172b3f # load_tags used to have class values, now the values are strings klass = resolve_class(klass) if klass.is_a?(String) return revive(klass, object) end case object.tag when %r{^!ruby/object} result = super if jruby_is_seriously_borked && result.is_a?(ActiveRecord::Base) klass = result.class id = result[klass.primary_key] begin klass.unscoped.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 %r{^!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 %r{^!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 %r{^!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 # defined? is triggering something really messed up in # jruby causing both the if AND else clauses to execute, # however if the check is run here, everything is fine def jruby_is_seriously_borked defined?(ActiveRecord::Base) 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.1.9/lib/delayed/plugins/0000755000004100000410000000000014000711134017643 5ustar www-datawww-datadelayed_job-4.1.9/lib/delayed/plugins/clear_locks.rb0000644000004100000410000000047514000711134022457 0ustar www-datawww-datamodule 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.1.9/lib/generators/0000755000004100000410000000000014000711134016724 5ustar www-datawww-datadelayed_job-4.1.9/lib/generators/delayed_job/0000755000004100000410000000000014000711134021165 5ustar www-datawww-datadelayed_job-4.1.9/lib/generators/delayed_job/templates/0000755000004100000410000000000014000711134023163 5ustar www-datawww-datadelayed_job-4.1.9/lib/generators/delayed_job/templates/script0000644000004100000410000000025714000711134024416 0ustar www-datawww-data#!/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.1.9/lib/generators/delayed_job/delayed_job_generator.rb0000644000004100000410000000057214000711134026025 0ustar www-datawww-datarequire '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", 0o755 end end delayed_job-4.1.9/lib/delayed_job.rb0000644000004100000410000000160314000711134017341 0ustar www-datawww-datarequire 'active_support' require 'delayed/compatibility' require 'delayed/exceptions' require 'delayed/message_sending' require 'delayed/performable_method' require 'delayed/yaml_ext' require 'delayed/lifecycle' require 'delayed/plugin' require 'delayed/plugins/clear_locks' require 'delayed/backend/base' require 'delayed/backend/job_preparer' require 'delayed/worker' require 'delayed/deserialization_error' require 'delayed/railtie' if defined?(Rails::Railtie) ActiveSupport.on_load(:action_mailer) do require 'delayed/performable_mailer' ActionMailer::Base.extend(Delayed::DelayMail) ActionMailer::Parameterized::Mailer.include(Delayed::DelayMail) if defined?(ActionMailer::Parameterized::Mailer) end module Delayed autoload :PerformableMailer, 'delayed/performable_mailer' end Object.send(:include, Delayed::MessageSending) Module.send(:include, Delayed::MessageSendingClassMethods) delayed_job-4.1.9/CONTRIBUTING.md0000644000004100000410000000223014000711134016233 0ustar www-datawww-dataHow 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.1.9/delayed_job.gemspec0000644000004100000410000000264114000711134017616 0ustar www-datawww-data# -*- encoding: utf-8 -*- Gem::Specification.new do |spec| spec.add_dependency 'activesupport', ['>= 3.0', '< 6.2'] 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}/**/*') # rubocop:disable SpaceAroundOperators 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.1.9' spec.metadata = { 'changelog_uri' => 'https://github.com/collectiveidea/delayed_job/blob/master/CHANGELOG.md', 'bug_tracker_uri' => 'https://github.com/collectiveidea/delayed_job/issues', 'source_code_uri' => 'https://github.com/collectiveidea/delayed_job' } end