emipair-delayed_job 2.0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/.gitignore +2 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +213 -0
  4. data/Rakefile +33 -0
  5. data/VERSION +1 -0
  6. data/benchmarks.rb +33 -0
  7. data/contrib/delayed_job.monitrc +14 -0
  8. data/contrib/delayed_job_multiple.monitrc +23 -0
  9. data/emipair-delayed_job.gemspec +25 -0
  10. data/generators/delayed_job/delayed_job_generator.rb +22 -0
  11. data/generators/delayed_job/templates/migration.rb +21 -0
  12. data/generators/delayed_job/templates/script +5 -0
  13. data/init.rb +1 -0
  14. data/lib/delayed/backend/active_record.rb +90 -0
  15. data/lib/delayed/backend/base.rb +111 -0
  16. data/lib/delayed/backend/data_mapper.rb +147 -0
  17. data/lib/delayed/backend/mongo_mapper.rb +110 -0
  18. data/lib/delayed/command.rb +109 -0
  19. data/lib/delayed/message_sending.rb +22 -0
  20. data/lib/delayed/performable_method.rb +62 -0
  21. data/lib/delayed/railtie.rb +10 -0
  22. data/lib/delayed/recipes.rb +31 -0
  23. data/lib/delayed/tasks.rb +15 -0
  24. data/lib/delayed/worker.rb +214 -0
  25. data/lib/delayed_job.rb +15 -0
  26. data/lib/passive_support.rb +4 -0
  27. data/lib/passive_support/basic_object.rb +16 -0
  28. data/lib/passive_support/core_ext.rb +8 -0
  29. data/lib/passive_support/core_ext/class.rb +1 -0
  30. data/lib/passive_support/core_ext/class/attribute_accessors.rb +57 -0
  31. data/lib/passive_support/core_ext/date.rb +10 -0
  32. data/lib/passive_support/core_ext/date/behavior.rb +42 -0
  33. data/lib/passive_support/core_ext/date/calculations.rb +241 -0
  34. data/lib/passive_support/core_ext/date/conversions.rb +107 -0
  35. data/lib/passive_support/core_ext/date_time.rb +12 -0
  36. data/lib/passive_support/core_ext/date_time/calculations.rb +126 -0
  37. data/lib/passive_support/core_ext/date_time/conversions.rb +107 -0
  38. data/lib/passive_support/core_ext/enumerable.rb +120 -0
  39. data/lib/passive_support/core_ext/kernel.rb +5 -0
  40. data/lib/passive_support/core_ext/kernel/agnostics.rb +11 -0
  41. data/lib/passive_support/core_ext/kernel/daemonizing.rb +7 -0
  42. data/lib/passive_support/core_ext/kernel/debugger.rb +16 -0
  43. data/lib/passive_support/core_ext/kernel/reporting.rb +59 -0
  44. data/lib/passive_support/core_ext/kernel/requires.rb +24 -0
  45. data/lib/passive_support/core_ext/module.rb +20 -0
  46. data/lib/passive_support/core_ext/module/aliasing.rb +74 -0
  47. data/lib/passive_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  48. data/lib/passive_support/core_ext/module/attr_internal.rb +32 -0
  49. data/lib/passive_support/core_ext/module/delegation.rb +135 -0
  50. data/lib/passive_support/core_ext/module/inclusion.rb +30 -0
  51. data/lib/passive_support/core_ext/module/introspection.rb +90 -0
  52. data/lib/passive_support/core_ext/module/loading.rb +23 -0
  53. data/lib/passive_support/core_ext/module/model_naming.rb +25 -0
  54. data/lib/passive_support/core_ext/module/synchronization.rb +39 -0
  55. data/lib/passive_support/core_ext/numeric.rb +9 -0
  56. data/lib/passive_support/core_ext/numeric/bytes.rb +50 -0
  57. data/lib/passive_support/core_ext/numeric/conversions.rb +19 -0
  58. data/lib/passive_support/core_ext/numeric/time.rb +81 -0
  59. data/lib/passive_support/core_ext/object.rb +6 -0
  60. data/lib/passive_support/core_ext/object/blank.rb +76 -0
  61. data/lib/passive_support/core_ext/object/conversions.rb +15 -0
  62. data/lib/passive_support/core_ext/object/extending.rb +80 -0
  63. data/lib/passive_support/core_ext/object/instance_variables.rb +74 -0
  64. data/lib/passive_support/core_ext/object/misc.rb +90 -0
  65. data/lib/passive_support/core_ext/object/singleton_class.rb +13 -0
  66. data/lib/passive_support/core_ext/string.rb +1 -0
  67. data/lib/passive_support/core_ext/string/constantize.rb +7 -0
  68. data/lib/passive_support/core_ext/time.rb +46 -0
  69. data/lib/passive_support/core_ext/time/behavior.rb +13 -0
  70. data/lib/passive_support/core_ext/time/calculations.rb +313 -0
  71. data/lib/passive_support/core_ext/time/conversions.rb +90 -0
  72. data/lib/passive_support/core_ext/time/zones.rb +86 -0
  73. data/lib/passive_support/duration.rb +100 -0
  74. data/lib/passive_support/ordered_hash.rb +158 -0
  75. data/rails/init.rb +5 -0
  76. data/recipes/delayed_job.rb +1 -0
  77. data/spec/backend/active_record_job_spec.rb +46 -0
  78. data/spec/backend/data_mapper_job_spec.rb +16 -0
  79. data/spec/backend/mongo_mapper_job_spec.rb +94 -0
  80. data/spec/backend/shared_backend_spec.rb +268 -0
  81. data/spec/delayed_method_spec.rb +58 -0
  82. data/spec/performable_method_spec.rb +42 -0
  83. data/spec/sample_jobs.rb +25 -0
  84. data/spec/setup/active_record.rb +33 -0
  85. data/spec/setup/data_mapper.rb +24 -0
  86. data/spec/setup/mongo_mapper.rb +17 -0
  87. data/spec/spec_helper.rb +19 -0
  88. data/spec/story_spec.rb +17 -0
  89. data/spec/worker_spec.rb +225 -0
  90. data/tasks/jobs.rake +1 -0
  91. metadata +323 -0
@@ -0,0 +1,22 @@
1
+ module Delayed
2
+ module MessageSending
3
+ def send_later(method, *args)
4
+ Delayed::Job.enqueue Delayed::PerformableMethod.new(self, method.to_sym, args)
5
+ end
6
+
7
+ def send_at(time, method, *args)
8
+ Delayed::Job.enqueue(Delayed::PerformableMethod.new(self, method.to_sym, args), 0, time)
9
+ end
10
+
11
+ module ClassMethods
12
+ def handle_asynchronously(method)
13
+ aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
14
+ with_method, without_method = "#{aliased_method}_with_send_later#{punctuation}", "#{aliased_method}_without_send_later#{punctuation}"
15
+ define_method(with_method) do |*args|
16
+ send_later(without_method, *args)
17
+ end
18
+ alias_method_chain method, :send_later
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ class Class
2
+ def load_for_delayed_job(arg)
3
+ self
4
+ end
5
+
6
+ def dump_for_delayed_job
7
+ name
8
+ end
9
+ end
10
+
11
+ module Delayed
12
+ class PerformableMethod < Struct.new(:object, :method, :args)
13
+ STRING_FORMAT = /^LOAD\;([A-Z][\w\:]+)(?:\;(\w+))?$/
14
+
15
+ class LoadError < StandardError
16
+ end
17
+
18
+ def initialize(object, method, args)
19
+ raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method)
20
+
21
+ self.object = dump(object)
22
+ self.args = args.map { |a| dump(a) }
23
+ self.method = method.to_sym
24
+ end
25
+
26
+ def display_name
27
+ if STRING_FORMAT === object
28
+ "#{$1}#{$2 ? '#' : '.'}#{method}"
29
+ else
30
+ "#{object.class}##{method}"
31
+ end
32
+ end
33
+
34
+ def perform
35
+ load(object).send(method, *args.map{|a| load(a)})
36
+ rescue PerformableMethod::LoadError => le
37
+ # We cannot do anything about objects that can't be loaded
38
+ true
39
+ end
40
+
41
+ private
42
+
43
+ def load(obj)
44
+ if STRING_FORMAT === obj
45
+ $1.constantize.load_for_delayed_job($2)
46
+ else
47
+ obj
48
+ end
49
+ rescue => e
50
+ Delayed::Worker.logger.warn "Could not load object for job: #{e.message}"
51
+ raise PerformableMethod::LoadError
52
+ end
53
+
54
+ def dump(obj)
55
+ if obj.respond_to?(:dump_for_delayed_job)
56
+ "LOAD;#{obj.dump_for_delayed_job}"
57
+ else
58
+ obj
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ require 'delayed_job'
2
+ require 'rails'
3
+
4
+ module Delayed
5
+ class Railtie < Rails::Railtie
6
+ initializer :after_initialize do
7
+ Delayed::Worker.guess_backend
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ # Capistrano Recipes for managing delayed_job
2
+ #
3
+ # Add these callbacks to have the delayed_job process restart when the server
4
+ # is restarted:
5
+ #
6
+ # after "deploy:stop", "delayed_job:stop"
7
+ # after "deploy:start", "delayed_job:start"
8
+ # after "deploy:restart", "delayed_job:restart"
9
+
10
+ Capistrano::Configuration.instance.load do
11
+ namespace :delayed_job do
12
+ def rails_env
13
+ fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
14
+ end
15
+
16
+ desc "Stop the delayed_job process"
17
+ task :stop, :roles => :app do
18
+ run "cd #{current_path};#{rails_env} script/delayed_job stop"
19
+ end
20
+
21
+ desc "Start the delayed_job process"
22
+ task :start, :roles => :app do
23
+ run "cd #{current_path};#{rails_env} script/delayed_job start"
24
+ end
25
+
26
+ desc "Restart the delayed_job process"
27
+ task :restart, :roles => :app do
28
+ run "cd #{current_path};#{rails_env} script/delayed_job restart"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # Re-definitions are appended to existing tasks
2
+ task :environment
3
+ task :merb_env
4
+
5
+ namespace :jobs do
6
+ desc "Clear the delayed_job queue."
7
+ task :clear => [:merb_env, :environment] do
8
+ Delayed::Job.delete_all
9
+ end
10
+
11
+ desc "Start a delayed_job worker."
12
+ task :work => [:merb_env, :environment] do
13
+ Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY']).start
14
+ end
15
+ end
@@ -0,0 +1,214 @@
1
+ require 'timeout'
2
+ require 'extlib'
3
+ require 'forwardable'
4
+
5
+ module Delayed
6
+
7
+ class DelayedMerbLogger
8
+ extend Forwardable
9
+
10
+ def initialize
11
+ @logger = Merb.logger
12
+ end
13
+
14
+ def_delegators :@logger, :<<, :debug, :info, :warn, :error, :fatal, :flush, :close, :push, :set_log
15
+
16
+ def add(level, message)
17
+ self.__send__(choose_level(level), message)
18
+ end
19
+
20
+ def choose_level(level)
21
+ case level
22
+ when Logger::FATAL then :fatal
23
+ when Logger::ERROR then :error
24
+ when Logger::WARN then :warn
25
+ when Logger::DEBUG then :debug
26
+ else :info
27
+ end
28
+ end
29
+ end
30
+
31
+ class Worker
32
+ cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :sleep_delay, :logger
33
+ self.sleep_delay = 5
34
+ self.max_attempts = 25
35
+ self.max_run_time = 4.hours
36
+
37
+ # By default failed jobs are destroyed after too many attempts. If you want to keep them around
38
+ # (perhaps to inspect the reason for the failure), set this to false.
39
+ cattr_accessor :destroy_failed_jobs
40
+ self.destroy_failed_jobs = true
41
+
42
+ self.logger =
43
+ if defined?(Merb::Logger)
44
+ DelayedMerbLogger.new
45
+ elsif defined?(RAILS_DEFAULT_LOGGER)
46
+ RAILS_DEFAULT_LOGGER
47
+ end
48
+
49
+ # name_prefix is ignored if name is set directly
50
+ attr_accessor :name_prefix
51
+
52
+ cattr_reader :backend
53
+
54
+ def self.backend=(backend)
55
+ if backend.is_a? Symbol
56
+ require "delayed/backend/#{backend}"
57
+ backend = "Delayed::Backend::#{backend.to_s.to_const_string}::Job".constantize
58
+ end
59
+ @@backend = backend
60
+ silence_warnings { ::Delayed.const_set(:Job, backend) }
61
+ end
62
+
63
+ def self.guess_backend
64
+ self.backend ||= if defined?(ActiveRecord)
65
+ :active_record
66
+ elsif defined?(MongoMapper)
67
+ :mongo_mapper
68
+ else
69
+ logger.warn "Could not decide on a backend, defaulting to active_record"
70
+ :active_record
71
+ end
72
+ end
73
+
74
+ def initialize(options={})
75
+ @quiet = options[:quiet]
76
+ self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority)
77
+ self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority)
78
+ end
79
+
80
+ # Every worker has a unique name which by default is the pid of the process. There are some
81
+ # advantages to overriding this with something which survives worker retarts: Workers can#
82
+ # safely resume working on tasks which are locked by themselves. The worker will assume that
83
+ # it crashed before.
84
+ def name
85
+ return @name unless @name.nil?
86
+ "#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
87
+ end
88
+
89
+ # Sets the name of the worker.
90
+ # Setting the name to nil will reset the default worker name
91
+ def name=(val)
92
+ @name = val
93
+ end
94
+
95
+ def start
96
+ say "*** Starting job worker #{name}"
97
+
98
+ trap('TERM') { say 'Exiting...'; $exit = true }
99
+ trap('INT') { say 'Exiting...'; $exit = true }
100
+ trap('QUIT') do
101
+ say "Delayed::Worker stack trace:"
102
+ say caller.join("\t\n")
103
+ $exit = false
104
+ end
105
+
106
+ loop do
107
+ result = nil
108
+
109
+ realtime = Benchmark.realtime do
110
+ result = work_off
111
+ end
112
+
113
+ count = result.sum
114
+
115
+ break if $exit
116
+
117
+ if count.zero?
118
+ sleep(@@sleep_delay)
119
+ else
120
+ say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
121
+ end
122
+
123
+ break if $exit
124
+ end
125
+
126
+ ensure
127
+ Delayed::Job.clear_locks!(name)
128
+ end
129
+
130
+ # Do num jobs and return stats on success/failure.
131
+ # Exit early if interrupted.
132
+ def work_off(num = 100)
133
+ success, failure = 0, 0
134
+
135
+ num.times do
136
+ case reserve_and_run_one_job
137
+ when true
138
+ success += 1
139
+ when false
140
+ failure += 1
141
+ else
142
+ break # leave if no work could be done
143
+ end
144
+ break if $exit # leave if we're exiting
145
+ end
146
+
147
+ return [success, failure]
148
+ end
149
+
150
+ def run(job)
151
+ runtime = Benchmark.realtime do
152
+ Timeout.timeout(self.class.max_run_time.to_i) { job.invoke_job }
153
+ job.destroy
154
+ end
155
+ # TODO: warn if runtime > max_run_time ?
156
+ say "* [JOB] #{name} completed after %.4f" % runtime
157
+ return true # did work
158
+ rescue Exception => e
159
+ handle_failed_job(job, e)
160
+ return false # work failed
161
+ end
162
+
163
+ # Reschedule the job in the future (when a job fails).
164
+ # Uses an exponential scale depending on the number of failed attempts.
165
+ def reschedule(job, time = nil)
166
+ if (job.attempts += 1) < self.class.max_attempts
167
+ time ||= Job.db_time_now + (job.attempts ** 4) + 5
168
+ job.run_at = time
169
+ job.unlock
170
+ job.save!
171
+ else
172
+ say "* [JOB] PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
173
+
174
+ if job.payload_object.respond_to? :on_permanent_failure
175
+ say "* [JOB] Running on_permanent_failure hook"
176
+ job.payload_object.on_permanent_failure
177
+ end
178
+
179
+ self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
180
+ end
181
+ end
182
+
183
+ def say(text, level = Logger::INFO)
184
+ puts text unless @quiet
185
+ logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger
186
+ end
187
+
188
+ protected
189
+
190
+ def handle_failed_job(job, error)
191
+ job.last_error = error.message + "\n" + error.backtrace.join("\n")
192
+ say "* [JOB] #{name} failed with #{error.class.name}: #{error.message} - #{job.attempts} failed attempts", Logger::ERROR
193
+ reschedule(job)
194
+ end
195
+
196
+ # Run the next job we can get an exclusive lock on.
197
+ # If no jobs are left we return nil
198
+ def reserve_and_run_one_job
199
+ # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
200
+ # this leads to a more even distribution of jobs across the worker processes
201
+ job = Delayed::Job.find_available(name, 5, self.class.max_run_time).detect do |job|
202
+ if job.lock_exclusively!(self.class.max_run_time, name)
203
+ say "* [Worker(#{name})] acquired lock on #{job.name}"
204
+ true
205
+ else
206
+ say "* [Worker(#{name})] failed to acquire exclusive lock for #{job.name}", Logger::WARN
207
+ false
208
+ end
209
+ end rescue nil
210
+
211
+ run(job) if job
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,15 @@
1
+ require 'passive_support'
2
+ require 'benchmark'
3
+
4
+ require File.dirname(__FILE__) + '/delayed/message_sending'
5
+ require File.dirname(__FILE__) + '/delayed/performable_method'
6
+ require File.dirname(__FILE__) + '/delayed/backend/base'
7
+ require File.dirname(__FILE__) + '/delayed/worker'
8
+ require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie)
9
+
10
+ Object.send(:include, Delayed::MessageSending)
11
+ Module.send(:include, Delayed::MessageSending::ClassMethods)
12
+
13
+ if defined?(Merb::Plugins)
14
+ Merb::Plugins.add_rakefiles File.dirname(__FILE__) / 'delayed' / 'tasks'
15
+ end
@@ -0,0 +1,4 @@
1
+ require 'passive_support/basic_object'
2
+ require 'passive_support/duration'
3
+ require 'passive_support/core_ext'
4
+ require 'passive_support/ordered_hash'
@@ -0,0 +1,16 @@
1
+ module PassiveSupport
2
+ if defined? ::BasicObject
3
+ class BasicObject < ::BasicObject
4
+ undef_method :==
5
+ undef_method :equal?
6
+
7
+ # Let PassiveSupport::BasicObject at least raise exceptions.
8
+ def raise(*args)
9
+ ::Object.send(:raise, *args)
10
+ end
11
+ end
12
+ else
13
+ require 'blankslate'
14
+ BasicObject = BlankSlate
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ filenames = Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.map do |path|
2
+ File.basename(path, '.rb')
3
+ end
4
+
5
+ # deprecated
6
+ filenames -= %w(blank)
7
+
8
+ filenames.each { |filename| require "passive_support/core_ext/#{filename}" }
@@ -0,0 +1 @@
1
+ require 'passive_support/core_ext/class/attribute_accessors'
@@ -0,0 +1,57 @@
1
+ class Class
2
+ def cattr_reader(* syms)
3
+ options = extract_options!(syms)
4
+ syms.each do |sym|
5
+ next if sym.is_a?(Hash)
6
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
7
+ unless defined? @@#{sym}
8
+ @@#{sym} = nil
9
+ end
10
+
11
+ def self.#{sym}
12
+ @@#{sym}
13
+ end
14
+ EOS
15
+
16
+ unless options[:instance_reader] == false
17
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
18
+ def #{sym}
19
+ @@#{sym}
20
+ end
21
+ EOS
22
+ end
23
+ end
24
+ end
25
+
26
+ def cattr_writer(* syms)
27
+ options = extract_options!(syms)
28
+ syms.each do |sym|
29
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
30
+ unless defined? @@#{sym}
31
+ @@#{sym} = nil
32
+ end
33
+
34
+ def self.#{sym}=(obj)
35
+ @@#{sym} = obj
36
+ end
37
+ EOS
38
+
39
+ unless options[:instance_writer] == false
40
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
41
+ def #{sym}=(obj)
42
+ @@#{sym} = obj
43
+ end
44
+ EOS
45
+ end
46
+ end
47
+ end
48
+
49
+ def cattr_accessor(*syms)
50
+ cattr_reader(*syms)
51
+ cattr_writer(*syms)
52
+ end
53
+
54
+ def extract_options!(array)
55
+ array.last.is_a?(::Hash) ? array.pop : {}
56
+ end
57
+ end