delayed_job_with_named_queues 2.0.7.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.
@@ -0,0 +1,53 @@
1
+ module Delayed
2
+ class DelayProxy < ActiveSupport::BasicObject
3
+ def initialize(target, options)
4
+ @target = target
5
+ @options = options
6
+ end
7
+
8
+ def method_missing(method, *args)
9
+ Job.create({
10
+ :payload_object => PerformableMethod.new(@target, method.to_sym, args),
11
+ :priority => ::Delayed::Worker.default_priority
12
+ }.merge(@options))
13
+ end
14
+ end
15
+
16
+ module MessageSending
17
+ def delay(options = {})
18
+ DelayProxy.new(self, options)
19
+ end
20
+ alias __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
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
+ if (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
+ end
47
+ delay(curr_opts).__send__(without_method, *args)
48
+ end
49
+ alias_method_chain method, :delay
50
+ end
51
+ end
52
+ end
53
+ 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, true)
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
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,50 @@
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
+ # If you want to use command line options, for example to start multiple workers,
11
+ # define a Capistrano variable delayed_job_args:
12
+ #
13
+ # set :delayed_jobs_args, "-n 2"
14
+ #
15
+ # If you've got delayed_job workers running on a servers, you can also specify
16
+ # which servers have delayed_job running and should be restarted after deploy.
17
+ #
18
+ # set :delayed_job_server_role, :worker
19
+ #
20
+
21
+ Capistrano::Configuration.instance.load do
22
+ namespace :delayed_job do
23
+ def rails_env
24
+ fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
25
+ end
26
+
27
+ def args
28
+ fetch(:delayed_job_args, "")
29
+ end
30
+
31
+ def roles
32
+ fetch(:delayed_job_server_role, :app)
33
+ end
34
+
35
+ desc "Stop the delayed_job process"
36
+ task :stop, :roles => lambda { roles } do
37
+ run "cd #{current_path};#{rails_env} script/delayed_job stop"
38
+ end
39
+
40
+ desc "Start the delayed_job process"
41
+ task :start, :roles => lambda { roles } do
42
+ run "cd #{current_path};#{rails_env} script/delayed_job start #{args}"
43
+ end
44
+
45
+ desc "Restart the delayed_job process"
46
+ task :restart, :roles => lambda { roles } do
47
+ run "cd #{current_path};#{rails_env} script/delayed_job restart #{args}"
48
+ end
49
+ end
50
+ 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'],:queues => (ENV['QUEUES'] || ENV['QUEUE'] || '').split(',') ).start
14
+ end
15
+ end
@@ -0,0 +1,178 @@
1
+ require 'timeout'
2
+ require 'active_support/core_ext/numeric/time'
3
+
4
+ module Delayed
5
+ class Worker
6
+ cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger, :queues
7
+ self.sleep_delay = 5
8
+ self.max_attempts = 25
9
+ self.max_run_time = 4.hours
10
+ self.default_priority = 0
11
+ self.queues = []
12
+
13
+ # By default failed jobs are destroyed after too many attempts. If you want to keep them around
14
+ # (perhaps to inspect the reason for the failure), set this to false.
15
+ cattr_accessor :destroy_failed_jobs
16
+ self.destroy_failed_jobs = true
17
+
18
+ self.logger = if defined?(Merb::Logger)
19
+ Merb.logger
20
+ elsif defined?(RAILS_DEFAULT_LOGGER)
21
+ RAILS_DEFAULT_LOGGER
22
+ end
23
+
24
+ # name_prefix is ignored if name is set directly
25
+ attr_accessor :name_prefix
26
+
27
+ cattr_reader :backend
28
+
29
+ def self.backend=(backend)
30
+ if backend.is_a? Symbol
31
+ require "delayed/backend/#{backend}"
32
+ backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
33
+ end
34
+ @@backend = backend
35
+ silence_warnings { ::Delayed.const_set(:Job, backend) }
36
+ end
37
+
38
+ def self.guess_backend
39
+ self.backend ||= if defined?(ActiveRecord)
40
+ :active_record
41
+ elsif defined?(MongoMapper)
42
+ :mongo_mapper
43
+ else
44
+ logger.warn "Could not decide on a backend, defaulting to active_record"
45
+ :active_record
46
+ end
47
+ end
48
+
49
+ def initialize(options={})
50
+ @quiet = options[:quiet]
51
+ self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority)
52
+ self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority)
53
+ self.class.sleep_delay = options[:sleep_delay] if options.has_key?(:sleep_delay)
54
+ self.class.queues = options[:queues] if options.has_key?(:queues)
55
+ end
56
+
57
+ # Every worker has a unique name which by default is the pid of the process. There are some
58
+ # advantages to overriding this with something which survives worker retarts: Workers can#
59
+ # safely resume working on tasks which are locked by themselves. The worker will assume that
60
+ # it crashed before.
61
+ def name
62
+ return @name unless @name.nil?
63
+ "#{@name_prefix}host:#{Socket.gethostname} pid:#{Process.pid}" rescue "#{@name_prefix}pid:#{Process.pid}"
64
+ end
65
+
66
+ # Sets the name of the worker.
67
+ # Setting the name to nil will reset the default worker name
68
+ def name=(val)
69
+ @name = val
70
+ end
71
+
72
+ def start
73
+ say "Starting job worker"
74
+
75
+ trap('TERM') { say 'Exiting...'; $exit = true }
76
+ trap('INT') { say 'Exiting...'; $exit = true }
77
+
78
+ loop do
79
+ result = nil
80
+
81
+ realtime = Benchmark.realtime do
82
+ result = work_off
83
+ end
84
+
85
+ count = result.sum
86
+
87
+ break if $exit
88
+
89
+ if count.zero?
90
+ sleep(self.class.sleep_delay)
91
+ else
92
+ say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last]
93
+ end
94
+
95
+ break if $exit
96
+ end
97
+
98
+ ensure
99
+ Delayed::Job.clear_locks!(name)
100
+ end
101
+
102
+ # Do num jobs and return stats on success/failure.
103
+ # Exit early if interrupted.
104
+ def work_off(num = 100)
105
+ success, failure = 0, 0
106
+
107
+ num.times do
108
+ case reserve_and_run_one_job
109
+ when true
110
+ success += 1
111
+ when false
112
+ failure += 1
113
+ else
114
+ break # leave if no work could be done
115
+ end
116
+ break if $exit # leave if we're exiting
117
+ end
118
+
119
+ return [success, failure]
120
+ end
121
+
122
+ def run(job)
123
+ runtime = Benchmark.realtime do
124
+ Timeout.timeout(self.class.max_run_time.to_i) { job.invoke_job }
125
+ job.destroy
126
+ end
127
+ say "#{job.name} completed after %.4f" % runtime
128
+ return true # did work
129
+ rescue Exception => e
130
+ handle_failed_job(job, e)
131
+ return false # work failed
132
+ end
133
+
134
+ # Reschedule the job in the future (when a job fails).
135
+ # Uses an exponential scale depending on the number of failed attempts.
136
+ def reschedule(job, time = nil)
137
+ if (job.attempts += 1) < max_attempts(job)
138
+ job.run_at = time || job.reschedule_at
139
+ job.unlock
140
+ job.save!
141
+ else
142
+ say "PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO
143
+
144
+ if job.payload_object.respond_to? :on_permanent_failure
145
+ say "Running on_permanent_failure hook"
146
+ job.payload_object.on_permanent_failure
147
+ end
148
+
149
+ self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now)
150
+ end
151
+ end
152
+
153
+ def say(text, level = Logger::INFO)
154
+ text = "[Worker(#{name})] #{text}"
155
+ puts text unless @quiet
156
+ logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger
157
+ end
158
+
159
+ def max_attempts(job)
160
+ job.max_attempts || self.class.max_attempts
161
+ end
162
+
163
+ protected
164
+
165
+ def handle_failed_job(job, error)
166
+ job.last_error = error.message + "\n" + error.backtrace.join("\n")
167
+ say "#{job.name} failed with #{error.class.name}: #{error.message} - #{job.attempts} failed attempts", Logger::ERROR
168
+ reschedule(job)
169
+ end
170
+
171
+ # Run the next job we can get an exclusive lock on.
172
+ # If no jobs are left we return nil
173
+ def reserve_and_run_one_job
174
+ job = Delayed::Job.reserve(self)
175
+ run(job) if job
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_support'
2
+
3
+ require File.dirname(__FILE__) + '/delayed/message_sending'
4
+ require File.dirname(__FILE__) + '/delayed/performable_method'
5
+ require File.dirname(__FILE__) + '/delayed/backend/base'
6
+ require File.dirname(__FILE__) + '/delayed/worker'
7
+ require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie)
8
+
9
+ Object.send(:include, Delayed::MessageSending)
10
+ Module.send(:include, Delayed::MessageSending::ClassMethods)
11
+
12
+ if defined?(Merb::Plugins)
13
+ Merb::Plugins.add_rakefiles File.dirname(__FILE__) / 'delayed' / 'tasks'
14
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'delayed_job'
2
+
3
+ config.after_initialize do
4
+ Delayed::Worker.guess_backend
5
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'recipes'))
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ require 'backend/shared_backend_spec'
3
+ require 'delayed/backend/active_record'
4
+
5
+ describe Delayed::Backend::ActiveRecord::Job do
6
+ before(:all) do
7
+ @backend = Delayed::Backend::ActiveRecord::Job
8
+ end
9
+
10
+ before(:each) do
11
+ Delayed::Backend::ActiveRecord::Job.delete_all
12
+ SimpleJob.runs = 0
13
+ end
14
+
15
+ after do
16
+ Time.zone = nil
17
+ end
18
+
19
+ it_should_behave_like 'a backend'
20
+
21
+ context "db_time_now" do
22
+ it "should return time in current time zone if set" do
23
+ Time.zone = 'Eastern Time (US & Canada)'
24
+ %w(EST EDT).should include(Delayed::Job.db_time_now.zone)
25
+ end
26
+
27
+ it "should return UTC time if that is the AR default" do
28
+ Time.zone = nil
29
+ ActiveRecord::Base.default_timezone = :utc
30
+ Delayed::Backend::ActiveRecord::Job.db_time_now.zone.should == 'UTC'
31
+ end
32
+
33
+ it "should return local time if that is the AR default" do
34
+ Time.zone = 'Central Time (US & Canada)'
35
+ ActiveRecord::Base.default_timezone = :local
36
+ %w(CST CDT).should include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
37
+ end
38
+ end
39
+
40
+ describe "after_fork" do
41
+ it "should call reconnect on the connection" do
42
+ ActiveRecord::Base.connection.should_receive(:reconnect!)
43
+ Delayed::Backend::ActiveRecord::Job.after_fork
44
+ end
45
+ end
46
+ end