backburner-allq 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +29 -0
- data/CHANGELOG.md +133 -0
- data/CONTRIBUTING.md +37 -0
- data/Gemfile +4 -0
- data/HOOKS.md +99 -0
- data/LICENSE +22 -0
- data/README.md +658 -0
- data/Rakefile +17 -0
- data/TODO +4 -0
- data/backburner-allq.gemspec +26 -0
- data/bin/backburner +7 -0
- data/circle.yml +3 -0
- data/deploy.sh +3 -0
- data/examples/custom.rb +25 -0
- data/examples/demo.rb +60 -0
- data/examples/god.rb +46 -0
- data/examples/hooked.rb +87 -0
- data/examples/retried.rb +31 -0
- data/examples/simple.rb +43 -0
- data/examples/stress.rb +31 -0
- data/lib/backburner.rb +75 -0
- data/lib/backburner/allq_wrapper.rb +317 -0
- data/lib/backburner/async_proxy.rb +25 -0
- data/lib/backburner/cli.rb +53 -0
- data/lib/backburner/configuration.rb +48 -0
- data/lib/backburner/connection.rb +157 -0
- data/lib/backburner/helpers.rb +193 -0
- data/lib/backburner/hooks.rb +53 -0
- data/lib/backburner/job.rb +118 -0
- data/lib/backburner/logger.rb +53 -0
- data/lib/backburner/performable.rb +95 -0
- data/lib/backburner/queue.rb +145 -0
- data/lib/backburner/tasks.rb +54 -0
- data/lib/backburner/version.rb +3 -0
- data/lib/backburner/worker.rb +221 -0
- data/lib/backburner/workers/forking.rb +52 -0
- data/lib/backburner/workers/simple.rb +29 -0
- data/lib/backburner/workers/threading.rb +163 -0
- data/lib/backburner/workers/threads_on_fork.rb +263 -0
- data/test/async_proxy_test.rb +36 -0
- data/test/back_burner_test.rb +88 -0
- data/test/connection_test.rb +179 -0
- data/test/fixtures/hooked.rb +122 -0
- data/test/fixtures/test_fork_jobs.rb +72 -0
- data/test/fixtures/test_forking_jobs.rb +56 -0
- data/test/fixtures/test_jobs.rb +87 -0
- data/test/fixtures/test_queue_settings.rb +14 -0
- data/test/helpers/templogger.rb +22 -0
- data/test/helpers_test.rb +278 -0
- data/test/hooks_test.rb +112 -0
- data/test/job_test.rb +185 -0
- data/test/logger_test.rb +44 -0
- data/test/performable_test.rb +88 -0
- data/test/queue_test.rb +69 -0
- data/test/test_helper.rb +128 -0
- data/test/worker_test.rb +157 -0
- data/test/workers/forking_worker_test.rb +181 -0
- data/test/workers/simple_worker_test.rb +350 -0
- data/test/workers/threading_worker_test.rb +104 -0
- data/test/workers/threads_on_fork_worker_test.rb +484 -0
- metadata +217 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
module Backburner
|
2
|
+
module Helpers
|
3
|
+
# Loads in instance and class levels
|
4
|
+
def self.included(base)
|
5
|
+
base.extend self
|
6
|
+
end
|
7
|
+
|
8
|
+
# Prints out exception_message based on specified e
|
9
|
+
def exception_message(e)
|
10
|
+
msg = [ "Exception #{e.class} -> #{e.message}" ]
|
11
|
+
|
12
|
+
base = File.expand_path(Dir.pwd) + '/'
|
13
|
+
e.backtrace.each do |t|
|
14
|
+
msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
|
15
|
+
end if e.backtrace
|
16
|
+
|
17
|
+
msg.join("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Given a word with dashes, returns a camel cased version of it.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# classify('job-name') # => 'JobName'
|
24
|
+
#
|
25
|
+
def classify(dashed_word)
|
26
|
+
dashed_word.to_s.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
27
|
+
end
|
28
|
+
|
29
|
+
# Given a class, dasherizes the name, used for getting tube names
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# dasherize('JobName') # => "job-name"
|
33
|
+
#
|
34
|
+
def dasherize(word)
|
35
|
+
classify(word).to_s.gsub(/::/, '/').
|
36
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
37
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
38
|
+
tr("_", "-").downcase
|
39
|
+
end
|
40
|
+
|
41
|
+
# Tries to find a constant with the name specified in the argument string:
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# constantize("Module") # => Module
|
45
|
+
# constantize("Test::Unit") # => Test::Unit
|
46
|
+
#
|
47
|
+
# NameError is raised when the constant is unknown.
|
48
|
+
def constantize(camel_cased_word)
|
49
|
+
camel_cased_word = camel_cased_word.to_s
|
50
|
+
|
51
|
+
if camel_cased_word.include?('-')
|
52
|
+
camel_cased_word = classify(camel_cased_word)
|
53
|
+
end
|
54
|
+
|
55
|
+
names = camel_cased_word.split('::')
|
56
|
+
names.shift if names.empty? || names.first.empty?
|
57
|
+
|
58
|
+
constant = Object
|
59
|
+
names.each do |name|
|
60
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
61
|
+
|
62
|
+
if constant.const_defined?(name, *args)
|
63
|
+
constant = constant.const_get(name)
|
64
|
+
else
|
65
|
+
constant = constant.const_missing(name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
constant
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns configuration options for backburner
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# queue_config.max_job_retries => 3
|
75
|
+
#
|
76
|
+
def queue_config
|
77
|
+
Backburner.configuration
|
78
|
+
end
|
79
|
+
|
80
|
+
# Expands a tube to include the prefix
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# expand_tube_name("foo_with_settings:3:100:6") # => <prefix>.foo_with_settings
|
84
|
+
# expand_tube_name("foo") # => <prefix>.foo
|
85
|
+
# expand_tube_name(FooJob) # => <prefix>.foo-job
|
86
|
+
#
|
87
|
+
def expand_tube_name(tube)
|
88
|
+
prefix = queue_config.tube_namespace
|
89
|
+
separator = queue_config.namespace_separator
|
90
|
+
queue_name = if tube.is_a?(String)
|
91
|
+
tube
|
92
|
+
elsif tube.respond_to?(:queue) # use queue name
|
93
|
+
queue = tube.queue
|
94
|
+
queue.is_a?(Proc) ? queue.call(tube) : queue
|
95
|
+
elsif tube.is_a?(Proc)
|
96
|
+
tube.call
|
97
|
+
elsif tube.is_a?(Class) # no queue name, use default
|
98
|
+
queue_config.primary_queue # tube.name
|
99
|
+
else # turn into a string
|
100
|
+
tube.to_s
|
101
|
+
end
|
102
|
+
[prefix.gsub(/\.$/, ''), dasherize(queue_name).gsub(/^#{prefix}/, '')].join(separator).gsub(/#{Regexp::escape(separator)}+/, separator).split(':').first
|
103
|
+
end
|
104
|
+
|
105
|
+
# Resolves job priority based on the value given. Can be integer, a class or nothing
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# resolve_priority(1000) => 1000
|
109
|
+
# resolve_priority(FooBar) => <queue priority>
|
110
|
+
# resolve_priority(nil) => <default priority>
|
111
|
+
#
|
112
|
+
def resolve_priority(pri)
|
113
|
+
if pri.respond_to?(:queue_priority)
|
114
|
+
resolve_priority(pri.queue_priority)
|
115
|
+
elsif pri.is_a?(String) || pri.is_a?(Symbol) # named priority
|
116
|
+
resolve_priority(Backburner.configuration.priority_labels[pri.to_sym])
|
117
|
+
elsif pri.is_a?(Integer) # numerical
|
118
|
+
pri
|
119
|
+
else # default
|
120
|
+
Backburner.configuration.default_priority
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Resolves job respond timeout based on the value given. Can be integer, a class or nothing
|
125
|
+
#
|
126
|
+
# @example
|
127
|
+
# resolve_respond_timeout(1000) => 1000
|
128
|
+
# resolve_respond_timeout(FooBar) => <queue respond_timeout>
|
129
|
+
# resolve_respond_timeout(nil) => <default respond_timeout>
|
130
|
+
#
|
131
|
+
def resolve_respond_timeout(ttr)
|
132
|
+
if ttr.respond_to?(:queue_respond_timeout)
|
133
|
+
resolve_respond_timeout(ttr.queue_respond_timeout)
|
134
|
+
elsif ttr.is_a?(Integer) # numerical
|
135
|
+
ttr
|
136
|
+
else # default
|
137
|
+
Backburner.configuration.respond_timeout
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Resolves max retries based on the value given. Can be integer, a class or nothing
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# resolve_max_job_retries(5) => 5
|
145
|
+
# resolve_max_job_retries(FooBar) => <queue max_job_retries>
|
146
|
+
# resolve_max_job_retries(nil) => <default max_job_retries>
|
147
|
+
#
|
148
|
+
def resolve_max_job_retries(retries)
|
149
|
+
if retries.respond_to?(:queue_max_job_retries)
|
150
|
+
resolve_max_job_retries(retries.queue_max_job_retries)
|
151
|
+
elsif retries.is_a?(Integer) # numerical
|
152
|
+
retries
|
153
|
+
else # default
|
154
|
+
Backburner.configuration.max_job_retries
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Resolves retry delay based on the value given. Can be integer, a class or nothing
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# resolve_retry_delay(5) => 5
|
162
|
+
# resolve_retry_delay(FooBar) => <queue retry_delay>
|
163
|
+
# resolve_retry_delay(nil) => <default retry_delay>
|
164
|
+
#
|
165
|
+
def resolve_retry_delay(delay)
|
166
|
+
if delay.respond_to?(:queue_retry_delay)
|
167
|
+
resolve_retry_delay(delay.queue_retry_delay)
|
168
|
+
elsif delay.is_a?(Integer) # numerical
|
169
|
+
delay
|
170
|
+
else # default
|
171
|
+
Backburner.configuration.retry_delay
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Resolves retry delay proc based on the value given. Can be proc, a class or nothing
|
176
|
+
#
|
177
|
+
# @example
|
178
|
+
# resolve_retry_delay_proc(proc) => proc
|
179
|
+
# resolve_retry_delay_proc(FooBar) => <queue retry_delay_proc>
|
180
|
+
# resolve_retry_delay_proc(nil) => <default retry_delay_proc>
|
181
|
+
#
|
182
|
+
def resolve_retry_delay_proc(proc)
|
183
|
+
if proc.respond_to?(:queue_retry_delay_proc)
|
184
|
+
resolve_retry_delay_proc(proc.queue_retry_delay_proc)
|
185
|
+
elsif proc.is_a?(Proc)
|
186
|
+
proc
|
187
|
+
else # default
|
188
|
+
Backburner.configuration.retry_delay_proc
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end # Helpers
|
193
|
+
end # Backburner
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Backburner
|
2
|
+
class Hooks
|
3
|
+
class << self
|
4
|
+
# Triggers all method hooks that match the given event type with specified arguments.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# invoke_hook_events(hookable, :before_enqueue, 'some', 'args')
|
8
|
+
# invoke_hook_events(hookable, :after_perform, 5)
|
9
|
+
#
|
10
|
+
def invoke_hook_events(hookable, event, *args)
|
11
|
+
res = find_hook_events(hookable, event).map { |e| hookable.send(e, *args) }
|
12
|
+
return false if res.any? { |result| result == false }
|
13
|
+
res
|
14
|
+
end
|
15
|
+
|
16
|
+
# Triggers all method hooks that match given around event type. Used for 'around' hooks
|
17
|
+
# that stack over the original task cumulatively onto one another.
|
18
|
+
#
|
19
|
+
# The final block will be the one that actually invokes the
|
20
|
+
# original task after calling all other around blocks.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# around_hook_events(hookable, :around_perform) { hookable.perform }
|
24
|
+
#
|
25
|
+
def around_hook_events(hookable, event, *args, &block)
|
26
|
+
raise "Please pass a block to hook events!" unless block_given?
|
27
|
+
around_hooks = find_hook_events(hookable, event).reverse
|
28
|
+
aggregate_filter = Proc.new { |&blk| blk.call }
|
29
|
+
around_hooks.each do |ah|
|
30
|
+
prior_around_filter = aggregate_filter
|
31
|
+
aggregate_filter = Proc.new do |&blk|
|
32
|
+
hookable.method(ah).call(*args) do
|
33
|
+
prior_around_filter.call(&blk)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
aggregate_filter.call(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# Returns all methods that match given hook type
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# find_hook_events(:before_enqueue)
|
46
|
+
# # => ['before_enqueue_foo', 'before_enqueue_bar']
|
47
|
+
#
|
48
|
+
def find_hook_events(hookable, event)
|
49
|
+
(hookable.methods - Object.methods).grep(/^#{event}/).sort
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end # Hooks
|
53
|
+
end # Backburner
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Backburner
|
2
|
+
# A single backburner job which can be processed and removed by the worker
|
3
|
+
class Job < SimpleDelegator
|
4
|
+
include Backburner::Helpers
|
5
|
+
|
6
|
+
# Raises when a job times out
|
7
|
+
class JobTimeout < RuntimeError; end
|
8
|
+
class JobNotFound < RuntimeError; end
|
9
|
+
class JobFormatInvalid < RuntimeError; end
|
10
|
+
class RetryJob < RuntimeError; end
|
11
|
+
|
12
|
+
attr_accessor :task, :body, :name, :args
|
13
|
+
|
14
|
+
# Construct a job to be parsed and processed
|
15
|
+
#
|
16
|
+
# task is a reserved object containing the json body in the form of
|
17
|
+
# { :class => "NewsletterSender", :args => ["foo@bar.com"] }
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Backburner::Job.new(payload)
|
21
|
+
#
|
22
|
+
def initialize(task)
|
23
|
+
@hooks = Backburner::Hooks
|
24
|
+
@task = task
|
25
|
+
@body = task.body.is_a?(Hash) ? task.body : Backburner.configuration.job_parser_proc.call(task.body)
|
26
|
+
@name = body["class"] || body[:class]
|
27
|
+
@args = body["args"] || body[:args]
|
28
|
+
rescue => ex # Job was not valid format
|
29
|
+
self.bury
|
30
|
+
raise JobFormatInvalid, "Job body could not be parsed: #{ex.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the delegator object to the underlying beaneater job
|
34
|
+
# self.bury
|
35
|
+
def __getobj__
|
36
|
+
__setobj__(@task)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
# Processes a job and handles any failure, deleting the job once complete
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# @task.process
|
44
|
+
#
|
45
|
+
def process
|
46
|
+
# Invoke before hook and stop if false
|
47
|
+
res = @hooks.invoke_hook_events(job_name, :before_perform, *args)
|
48
|
+
return false unless res
|
49
|
+
# Execute the job
|
50
|
+
@hooks.around_hook_events(job_name, :around_perform, *args) do
|
51
|
+
# We subtract one to ensure we timeout before beanstalkd does, except if:
|
52
|
+
# a) ttr == 0, to support never timing out
|
53
|
+
# b) ttr == 1, so that we don't accidentally set it to never time out
|
54
|
+
# NB: A ttr of 1 will likely result in race conditions between
|
55
|
+
# Backburner and beanstalkd and should probably be avoided
|
56
|
+
timeout_job_after(task.ttr > 1 ? task.ttr - 1 : task.ttr) { job_class.perform(*args) }
|
57
|
+
end
|
58
|
+
task.delete
|
59
|
+
# Invoke after perform hook
|
60
|
+
@hooks.invoke_hook_events(job_name, :after_perform, *args)
|
61
|
+
rescue => e
|
62
|
+
@hooks.invoke_hook_events(job_name, :on_failure, e, *args)
|
63
|
+
raise e
|
64
|
+
end
|
65
|
+
|
66
|
+
def bury
|
67
|
+
@hooks.invoke_hook_events(job_name, :on_bury, *args)
|
68
|
+
task.bury
|
69
|
+
end
|
70
|
+
|
71
|
+
def retry(count, delay)
|
72
|
+
@hooks.invoke_hook_events(job_name, :on_retry, count, delay, *args)
|
73
|
+
task.release(delay: delay)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the class for the job handler
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# job_class # => NewsletterSender
|
80
|
+
#
|
81
|
+
def job_class
|
82
|
+
handler = try_job_class
|
83
|
+
raise(JobNotFound, self.name) unless handler
|
84
|
+
handler
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
# Attempts to return a constantized job name, otherwise reverts to the name string
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# job_name # => "SomeUnknownJob"
|
93
|
+
def job_name
|
94
|
+
handler = try_job_class
|
95
|
+
handler ? handler : self.name
|
96
|
+
end
|
97
|
+
|
98
|
+
def try_job_class
|
99
|
+
constantize(self.name)
|
100
|
+
rescue NameError
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
# Timeout job within specified block after given time.
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# timeout_job_after(3) { do_something! }
|
108
|
+
#
|
109
|
+
def timeout_job_after(secs, &block)
|
110
|
+
begin
|
111
|
+
Timeout::timeout(secs) { yield }
|
112
|
+
rescue Timeout::Error => e
|
113
|
+
raise JobTimeout, "#{name}(#{(@args||[]).join(', ')}) hit #{secs}s timeout.\nbacktrace: #{e.backtrace}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end # Job
|
118
|
+
end # Backburner
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Backburner
|
4
|
+
module Logger
|
5
|
+
# Loads in instance and class levels
|
6
|
+
def self.included(base)
|
7
|
+
base.extend self
|
8
|
+
end
|
9
|
+
|
10
|
+
# Print out when a job is about to begin
|
11
|
+
def log_job_begin(name, args)
|
12
|
+
log_info "Work job #{name} with #{args.inspect}"
|
13
|
+
Thread.current[:job_started_at] = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
# Print out when a job completed
|
17
|
+
# If message is nil, job is considered complete
|
18
|
+
def log_job_end(name, message = nil)
|
19
|
+
ellapsed = Time.now - job_started_at
|
20
|
+
ms = (ellapsed.to_f * 1000).to_i
|
21
|
+
action_word = message ? 'Finished' : 'Completed'
|
22
|
+
log_info("#{action_word} #{name} in #{ms}ms #{message}")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns true if the job logging started
|
26
|
+
def job_started_at
|
27
|
+
Thread.current[:job_started_at]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Print a message to stdout
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# log_info("Working on task")
|
34
|
+
#
|
35
|
+
def log_info(msg)
|
36
|
+
logger ? logger.info(msg) : puts(msg)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Print an error to stderr
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# log_error("Task failed!")
|
43
|
+
#
|
44
|
+
def log_error(msg)
|
45
|
+
logger ? logger.error(msg) : $stderr.puts(msg)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return logger if specified
|
49
|
+
def logger
|
50
|
+
Backburner.configuration.logger
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'backburner/async_proxy'
|
2
|
+
|
3
|
+
module Backburner
|
4
|
+
module Performable
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:include, InstanceMethods)
|
7
|
+
base.send(:include, Backburner::Queue)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
# Return proxy object to enqueue jobs for object
|
13
|
+
# Options: `pri` (priority), `delay` (delay in secs), `ttr` (time to respond), `queue` (queue name)
|
14
|
+
# @example
|
15
|
+
# @model.async(:pri => 1000).do_something("foo")
|
16
|
+
#
|
17
|
+
def async(opts={})
|
18
|
+
Backburner::AsyncProxy.new(self.class, self.id, opts)
|
19
|
+
end
|
20
|
+
end # InstanceMethods
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Return proxy object to enqueue jobs for object
|
24
|
+
# Options: `pri` (priority), `delay` (delay in secs), `ttr` (time to respond), `queue` (queue name)
|
25
|
+
# @example
|
26
|
+
# Model.async(:ttr => 300).do_something("foo")
|
27
|
+
def async(opts={})
|
28
|
+
Backburner::AsyncProxy.new(self, nil, opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Defines perform method for job processing
|
32
|
+
# @example
|
33
|
+
# perform(55, :do_something, "foo", "bar")
|
34
|
+
def perform(id, method, *args)
|
35
|
+
if id # instance
|
36
|
+
find(id).send(method, *args)
|
37
|
+
else # class method
|
38
|
+
send(method, *args)
|
39
|
+
end
|
40
|
+
end # perform
|
41
|
+
|
42
|
+
# Always handle an instance method asynchronously
|
43
|
+
# @example
|
44
|
+
# User.handle_asynchronously :send_welcome_email, queue: 'send-mail', delay: 10
|
45
|
+
def handle_asynchronously(method, opts={})
|
46
|
+
Backburner::Performable.handle_asynchronously(self, method, opts)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Always handle a class method asynchronously
|
50
|
+
# @example
|
51
|
+
# User.handle_static_asynchronously :update_recent_visitors, ttr: 300
|
52
|
+
def handle_static_asynchronously(method, opts={})
|
53
|
+
Backburner::Performable.handle_static_asynchronously(self, method, opts)
|
54
|
+
end
|
55
|
+
end # ClassMethods
|
56
|
+
|
57
|
+
|
58
|
+
# Make all calls to an instance method asynchronous. The given opts will be passed
|
59
|
+
# to the async method.
|
60
|
+
# @example
|
61
|
+
# Backburner::Performable.handle_asynchronously(MyObject, :long_task, queue: 'long-tasks')
|
62
|
+
# NB: The method called on the async proxy will be ""#{method}_without_async". This
|
63
|
+
# will also be what's given to the Worker.enqueue method so your workers need
|
64
|
+
# to know about that. It shouldn't be a problem unless the producer and consumer are
|
65
|
+
# from different codebases (or anywhere they don't both call the handle_asynchronously
|
66
|
+
# method when booting up)
|
67
|
+
def self.handle_asynchronously(klass, method, opts={})
|
68
|
+
_handle_asynchronously(klass, klass, method, opts)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Make all calls to a class method asynchronous. The given opts will be passed
|
72
|
+
# to the async method. Please see the NB on #handle_asynchronously
|
73
|
+
def self.handle_static_asynchronously(klass, method, opts={})
|
74
|
+
_handle_asynchronously(klass, klass.singleton_class, method, opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self._handle_asynchronously(klass, klass_eval_scope, method, opts={})
|
78
|
+
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
79
|
+
with_async_name = :"#{aliased_method}_with_async#{punctuation}"
|
80
|
+
without_async_name = :"#{aliased_method}_without_async#{punctuation}"
|
81
|
+
|
82
|
+
klass.send(:include, Performable) unless included_modules.include?(Performable)
|
83
|
+
klass_eval_scope.class_eval do
|
84
|
+
define_method with_async_name do |*args|
|
85
|
+
async(opts).__send__ without_async_name, *args
|
86
|
+
end
|
87
|
+
alias_method without_async_name, method.to_sym
|
88
|
+
alias_method method.to_sym, with_async_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private_class_method :_handle_asynchronously
|
92
|
+
|
93
|
+
|
94
|
+
end # Performable
|
95
|
+
end # Backburner
|