delayed_job 4.0.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,7 +25,7 @@ Capistrano::Configuration.instance.load do
25
25
  end
26
26
 
27
27
  def args
28
- fetch(:delayed_job_args, "")
28
+ fetch(:delayed_job_args, '')
29
29
  end
30
30
 
31
31
  def roles
@@ -33,20 +33,20 @@ Capistrano::Configuration.instance.load do
33
33
  end
34
34
 
35
35
  def delayed_job_command
36
- fetch(:delayed_job_command, "script/delayed_job")
36
+ fetch(:delayed_job_command, 'script/delayed_job')
37
37
  end
38
38
 
39
- desc "Stop the delayed_job process"
39
+ desc 'Stop the delayed_job process'
40
40
  task :stop, :roles => lambda { roles } do
41
41
  run "cd #{current_path};#{rails_env} #{delayed_job_command} stop"
42
42
  end
43
43
 
44
- desc "Start the delayed_job process"
44
+ desc 'Start the delayed_job process'
45
45
  task :start, :roles => lambda { roles } do
46
46
  run "cd #{current_path};#{rails_env} #{delayed_job_command} start #{args}"
47
47
  end
48
48
 
49
- desc "Restart the delayed_job process"
49
+ desc 'Restart the delayed_job process'
50
50
  task :restart, :roles => lambda { roles } do
51
51
  run "cd #{current_path};#{rails_env} #{delayed_job_command} restart #{args}"
52
52
  end
@@ -1,15 +1,17 @@
1
1
  if defined?(ActiveRecord)
2
- class ActiveRecord::Base
3
- yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
2
+ module ActiveRecord
3
+ class Base
4
+ yaml_as 'tag:ruby.yaml.org,2002:ActiveRecord'
4
5
 
5
- def self.yaml_new(klass, tag, val)
6
- klass.unscoped.find(val['attributes'][klass.primary_key])
7
- rescue ActiveRecord::RecordNotFound
8
- raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass} , primary key: #{val['attributes'][klass.primary_key]} "
9
- end
6
+ def self.yaml_new(klass, _tag, val)
7
+ klass.unscoped.find(val['attributes'][klass.primary_key])
8
+ rescue ActiveRecord::RecordNotFound
9
+ raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass} , primary key: #{val['attributes'][klass.primary_key]}"
10
+ end
10
11
 
11
- def to_yaml_properties
12
- ['@attributes']
12
+ def to_yaml_properties
13
+ ['@attributes']
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -1,7 +1,7 @@
1
1
  class Module
2
- yaml_as "tag:ruby.yaml.org,2002:module"
2
+ yaml_as 'tag:ruby.yaml.org,2002:module'
3
3
 
4
- def self.yaml_new(klass, tag, val)
4
+ def self.yaml_new(_klass, _tag, val)
5
5
  val.constantize
6
6
  end
7
7
 
@@ -20,7 +20,7 @@ class Module
20
20
  end
21
21
 
22
22
  class Class
23
- yaml_as "tag:ruby.yaml.org,2002:class"
23
+ yaml_as 'tag:ruby.yaml.org,2002:class'
24
24
  remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml
25
25
  end
26
26
 
@@ -1,17 +1,17 @@
1
1
  namespace :jobs do
2
- desc "Clear the delayed_job queue."
2
+ desc 'Clear the delayed_job queue.'
3
3
  task :clear => :environment do
4
4
  Delayed::Job.delete_all
5
5
  end
6
6
 
7
- desc "Start a delayed_job worker."
7
+ desc 'Start a delayed_job worker.'
8
8
  task :work => :environment_options do
9
9
  Delayed::Worker.new(@worker_options).start
10
10
  end
11
11
 
12
- desc "Start a delayed_job worker and exit when all available jobs are complete."
12
+ desc 'Start a delayed_job worker and exit when all available jobs are complete.'
13
13
  task :workoff => :environment_options do
14
- Delayed::Worker.new(@worker_options.merge({:exit_on_complete => true})).start
14
+ Delayed::Worker.new(@worker_options.merge(:exit_on_complete => true)).start
15
15
  end
16
16
 
17
17
  task :environment_options => :environment do
@@ -30,7 +30,7 @@ namespace :jobs do
30
30
  unprocessed_jobs = Delayed::Job.where('attempts = 0 AND created_at < ?', Time.now - args[:max_age].to_i).count
31
31
 
32
32
  if unprocessed_jobs > 0
33
- fail "#{unprocessed_jobs} jobs older than #{args[:max_age]} seconds have not been processed yet"
33
+ raise "#{unprocessed_jobs} jobs older than #{args[:max_age]} seconds have not been processed yet"
34
34
  end
35
35
 
36
36
  end
@@ -7,8 +7,7 @@ require 'logger'
7
7
  require 'benchmark'
8
8
 
9
9
  module Delayed
10
-
11
- class Worker
10
+ class Worker # rubocop:disable ClassLength
12
11
  DEFAULT_LOG_LEVEL = 'info'
13
12
  DEFAULT_SLEEP_DELAY = 5
14
13
  DEFAULT_MAX_ATTEMPTS = 25
@@ -19,8 +18,8 @@ module Delayed
19
18
  DEFAULT_READ_AHEAD = 5
20
19
 
21
20
  cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time,
22
- :default_priority, :sleep_delay, :logger, :delay_jobs, :queues,
23
- :read_ahead, :plugins, :destroy_failed_jobs, :exit_on_complete
21
+ :default_priority, :sleep_delay, :logger, :delay_jobs, :queues,
22
+ :read_ahead, :plugins, :destroy_failed_jobs, :exit_on_complete
24
23
 
25
24
  # Named queue into which jobs are enqueued by default
26
25
  cattr_accessor :default_queue_name
@@ -70,12 +69,12 @@ module Delayed
70
69
  require "delayed/backend/#{backend}"
71
70
  backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
72
71
  end
73
- @@backend = backend
72
+ @@backend = backend # rubocop:disable ClassVars
74
73
  silence_warnings { ::Delayed.const_set(:Job, backend) }
75
74
  end
76
75
 
77
76
  def self.guess_backend
78
- warn "[DEPRECATION] guess_backend is deprecated. Please remove it from your code."
77
+ warn '[DEPRECATION] guess_backend is deprecated. Please remove it from your code.'
79
78
  end
80
79
 
81
80
  def self.before_fork
@@ -93,12 +92,11 @@ module Delayed
93
92
  # Re-open file handles
94
93
  @files_to_reopen.each do |file|
95
94
  begin
96
- file.reopen file.path, "a+"
95
+ file.reopen file.path, 'a+'
97
96
  file.sync = true
98
- rescue ::Exception
97
+ rescue ::Exception # rubocop:disable HandleExceptions, RescueException
99
98
  end
100
99
  end
101
-
102
100
  backend.after_fork
103
101
  end
104
102
 
@@ -106,15 +104,15 @@ module Delayed
106
104
  @lifecycle ||= Delayed::Lifecycle.new
107
105
  end
108
106
 
109
- def initialize(options={})
110
- @quiet = options.has_key?(:quiet) ? options[:quiet] : true
107
+ def initialize(options = {})
108
+ @quiet = options.key?(:quiet) ? options[:quiet] : true
111
109
  @failed_reserve_count = 0
112
110
 
113
111
  [:min_priority, :max_priority, :sleep_delay, :read_ahead, :queues, :exit_on_complete].each do |option|
114
- self.class.send("#{option}=", options[option]) if options.has_key?(option)
112
+ self.class.send("#{option}=", options[option]) if options.key?(option)
115
113
  end
116
114
 
117
- self.plugins.each { |klass| klass.new }
115
+ plugins.each { |klass| klass.new }
118
116
  end
119
117
 
120
118
  # Every worker has a unique name which by default is the pid of the process. There are some
@@ -123,29 +121,27 @@ module Delayed
123
121
  # it crashed before.
124
122
  def name
125
123
  return @name unless @name.nil?
126
- "#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
124
+ "#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}" # rubocop:disable RescueModifier
127
125
  end
128
126
 
129
127
  # Sets the name of the worker.
130
128
  # Setting the name to nil will reset the default worker name
131
- def name=(val)
132
- @name = val
133
- end
129
+ attr_writer :name
134
130
 
135
- def start
131
+ def start # rubocop:disable CyclomaticComplexity, PerceivedComplexity
136
132
  trap('TERM') do
137
133
  say 'Exiting...'
138
134
  stop
139
- raise SignalException.new('TERM') if self.class.raise_signal_exceptions
135
+ raise SignalException, 'TERM' if self.class.raise_signal_exceptions
140
136
  end
141
137
 
142
138
  trap('INT') do
143
139
  say 'Exiting...'
144
140
  stop
145
- raise SignalException.new('INT') if self.class.raise_signal_exceptions && self.class.raise_signal_exceptions != :term
141
+ raise SignalException, 'INT' if self.class.raise_signal_exceptions && self.class.raise_signal_exceptions != :term
146
142
  end
147
143
 
148
- say "Starting job worker"
144
+ say 'Starting job worker'
149
145
 
150
146
  self.class.lifecycle.run_callbacks(:execute, self) do
151
147
  loop do
@@ -159,13 +155,13 @@ module Delayed
159
155
 
160
156
  if count.zero?
161
157
  if self.class.exit_on_complete
162
- say "No more jobs available. Exiting"
158
+ say 'No more jobs available. Exiting'
163
159
  break
164
- else
165
- sleep(self.class.sleep_delay) unless stop?
160
+ elsif !stop?
161
+ sleep(self.class.sleep_delay)
166
162
  end
167
163
  else
168
- say "#{count} jobs processed at %.4f j/s, %d failed" % [count / @realtime, @result.last]
164
+ say format("#{count} jobs processed at %.4f j/s, %d failed", count / @realtime, @result.last)
169
165
  end
170
166
 
171
167
  break if stop?
@@ -189,16 +185,16 @@ module Delayed
189
185
  num.times do
190
186
  case reserve_and_run_one_job
191
187
  when true
192
- success += 1
188
+ success += 1
193
189
  when false
194
- failure += 1
190
+ failure += 1
195
191
  else
196
192
  break # leave if no work could be done
197
193
  end
198
194
  break if stop? # leave if we're exiting
199
195
  end
200
196
 
201
- return [success, failure]
197
+ [success, failure]
202
198
  end
203
199
 
204
200
  def run(job)
@@ -207,13 +203,13 @@ module Delayed
207
203
  Timeout.timeout(self.class.max_run_time.to_i, WorkerTimeout) { job.invoke_job }
208
204
  job.destroy
209
205
  end
210
- job_say job, 'COMPLETED after %.4f' % runtime
206
+ job_say job, format('COMPLETED after %.4f', runtime)
211
207
  return true # did work
212
208
  rescue DeserializationError => error
213
209
  job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}"
214
210
  failed(job)
215
- rescue Exception => error
216
- self.class.lifecycle.run_callbacks(:error, self, job){ handle_failed_job(job, error) }
211
+ rescue => error
212
+ self.class.lifecycle.run_callbacks(:error, self, job) { handle_failed_job(job, error) }
217
213
  return false # work failed
218
214
  end
219
215
 
@@ -233,8 +229,14 @@ module Delayed
233
229
 
234
230
  def failed(job)
235
231
  self.class.lifecycle.run_callbacks(:failure, self, job) do
236
- job.hook(:failure)
237
- self.class.destroy_failed_jobs ? job.destroy : job.fail!
232
+ begin
233
+ job.hook(:failure)
234
+ rescue => error
235
+ say "Error when running failure callback: #{error}", 'error'
236
+ say error.backtrace.join("\n"), 'error'
237
+ ensure
238
+ self.class.destroy_failed_jobs ? job.destroy : job.fail!
239
+ end
238
240
  end
239
241
  end
240
242
 
@@ -246,13 +248,12 @@ module Delayed
246
248
  def say(text, level = DEFAULT_LOG_LEVEL)
247
249
  text = "[Worker(#{name})] #{text}"
248
250
  puts text unless @quiet
249
- if logger
250
- # TODO: Deprecate use of Fixnum log levels
251
- if !level.is_a?(String)
252
- level = Logger::Severity.constants.detect {|i| Logger::Severity.const_get(i) == level }.to_s.downcase
253
- end
254
- logger.send(level, "#{Time.now.strftime('%FT%T%z')}: #{text}")
251
+ return unless logger
252
+ # TODO: Deprecate use of Fixnum log levels
253
+ unless level.is_a?(String)
254
+ level = Logger::Severity.constants.detect { |i| Logger::Severity.const_get(i) == level }.to_s.downcase
255
255
  end
256
+ logger.send(level, "#{Time.now.strftime('%FT%T%z')}: #{text}")
256
257
  end
257
258
 
258
259
  def max_attempts(job)
@@ -271,14 +272,14 @@ module Delayed
271
272
  # If no jobs are left we return nil
272
273
  def reserve_and_run_one_job
273
274
  job = reserve_job
274
- self.class.lifecycle.run_callbacks(:perform, self, job){ run(job) } if job
275
+ self.class.lifecycle.run_callbacks(:perform, self, job) { run(job) } if job
275
276
  end
276
277
 
277
278
  def reserve_job
278
279
  job = Delayed::Job.reserve(self)
279
280
  @failed_reserve_count = 0
280
281
  job
281
- rescue Exception => error
282
+ rescue ::Exception => error # rubocop:disable RescueException
282
283
  say "Error while reserving job: #{error}"
283
284
  Delayed::Job.recover_from(error)
284
285
  @failed_reserve_count += 1
@@ -286,5 +287,4 @@ module Delayed
286
287
  nil
287
288
  end
288
289
  end
289
-
290
290
  end
@@ -2,11 +2,10 @@ require 'rails/generators'
2
2
  require 'delayed/compatibility'
3
3
 
4
4
  class DelayedJobGenerator < Rails::Generators::Base
5
-
6
- self.source_paths << File.join(File.dirname(__FILE__), 'templates')
5
+ source_paths << File.join(File.dirname(__FILE__), 'templates')
7
6
 
8
7
  def create_executable_file
9
- template "script", "#{Delayed::Compatibility.executable_prefix}/delayed_job"
8
+ template 'script', "#{Delayed::Compatibility.executable_prefix}/delayed_job"
10
9
  chmod "#{Delayed::Compatibility.executable_prefix}/delayed_job", 0755
11
10
  end
12
11
  end
@@ -25,12 +25,11 @@ module Delayed
25
25
  self.attempts = 0
26
26
  self.priority = 0
27
27
  self.id = (self.class.id += 1)
28
- hash.each{|k,v| send(:"#{k}=", v)}
28
+ hash.each { |k, v| send(:"#{k}=", v) }
29
29
  end
30
30
 
31
- @jobs = []
32
31
  def self.all
33
- @jobs
32
+ @jobs ||= []
34
33
  end
35
34
 
36
35
  def self.count
@@ -47,29 +46,33 @@ module Delayed
47
46
  end
48
47
  end
49
48
 
50
- def self.create!(*args); create(*args); end
49
+ def self.create!(*args)
50
+ create(*args)
51
+ end
51
52
 
52
53
  def self.clear_locks!(worker_name)
53
- all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil}
54
+ all.select { |j| j.locked_by == worker_name }.each do |j|
55
+ j.locked_by = nil
56
+ j.locked_at = nil
57
+ end
54
58
  end
55
59
 
56
60
  # Find a few candidate jobs to run (in case some immediately get locked by others).
57
- def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time)
61
+ def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) # rubocop:disable CyclomaticComplexity, PerceivedComplexity
58
62
  jobs = all.select do |j|
59
63
  j.run_at <= db_time_now &&
60
- (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
61
- !j.failed?
64
+ (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) &&
65
+ !j.failed?
62
66
  end
63
-
64
- jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any?
65
- jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority
66
- jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority
67
- jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1]
67
+ jobs.select! { |j| j.priority <= Worker.max_priority } if Worker.max_priority
68
+ jobs.select! { |j| j.priority >= Worker.min_priority } if Worker.min_priority
69
+ jobs.select! { |j| Worker.queues.include?(j.queue) } if Worker.queues.any?
70
+ jobs.sort_by! { |j| [j.priority, j.run_at] }[0..limit - 1]
68
71
  end
69
72
 
70
73
  # Lock this job for this worker.
71
74
  # Returns true if we have the lock, false otherwise.
72
- def lock_exclusively!(max_run_time, worker)
75
+ def lock_exclusively!(_max_run_time, worker)
73
76
  now = self.class.db_time_now
74
77
  if locked_by != worker
75
78
  # We don't own this job so we will update the locked_by name and the locked_at
@@ -77,7 +80,7 @@ module Delayed
77
80
  self.locked_by = worker
78
81
  end
79
82
 
80
- return true
83
+ true
81
84
  end
82
85
 
83
86
  def self.db_time_now
@@ -85,7 +88,7 @@ module Delayed
85
88
  end
86
89
 
87
90
  def update_attributes(attrs = {})
88
- attrs.each{|k,v| send(:"#{k}=", v)}
91
+ attrs.each { |k, v| send(:"#{k}=", v) }
89
92
  save
90
93
  end
91
94
 
@@ -100,7 +103,9 @@ module Delayed
100
103
  true
101
104
  end
102
105
 
103
- def save!; save; end
106
+ def save!
107
+ save
108
+ end
104
109
 
105
110
  def reload
106
111
  reset
@@ -0,0 +1,57 @@
1
+ require 'helper'
2
+ require 'delayed/command'
3
+
4
+ describe Delayed::Command do
5
+ describe 'parsing --pool argument' do
6
+ it 'should parse --pool correctly' do
7
+ command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2'])
8
+
9
+ expect(command.worker_pools).to eq [
10
+ [[], 1],
11
+ [['test_queue'], 4],
12
+ [%w[mailers misc], 2]
13
+ ]
14
+ end
15
+
16
+ it 'should allow * or blank to specify any pools' do
17
+ command = Delayed::Command.new(['--pool=*:4'])
18
+ expect(command.worker_pools).to eq [
19
+ [[], 4],
20
+ ]
21
+
22
+ command = Delayed::Command.new(['--pool=:4'])
23
+ expect(command.worker_pools).to eq [
24
+ [[], 4],
25
+ ]
26
+ end
27
+
28
+ it 'should default to one worker if not specified' do
29
+ command = Delayed::Command.new(['--pool=mailers'])
30
+ expect(command.worker_pools).to eq [
31
+ [['mailers'], 1],
32
+ ]
33
+ end
34
+ end
35
+
36
+ describe 'running worker pools defined by multiple --pool arguments' do
37
+ it 'should run the correct worker processes' do
38
+ command = Delayed::Command.new(['--pool=*:1', '--pool=test_queue:4', '--pool=mailers,misc:2'])
39
+
40
+ expect(Dir).to receive(:mkdir).with('./tmp/pids').once
41
+
42
+ [
43
+ ['delayed_job.0', {:quiet => true, :pid_dir => './tmp/pids', :queues => []}],
44
+ ['delayed_job.1', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
45
+ ['delayed_job.2', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
46
+ ['delayed_job.3', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
47
+ ['delayed_job.4', {:quiet => true, :pid_dir => './tmp/pids', :queues => ['test_queue']}],
48
+ ['delayed_job.5', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}],
49
+ ['delayed_job.6', {:quiet => true, :pid_dir => './tmp/pids', :queues => %w[mailers misc]}]
50
+ ].each do |args|
51
+ expect(command).to receive(:run_process).with(*args).once
52
+ end
53
+
54
+ command.daemonize
55
+ end
56
+ end
57
+ end