delayed_job_tgmerritt 4.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +208 -0
  3. data/CONTRIBUTING.md +27 -0
  4. data/LICENSE.md +20 -0
  5. data/README.md +424 -0
  6. data/Rakefile +15 -0
  7. data/contrib/delayed_job.monitrc +14 -0
  8. data/contrib/delayed_job_multiple.monitrc +34 -0
  9. data/contrib/delayed_job_rails_4.monitrc +14 -0
  10. data/contrib/delayed_job_rails_4_multiple.monitrc +34 -0
  11. data/delayed_job.gemspec +15 -0
  12. data/lib/delayed/backend/base.rb +159 -0
  13. data/lib/delayed/backend/shared_spec.rb +657 -0
  14. data/lib/delayed/command.rb +170 -0
  15. data/lib/delayed/compatibility.rb +27 -0
  16. data/lib/delayed/deserialization_error.rb +4 -0
  17. data/lib/delayed/exceptions.rb +12 -0
  18. data/lib/delayed/lifecycle.rb +85 -0
  19. data/lib/delayed/message_sending.rb +52 -0
  20. data/lib/delayed/performable_mailer.rb +22 -0
  21. data/lib/delayed/performable_method.rb +41 -0
  22. data/lib/delayed/plugin.rb +15 -0
  23. data/lib/delayed/plugins/clear_locks.rb +15 -0
  24. data/lib/delayed/psych_ext.rb +89 -0
  25. data/lib/delayed/railtie.rb +22 -0
  26. data/lib/delayed/recipes.rb +54 -0
  27. data/lib/delayed/serialization/active_record.rb +17 -0
  28. data/lib/delayed/syck_ext.rb +42 -0
  29. data/lib/delayed/tasks.rb +39 -0
  30. data/lib/delayed/worker.rb +312 -0
  31. data/lib/delayed/yaml_ext.rb +10 -0
  32. data/lib/delayed_job.rb +22 -0
  33. data/lib/generators/delayed_job/delayed_job_generator.rb +11 -0
  34. data/lib/generators/delayed_job/templates/script +5 -0
  35. data/recipes/delayed_job.rb +1 -0
  36. data/spec/autoloaded/clazz.rb +7 -0
  37. data/spec/autoloaded/instance_clazz.rb +6 -0
  38. data/spec/autoloaded/instance_struct.rb +7 -0
  39. data/spec/autoloaded/struct.rb +8 -0
  40. data/spec/daemons.rb +2 -0
  41. data/spec/delayed/backend/test.rb +117 -0
  42. data/spec/delayed/command_spec.rb +180 -0
  43. data/spec/delayed/serialization/test.rb +0 -0
  44. data/spec/helper.rb +85 -0
  45. data/spec/lifecycle_spec.rb +75 -0
  46. data/spec/message_sending_spec.rb +122 -0
  47. data/spec/performable_mailer_spec.rb +43 -0
  48. data/spec/performable_method_spec.rb +111 -0
  49. data/spec/psych_ext_spec.rb +12 -0
  50. data/spec/sample_jobs.rb +111 -0
  51. data/spec/test_backend_spec.rb +13 -0
  52. data/spec/worker_spec.rb +175 -0
  53. data/spec/yaml_ext_spec.rb +48 -0
  54. metadata +144 -0
@@ -0,0 +1,170 @@
1
+ unless ENV['RAILS_ENV'] == 'test'
2
+ begin
3
+ require 'daemons'
4
+ rescue LoadError
5
+ raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
6
+ end
7
+ end
8
+ require 'optparse'
9
+ require 'pathname'
10
+
11
+ module Delayed
12
+ class Command # rubocop:disable ClassLength
13
+ attr_accessor :worker_count, :worker_pools
14
+
15
+ DIR_PWD = Pathname.new Dir.pwd
16
+
17
+ def initialize(args) # rubocop:disable MethodLength
18
+ @options = {
19
+ :quiet => true,
20
+ :pid_dir => "#{root}/tmp/pids",
21
+ :log_dir => "#{root}/log"
22
+ }
23
+
24
+ @worker_count = 1
25
+ @monitor = false
26
+
27
+ opts = OptionParser.new do |opt|
28
+ opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] start|stop|restart|run"
29
+
30
+ opt.on('-h', '--help', 'Show this message') do
31
+ puts opt
32
+ exit 1
33
+ end
34
+ opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e|
35
+ STDERR.puts 'The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7'
36
+ end
37
+ opt.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
38
+ @options[:min_priority] = n
39
+ end
40
+ opt.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
41
+ @options[:max_priority] = n
42
+ end
43
+ opt.on('-n', '--number_of_workers=workers', 'Number of unique workers to spawn') do |worker_count|
44
+ @worker_count = worker_count.to_i rescue 1 # rubocop:disable RescueModifier
45
+ end
46
+ opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
47
+ @options[:pid_dir] = dir
48
+ end
49
+ opt.on('--log-dir=DIR', 'Specifies an alternate directory in which to store the delayed_job log.') do |dir|
50
+ @options[:log_dir] = dir
51
+ end
52
+ opt.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
53
+ @options[:identifier] = n
54
+ end
55
+ opt.on('-m', '--monitor', 'Start monitor process.') do
56
+ @monitor = true
57
+ end
58
+ opt.on('--sleep-delay N', 'Amount of time to sleep when no jobs are found') do |n|
59
+ @options[:sleep_delay] = n.to_i
60
+ end
61
+ opt.on('--read-ahead N', 'Number of jobs from the queue to consider') do |n|
62
+ @options[:read_ahead] = n
63
+ end
64
+ opt.on('-p', '--prefix NAME', 'String to be prefixed to worker process names') do |prefix|
65
+ @options[:prefix] = prefix
66
+ end
67
+ opt.on('--queues=queues', 'Specify which queue DJ must look up for jobs') do |queues|
68
+ @options[:queues] = queues.split(',')
69
+ end
70
+ opt.on('--queue=queue', 'Specify which queue DJ must look up for jobs') do |queue|
71
+ @options[:queues] = queue.split(',')
72
+ end
73
+ opt.on('--pool=queue1[,queue2][:worker_count]', 'Specify queues and number of workers for a worker pool') do |pool|
74
+ parse_worker_pool(pool)
75
+ end
76
+ 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
77
+ @options[:exit_on_complete] = true
78
+ end
79
+ end
80
+ @args = opts.parse!(args)
81
+ end
82
+
83
+ def daemonize # rubocop:disable PerceivedComplexity
84
+ dir = @options[:pid_dir]
85
+ Dir.mkdir(dir) unless File.exist?(dir)
86
+
87
+ if worker_pools
88
+ setup_pools
89
+ elsif @options[:identifier]
90
+ if worker_count > 1
91
+ raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
92
+ else
93
+ run_process("delayed_job.#{@options[:identifier]}", @options)
94
+ end
95
+ else
96
+ worker_count.times do |worker_index|
97
+ process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}"
98
+ run_process(process_name, @options)
99
+ end
100
+ end
101
+ end
102
+
103
+ def setup_pools
104
+ worker_index = 0
105
+ @worker_pools.each do |queues, worker_count|
106
+ options = @options.merge(:queues => queues)
107
+ worker_count.times do
108
+ process_name = "delayed_job.#{worker_index}"
109
+ run_process(process_name, options)
110
+ worker_index += 1
111
+ end
112
+ end
113
+ end
114
+
115
+ def run_process(process_name, options = {})
116
+ Delayed::Worker.before_fork
117
+ Daemons.run_proc(process_name, :dir => options[:pid_dir], :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*_args|
118
+ $0 = File.join(options[:prefix], process_name) if @options[:prefix]
119
+ run process_name, options
120
+ end
121
+ end
122
+
123
+ def run(worker_name = nil, options = {})
124
+ Dir.chdir(root)
125
+
126
+ Delayed::Worker.after_fork
127
+ Delayed::Worker.logger ||= Logger.new(File.join(@options[:log_dir], 'delayed_job.log'))
128
+
129
+ worker = Delayed::Worker.new(options)
130
+ worker.name_prefix = "#{worker_name} "
131
+ worker.start
132
+ rescue => e
133
+ STDERR.puts e.message
134
+ STDERR.puts e.backtrace
135
+ ::Rails.logger.fatal(e) if rails_logger_defined?
136
+ exit_with_error_status
137
+ end
138
+
139
+ private
140
+
141
+ def parse_worker_pool(pool)
142
+ @worker_pools ||= []
143
+
144
+ queues, worker_count = pool.split(':')
145
+ if ['*', '', nil].include?(queues)
146
+ queues = []
147
+ else
148
+ queues = queues.split(',')
149
+ end
150
+ worker_count = (worker_count || 1).to_i rescue 1
151
+ @worker_pools << [queues, worker_count]
152
+ end
153
+
154
+ def root
155
+ @root ||= rails_root_defined? ? ::Rails.root : DIR_PWD
156
+ end
157
+
158
+ def rails_root_defined?
159
+ defined?(::Rails.root)
160
+ end
161
+
162
+ def rails_logger_defined?
163
+ defined?(::Rails.logger)
164
+ end
165
+
166
+ def exit_with_error_status
167
+ exit 1
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/version'
2
+
3
+ module Delayed
4
+ module Compatibility
5
+ if ActiveSupport::VERSION::MAJOR >= 4
6
+ require 'active_support/proxy_object'
7
+
8
+ def self.executable_prefix
9
+ 'bin'
10
+ end
11
+
12
+ def self.proxy_object_class
13
+ ActiveSupport::ProxyObject
14
+ end
15
+ else
16
+ require 'active_support/basic_object'
17
+
18
+ def self.executable_prefix
19
+ 'script'
20
+ end
21
+
22
+ def self.proxy_object_class
23
+ ActiveSupport::BasicObject
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module Delayed
2
+ class DeserializationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ require 'timeout'
2
+
3
+ module Delayed
4
+ class WorkerTimeout < Timeout::Error
5
+ def message
6
+ seconds = Delayed::Worker.max_run_time.to_i
7
+ "#{super} (Delayed::Worker.max_run_time is only #{seconds} second#{seconds == 1 ? '' : 's'})"
8
+ end
9
+ end
10
+
11
+ class FatalBackendError < Exception; end
12
+ end
@@ -0,0 +1,85 @@
1
+ module Delayed
2
+ class InvalidCallback < Exception; end
3
+
4
+ class Lifecycle
5
+ EVENTS = {
6
+ :enqueue => [:job],
7
+ :execute => [:worker],
8
+ :loop => [:worker],
9
+ :perform => [:worker, :job],
10
+ :error => [:worker, :job],
11
+ :failure => [:worker, :job],
12
+ :invoke_job => [:job]
13
+ }
14
+
15
+ def initialize
16
+ @callbacks = EVENTS.keys.each_with_object({}) do |e, hash|
17
+ hash[e] = Callback.new
18
+ end
19
+ end
20
+
21
+ def before(event, &block)
22
+ add(:before, event, &block)
23
+ end
24
+
25
+ def after(event, &block)
26
+ add(:after, event, &block)
27
+ end
28
+
29
+ def around(event, &block)
30
+ add(:around, event, &block)
31
+ end
32
+
33
+ def run_callbacks(event, *args, &block)
34
+ missing_callback(event) unless @callbacks.key?(event)
35
+
36
+ unless EVENTS[event].size == args.size
37
+ raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}"
38
+ end
39
+
40
+ @callbacks[event].execute(*args, &block)
41
+ end
42
+
43
+ private
44
+
45
+ def add(type, event, &block)
46
+ missing_callback(event) unless @callbacks.key?(event)
47
+ @callbacks[event].add(type, &block)
48
+ end
49
+
50
+ def missing_callback(event)
51
+ raise InvalidCallback, "Unknown callback event: #{event}"
52
+ end
53
+ end
54
+
55
+ class Callback
56
+ def initialize
57
+ @before = []
58
+ @after = []
59
+
60
+ # Identity proc. Avoids special cases when there is no existing around chain.
61
+ @around = lambda { |*args, &block| block.call(*args) }
62
+ end
63
+
64
+ def execute(*args, &block)
65
+ @before.each { |c| c.call(*args) }
66
+ result = @around.call(*args, &block)
67
+ @after.each { |c| c.call(*args) }
68
+ result
69
+ end
70
+
71
+ def add(type, &callback)
72
+ case type
73
+ when :before
74
+ @before << callback
75
+ when :after
76
+ @after << callback
77
+ when :around
78
+ chain = @around # use a local variable so that the current chain is closed over in the following lambda
79
+ @around = lambda { |*a, &block| chain.call(*a) { |*b| callback.call(*b, &block) } }
80
+ else
81
+ raise InvalidCallback, "Invalid callback type: #{type}"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,52 @@
1
+ require 'active_support/core_ext/module/aliasing'
2
+
3
+ module Delayed
4
+ class DelayProxy < Delayed::Compatibility.proxy_object_class
5
+ def initialize(payload_class, target, options)
6
+ @payload_class = payload_class
7
+ @target = target
8
+ @options = options
9
+ end
10
+
11
+ def method_missing(method, *args)
12
+ Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options))
13
+ end
14
+ end
15
+
16
+ module MessageSending
17
+ def delay(options = {})
18
+ DelayProxy.new(PerformableMethod, self, options)
19
+ end
20
+ alias_method :__delay__, :delay
21
+
22
+ def send_later(method, *args)
23
+ warn '[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method'
24
+ __delay__.__send__(method, *args)
25
+ end
26
+
27
+ def send_at(time, method, *args)
28
+ warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method'
29
+ __delay__(:run_at => time).__send__(method, *args)
30
+ end
31
+
32
+ module ClassMethods
33
+ def handle_asynchronously(method, opts = {})
34
+ aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable PerlBackrefs
35
+ with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}"
36
+ define_method(with_method) do |*args|
37
+ curr_opts = opts.clone
38
+ curr_opts.each_key do |key|
39
+ next unless (val = curr_opts[key]).is_a?(Proc)
40
+ curr_opts[key] = if val.arity == 1
41
+ val.call(self)
42
+ else
43
+ val.call
44
+ end
45
+ end
46
+ delay(curr_opts).__send__(without_method, *args)
47
+ end
48
+ alias_method_chain method, :delay
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ require 'mail'
2
+
3
+ module Delayed
4
+ class PerformableMailer < PerformableMethod
5
+ def perform
6
+ mailer = object.send(method_name, *args)
7
+ mailer.respond_to?(:deliver_now) ? mailer.deliver_now : mailer.deliver
8
+ end
9
+ end
10
+
11
+ module DelayMail
12
+ def delay(options = {})
13
+ DelayProxy.new(PerformableMailer, self, options)
14
+ end
15
+ end
16
+ end
17
+
18
+ Mail::Message.class_eval do
19
+ def delay(*_args)
20
+ raise 'Use MyMailer.delay.mailer_action(args) to delay sending of emails.'
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Delayed
4
+ class PerformableMethod
5
+ attr_accessor :object, :method_name, :args
6
+
7
+ delegate :method, :to => :object
8
+
9
+ def initialize(object, method_name, args)
10
+ raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
11
+
12
+ if object.respond_to?(:persisted?) && !object.persisted?
13
+ raise(ArgumentError, "job cannot be created for non-persisted record: #{object.inspect}")
14
+ end
15
+
16
+ self.object = object
17
+ self.args = args
18
+ self.method_name = method_name.to_sym
19
+ end
20
+
21
+ def display_name
22
+ if object.is_a?(Class)
23
+ "#{object}.#{method_name}"
24
+ else
25
+ "#{object.class}##{method_name}"
26
+ end
27
+ end
28
+
29
+ def perform
30
+ object.send(method_name, *args) if object
31
+ end
32
+
33
+ def method_missing(symbol, *args)
34
+ object.send(symbol, *args)
35
+ end
36
+
37
+ def respond_to?(symbol, include_private = false)
38
+ super || object.respond_to?(symbol, include_private)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module Delayed
4
+ class Plugin
5
+ class_attribute :callback_block
6
+
7
+ def self.callbacks(&block)
8
+ self.callback_block = block
9
+ end
10
+
11
+ def initialize
12
+ self.class.callback_block.call(Delayed::Worker.lifecycle) if self.class.callback_block
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Delayed
2
+ module Plugins
3
+ class ClearLocks < Plugin
4
+ callbacks do |lifecycle|
5
+ lifecycle.around(:execute) do |worker, &block|
6
+ begin
7
+ block.call(worker)
8
+ ensure
9
+ Delayed::Job.clear_locks!(worker.name)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end