backburner-allq 1.0.0
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.
- 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,317 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'yajl'
|
3
|
+
require 'timeout'
|
4
|
+
require 'lz4-ruby'
|
5
|
+
require 'allq_client'
|
6
|
+
|
7
|
+
module Backburner
|
8
|
+
class AllqWatcher
|
9
|
+
attr_accessor :tube
|
10
|
+
|
11
|
+
def initialize(tube, allq_wrapper)
|
12
|
+
@tube = tube
|
13
|
+
@allq_wrapper = allq_wrapper
|
14
|
+
end
|
15
|
+
|
16
|
+
def watch
|
17
|
+
Thread.new do
|
18
|
+
ran = false
|
19
|
+
job = @allq_wrapper.get(@tube_name)
|
20
|
+
if job.body
|
21
|
+
perform(job)
|
22
|
+
ran = true
|
23
|
+
end
|
24
|
+
# Wait if nothing returned
|
25
|
+
sleep(rand() * 3) unless ran
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class AllQJob
|
31
|
+
attr_accessor :id
|
32
|
+
|
33
|
+
# Body
|
34
|
+
attr_accessor :body
|
35
|
+
|
36
|
+
# Tube name
|
37
|
+
attr_accessor :tube
|
38
|
+
|
39
|
+
# Expired count
|
40
|
+
attr_accessor :expireds
|
41
|
+
|
42
|
+
# Release count
|
43
|
+
attr_accessor :releases
|
44
|
+
|
45
|
+
def initialize(wrapper, job_resposne)
|
46
|
+
@client = wrapper
|
47
|
+
@id = job_resposne.id
|
48
|
+
@body = job_resposne.body
|
49
|
+
@expireds = job_resposne.expireds
|
50
|
+
@releases = job_resposne.releases
|
51
|
+
@tube = job_resposne.tube.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def done
|
55
|
+
@client.done(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete
|
59
|
+
@client.delete(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
def touch
|
63
|
+
@client.touch(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
def kick
|
67
|
+
@client.kick(self)
|
68
|
+
end
|
69
|
+
|
70
|
+
def release(delay = 0)
|
71
|
+
@client.release(self, delay)
|
72
|
+
end
|
73
|
+
|
74
|
+
def bury
|
75
|
+
@client.bury(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
def stats
|
79
|
+
{ 'expireds' => expireds, 'releases' => releases }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class AllQWrapper
|
84
|
+
def initialize(url = Blitline::Constants::ALLQ_DEFAULT_EU_URL)
|
85
|
+
allq_conf = Allq::Configuration.new do |config|
|
86
|
+
config.host = url
|
87
|
+
end
|
88
|
+
|
89
|
+
raw_client = Allq::ApiClient.new(allq_conf)
|
90
|
+
@client = Allq::ActionsApi.new(raw_client)
|
91
|
+
@admin = Allq::AdminApi.new(raw_client)
|
92
|
+
@recent_times = []
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def speed
|
97
|
+
if @recent_times.size > 0
|
98
|
+
return @recent_times.sum(0.0) / @recent_times.size
|
99
|
+
end
|
100
|
+
return 0
|
101
|
+
end
|
102
|
+
|
103
|
+
def touch(job)
|
104
|
+
@client.touch_put(job.id)
|
105
|
+
end
|
106
|
+
|
107
|
+
def done(job)
|
108
|
+
@client.job_delete(job.id)
|
109
|
+
end
|
110
|
+
|
111
|
+
def delete(job)
|
112
|
+
@client.job_delete(job.id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def release(job, _delay = 0)
|
116
|
+
@client.release_put(job.id)
|
117
|
+
end
|
118
|
+
|
119
|
+
def bury(job); end
|
120
|
+
|
121
|
+
def tube_names
|
122
|
+
stats_hash = stats
|
123
|
+
stats_hash.keys
|
124
|
+
end
|
125
|
+
|
126
|
+
def peek_buried(tube_name = 'default')
|
127
|
+
job = nil
|
128
|
+
job = @client.peek_get(tube_name, buried: true)
|
129
|
+
return nil if job.body.nil?
|
130
|
+
|
131
|
+
job.body = Base64.decode64(job.body) if job
|
132
|
+
job_obj = Blitline::AllQJob.new(self, job)
|
133
|
+
job_obj
|
134
|
+
end
|
135
|
+
|
136
|
+
def get(tube_name = 'default')
|
137
|
+
job = nil
|
138
|
+
delt = Blitline::Utils.get_delta do
|
139
|
+
job = @client.job_get(tube_name)
|
140
|
+
end
|
141
|
+
puts "Allq http get delta: #{delt} #{tube_name} #{job}" if delt.to_f > 1.5
|
142
|
+
|
143
|
+
@recent_times.push(delt.to_f)
|
144
|
+
@recent_times.shift if @recent_times.size > 2
|
145
|
+
# Inplace decode
|
146
|
+
job.body = Base64.decode64(job.body) if job&.body
|
147
|
+
|
148
|
+
job_obj = Blitline::AllQJob.new(self, job)
|
149
|
+
job_obj
|
150
|
+
rescue StandardError => ex
|
151
|
+
if ex.message == "Couldn't resolve host name"
|
152
|
+
BlitlineLogger.log("COUDNT RESOLVE HOST NAME------ SHOULD REBOOT")
|
153
|
+
else
|
154
|
+
BlitlineLogger.log(ex)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def close
|
159
|
+
rescue StandardError => ex
|
160
|
+
BlitlineLogger.log(ex)
|
161
|
+
end
|
162
|
+
|
163
|
+
def map_priority(app_priority)
|
164
|
+
app_priority = app_priority.to_i
|
165
|
+
|
166
|
+
# IF already using allq-like priority, stick with it
|
167
|
+
return app_priority if app_priority < 11 && app_priority > 0
|
168
|
+
|
169
|
+
default = 5
|
170
|
+
|
171
|
+
return default if app_priority == 32_000
|
172
|
+
|
173
|
+
return 6 if app_priority >= 33_000
|
174
|
+
|
175
|
+
return 7 if app_priority >= 35_000
|
176
|
+
|
177
|
+
return 8 if app_priority >= 37_000
|
178
|
+
|
179
|
+
default
|
180
|
+
end
|
181
|
+
|
182
|
+
def log_result(job_result)
|
183
|
+
BlitlineLogger.log("ALLQ-HTTP-JOB-ID=#{job_result.job_id}")
|
184
|
+
rescue StandardError => ex
|
185
|
+
BlitlineLogger.log(ex)
|
186
|
+
end
|
187
|
+
|
188
|
+
def build_new_job(body, options)
|
189
|
+
adjusted_priority = map_priority(options[:pri] || 5)
|
190
|
+
|
191
|
+
ttl = options[:ttl] || 600
|
192
|
+
tube_name = options[:tube_name] || 'default'
|
193
|
+
delay = options[:delay] || 0
|
194
|
+
parent_id = options[:parent_id]
|
195
|
+
|
196
|
+
new_job = Allq::NewJob.new(tube: tube_name,
|
197
|
+
body: Base64.strict_encode64(body),
|
198
|
+
ttl: ttl,
|
199
|
+
delay: delay,
|
200
|
+
priority: adjusted_priority,
|
201
|
+
parent_id: parent_id)
|
202
|
+
new_job
|
203
|
+
end
|
204
|
+
|
205
|
+
def build_new_parent_job(body, options)
|
206
|
+
adjusted_priority = map_priority(options[:pri] || 5)
|
207
|
+
ttl = options[:ttl] || 600
|
208
|
+
tube_name = options[:tube_name] || 'default'
|
209
|
+
delay = options[:delay] || 0
|
210
|
+
parent_id = options[:parent_id]
|
211
|
+
limit = options[:limit]
|
212
|
+
timeout = options[:timeout] || 3_600
|
213
|
+
run_on_timeout = options[:run_on_timeout] || false
|
214
|
+
|
215
|
+
new_parent_job = Allq::NewParentJob.new(tube: tube_name,
|
216
|
+
body: Base64.strict_encode64(body),
|
217
|
+
ttl: ttl,
|
218
|
+
delay: delay,
|
219
|
+
priority: adjusted_priority,
|
220
|
+
timeout: timeout,
|
221
|
+
run_on_timeout: run_on_timeout,
|
222
|
+
limit: limit)
|
223
|
+
new_parent_job
|
224
|
+
end
|
225
|
+
|
226
|
+
def put2(body, pri = 5, ttl = 600, tube_name = "default", delay = 0)
|
227
|
+
# Old school way
|
228
|
+
options = {
|
229
|
+
pri: pri,
|
230
|
+
ttl: ttl,
|
231
|
+
tube_name: tube_name,
|
232
|
+
delay: delay
|
233
|
+
}
|
234
|
+
put(body, options)
|
235
|
+
end
|
236
|
+
|
237
|
+
def put(body, options)
|
238
|
+
# New school put
|
239
|
+
retry_count = 0
|
240
|
+
is_parent = options[:is_parent] || false
|
241
|
+
result = nil
|
242
|
+
|
243
|
+
begin
|
244
|
+
Timeout.timeout(10) do
|
245
|
+
if body && body.to_s.include?('["default"]')
|
246
|
+
BlitlineLogger.log "PUTTING DEFAULT! #{caller.inspect}"
|
247
|
+
end
|
248
|
+
|
249
|
+
delt = Blitline::Utils.get_delta do
|
250
|
+
if is_parent
|
251
|
+
new_job = build_new_parent_job(body, options)
|
252
|
+
result = @client.parent_job_post(new_job)
|
253
|
+
else
|
254
|
+
new_job = build_new_job(body, options)
|
255
|
+
result = @client.job_post(new_job)
|
256
|
+
end
|
257
|
+
raise 'PUT returned nil' if result.nil? || result.to_s == ''
|
258
|
+
end
|
259
|
+
BlitlineLogger.log "Allq http put delta: #{delt}"
|
260
|
+
end
|
261
|
+
rescue Timeout::Error
|
262
|
+
BlitlineLogger.log('ALLQ_PUT_TIMEOUT')
|
263
|
+
sleep(5)
|
264
|
+
retry_count += 1
|
265
|
+
retry if retry_count < 4
|
266
|
+
raise 'Failed to put on allq, we are investigating the problem, please try again'
|
267
|
+
rescue StandardError => ex
|
268
|
+
BlitlineLogger.log('Failed to ALLQ PUT')
|
269
|
+
BlitlineLogger.log(ex)
|
270
|
+
retry_count += 1
|
271
|
+
sleep(5)
|
272
|
+
retry if retry_count < 4
|
273
|
+
raise 'Failed to put on allq, we are investigating the problem, please try again'
|
274
|
+
end
|
275
|
+
result
|
276
|
+
end
|
277
|
+
|
278
|
+
def stats
|
279
|
+
raw_stats = @admin.stats_get
|
280
|
+
final_stats = {}
|
281
|
+
|
282
|
+
raw_stats.each do |agg|
|
283
|
+
agg.stats.each do |tube_ref|
|
284
|
+
name = tube_ref.tube
|
285
|
+
final_stats[name] = {} unless final_stats[name]
|
286
|
+
final_stats[name]['ready'] = final_stats[name]['ready'].to_i + tube_ref.ready.to_i
|
287
|
+
final_stats[name]['reserved'] = final_stats[name]['reserved'].to_i + tube_ref.reserved.to_i
|
288
|
+
final_stats[name]['delayed'] = final_stats[name]['delayed'].to_i + tube_ref.delayed.to_i
|
289
|
+
final_stats[name]['buried'] = final_stats[name]['buried'].to_i + tube_ref.buried.to_i
|
290
|
+
final_stats[name]['parents'] = final_stats[name]['parents'].to_i + tube_ref.parents.to_i
|
291
|
+
end
|
292
|
+
end
|
293
|
+
final_stats
|
294
|
+
rescue StandardError => ex
|
295
|
+
BlitlineLogger.log(ex)
|
296
|
+
{}
|
297
|
+
end
|
298
|
+
|
299
|
+
def get_ready_by_tube(name)
|
300
|
+
count = -1
|
301
|
+
tube_stats = stats[name]
|
302
|
+
count = tube_stats['ready'].to_i if tube_stats && tube_stats['ready']
|
303
|
+
count
|
304
|
+
rescue StandardError => ex
|
305
|
+
BlitlineLogger.log(ex)
|
306
|
+
-1
|
307
|
+
end
|
308
|
+
|
309
|
+
def size
|
310
|
+
result = get_ready_by_tube('default')
|
311
|
+
result.to_i
|
312
|
+
rescue StandardError => ex
|
313
|
+
BlitlineLogger.log(ex)
|
314
|
+
0
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Backburner
|
2
|
+
# BasicObject for 1.8.7
|
3
|
+
class BasicObject
|
4
|
+
instance_methods.each do |m|
|
5
|
+
undef_method(m) if m.to_s !~ /(?:^__|^nil?$|^send$|^object_id$)/
|
6
|
+
end
|
7
|
+
end unless defined?(::BasicObject)
|
8
|
+
|
9
|
+
# Class allows async task to be proxied
|
10
|
+
class AsyncProxy < BasicObject
|
11
|
+
# Options include `pri` (priority), `delay` (delay in secs), `ttr` (time to respond)
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# AsyncProxy.new(User, 10, :pri => 1000, :ttr => 1000)
|
15
|
+
#
|
16
|
+
def initialize(klazz, id=nil, opts={})
|
17
|
+
@klazz, @id, @opts = klazz, id, opts
|
18
|
+
end
|
19
|
+
|
20
|
+
# Enqueue as job when a method is invoked
|
21
|
+
def method_missing(method, *args, &block)
|
22
|
+
::Backburner::Worker.enqueue(@klazz, [@id, method, *args], @opts)
|
23
|
+
end
|
24
|
+
end # AsyncProxy
|
25
|
+
end # Backburner
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'dante'
|
2
|
+
|
3
|
+
module Backburner
|
4
|
+
class CLI
|
5
|
+
|
6
|
+
def self.start(args)
|
7
|
+
runner = Dante::Runner.new('backburner')
|
8
|
+
runner.description = "Execute a backburner worker process"
|
9
|
+
runner.with_options do |opts|
|
10
|
+
opts.on("-r", "--require PATH", String, "The path to load as the environment.") do |req|
|
11
|
+
options[:require] = req
|
12
|
+
end
|
13
|
+
opts.on("-q", "--queues PATH", String, "The specific queues to work.") do |queues|
|
14
|
+
options[:queues] = queues
|
15
|
+
end
|
16
|
+
opts.on("-e", "--environment ENVIRONMENT", String, "The environment to run Backburner within") do |environment|
|
17
|
+
options[:environment] = environment
|
18
|
+
end
|
19
|
+
end
|
20
|
+
runner.execute do |opts|
|
21
|
+
queues = (opts[:queues] ? opts[:queues].split(',') : nil) rescue nil
|
22
|
+
load_environment(opts[:require], opts[:environment])
|
23
|
+
Backburner.work(queues)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def self.load_environment(file = nil, environment = nil)
|
30
|
+
file ||= "."
|
31
|
+
if File.directory?(file) && File.exists?(File.expand_path("#{file}/config/environment.rb"))
|
32
|
+
ENV["RAILS_ENV"] = environment if environment && ENV["RAILS_ENV"].nil?
|
33
|
+
require "rails"
|
34
|
+
require File.expand_path("#{file}/config/environment.rb")
|
35
|
+
if defined?(::Rails) && ::Rails.respond_to?(:application)
|
36
|
+
# Rails 3
|
37
|
+
::Rails.application.eager_load!
|
38
|
+
elsif defined?(::Rails::Initializer)
|
39
|
+
# Rails 2.3
|
40
|
+
$rails_rake_task = false
|
41
|
+
::Rails::Initializer.run :load_application_classes
|
42
|
+
end
|
43
|
+
elsif File.directory?(file) && File.exists?(File.expand_path("#{file}/config/boot.rb"))
|
44
|
+
ENV["RACK_ENV"] = environment if environment && ENV["RACK_ENV"].nil?
|
45
|
+
ENV["PADRINO_ROOT"] = file
|
46
|
+
require File.expand_path("#{file}/config/boot.rb")
|
47
|
+
elsif File.file?(file)
|
48
|
+
require File.expand_path(file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Backburner
|
2
|
+
class Configuration
|
3
|
+
PRIORITY_LABELS = { :high => 0, :medium => 5, :low => 9 }
|
4
|
+
|
5
|
+
attr_accessor :allq_url # beanstalk url connection
|
6
|
+
attr_accessor :tube_namespace # namespace prefix for every queue
|
7
|
+
attr_reader :namespace_separator # namespace separator
|
8
|
+
attr_accessor :default_priority # default job priority
|
9
|
+
attr_accessor :respond_timeout # default job timeout
|
10
|
+
attr_accessor :on_error # error handler
|
11
|
+
attr_accessor :max_job_retries # max job retries
|
12
|
+
attr_accessor :retry_delay # (minimum) retry delay in seconds
|
13
|
+
attr_accessor :retry_delay_proc # proc to calculate delay (and allow for back-off)
|
14
|
+
attr_accessor :default_queues # default queues
|
15
|
+
attr_accessor :logger # logger
|
16
|
+
attr_accessor :default_worker # default worker class
|
17
|
+
attr_accessor :primary_queue # the general queue
|
18
|
+
attr_accessor :priority_labels # priority labels
|
19
|
+
attr_accessor :reserve_timeout # duration to wait to reserve on a single server
|
20
|
+
attr_accessor :job_serializer_proc # proc to write the job body to a string
|
21
|
+
attr_accessor :job_parser_proc # proc to parse a job body from a string
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@allq_url = "127.0.0.1:8090"
|
25
|
+
@tube_namespace = ""
|
26
|
+
@namespace_separator = "."
|
27
|
+
@default_priority = 5
|
28
|
+
@respond_timeout = 120
|
29
|
+
@on_error = nil
|
30
|
+
@max_job_retries = 0
|
31
|
+
@retry_delay = 5
|
32
|
+
@retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
33
|
+
@default_queues = []
|
34
|
+
@logger = nil
|
35
|
+
@default_worker = Backburner::Workers::Simple
|
36
|
+
@primary_queue = "backburner-jobs"
|
37
|
+
@priority_labels = PRIORITY_LABELS
|
38
|
+
@reserve_timeout = nil
|
39
|
+
@job_serializer_proc = lambda { |body| body.to_json }
|
40
|
+
@job_parser_proc = lambda { |body| JSON.parse(body) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def namespace_separator=(val)
|
44
|
+
raise 'Namespace separator cannot used reserved queue configuration separator ":"' if val == ':'
|
45
|
+
@namespace_separator = val
|
46
|
+
end
|
47
|
+
end # Configuration
|
48
|
+
end # Backburner
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Backburner
|
4
|
+
class Connection < SimpleDelegator
|
5
|
+
class BadURL < RuntimeError; end
|
6
|
+
|
7
|
+
attr_accessor :url, :allq_wrapper
|
8
|
+
|
9
|
+
# If a proc is provided, it will be called (and given this connection as an
|
10
|
+
# argument) whenever the connection is reconnected.
|
11
|
+
# @example
|
12
|
+
# connection.on_reconnect = lambda { |conn| puts 'reconnected!' }
|
13
|
+
attr_accessor :on_reconnect
|
14
|
+
|
15
|
+
# Constructs a backburner connection
|
16
|
+
# `url` can be a string i.e '127.0.0.1:3001' or an array of
|
17
|
+
# addresses (however, only the first element in the array will
|
18
|
+
# be used)
|
19
|
+
def initialize(url, &on_reconnect)
|
20
|
+
@url = url
|
21
|
+
@allq_wrapper = nil
|
22
|
+
@on_reconnect = on_reconnect
|
23
|
+
connect!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Close the connection, if it exists
|
27
|
+
def close
|
28
|
+
@allq_wrapper.close if @allq_wrapper
|
29
|
+
@allq_wrapper = nil
|
30
|
+
__setobj__(@allq_wrapper)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determines if the connection to allq is currently open
|
34
|
+
def connected?
|
35
|
+
begin
|
36
|
+
!!(@allq_wrapper && @allq_wrapper.connection && @allq_wrapper.connection.connection && !@allq_wrapper.connection.connection.closed?) # Would be nice if beaneater provided a connected? method
|
37
|
+
rescue
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Attempt to reconnect to allq. Note: the connection will not be watching
|
43
|
+
# or using the tubes it was before it was reconnected (as it's actually a
|
44
|
+
# completely new connection)
|
45
|
+
# @raise [Beaneater::NotConnected] If allq fails to connect
|
46
|
+
def reconnect!
|
47
|
+
close
|
48
|
+
connect!
|
49
|
+
@on_reconnect.call(self) if @on_reconnect.respond_to?(:call)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Yield to a block that will be retried several times if the connection to
|
53
|
+
# allq goes down and is able to be re-established.
|
54
|
+
#
|
55
|
+
# @param options Hash Options. Valid options are:
|
56
|
+
# :max_retries Integer The maximum number of times the block will be yielded to.
|
57
|
+
# Defaults to 4
|
58
|
+
# :on_retry Proc An optional proc that will be called for each retry. Will be
|
59
|
+
# called after the connection is re-established and :retry_delay
|
60
|
+
# has passed but before the block is yielded to again
|
61
|
+
# :retry_delay Float The amount to sleep before retrying. Defaults to 1.0
|
62
|
+
# @raise Beaneater::NotConnected If a connection is unable to be re-established
|
63
|
+
def retryable(options = {}, &block)
|
64
|
+
options = {:max_retries => 4, :on_retry => nil, :retry_delay => 1.0}.merge!(options)
|
65
|
+
retry_count = options[:max_retries]
|
66
|
+
|
67
|
+
begin
|
68
|
+
yield
|
69
|
+
|
70
|
+
rescue Beaneater::NotConnected
|
71
|
+
if retry_count > 0
|
72
|
+
reconnect!
|
73
|
+
retry_count -= 1
|
74
|
+
sleep options[:retry_delay]
|
75
|
+
options[:on_retry].call if options[:on_retry].respond_to?(:call)
|
76
|
+
retry
|
77
|
+
else # stop retrying
|
78
|
+
raise e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# Attempt to ensure we're connected to allq if the missing method is
|
86
|
+
# present in the delegate and we haven't shut down the connection on purpose
|
87
|
+
# @raise [Beaneater::NotConnected] If allq fails to connect after multiple attempts.
|
88
|
+
def method_missing(m, *args, &block)
|
89
|
+
ensure_connected! if respond_to_missing?(m, false)
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
# Connects to a allq queue
|
94
|
+
# @raise Beaneater::NotConnected if the connection cannot be established
|
95
|
+
def connect!
|
96
|
+
@allq_wrapper = Backburner::AllqWrapper.new(allq_addresses)
|
97
|
+
__setobj__(@allq_wrapper)
|
98
|
+
@allq_wrapper
|
99
|
+
end
|
100
|
+
|
101
|
+
def put(tube_name, data, opt)
|
102
|
+
pri = (opt[:pri] || 5).to_i
|
103
|
+
delay = opt[:delay].to_i
|
104
|
+
ttr = (opt[:ttr] || 600).to_i
|
105
|
+
@allq_wrapper.put2(data, pri, ttr, delay)
|
106
|
+
end
|
107
|
+
|
108
|
+
def get(tube_name)
|
109
|
+
@allq_wrapper.get(tube_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Attempts to ensure a connection to allq is established but only if
|
113
|
+
# we're not connected already
|
114
|
+
# @param max_retries Integer The maximum number of times to attempt connecting. Defaults to 4
|
115
|
+
# @param retry_delay Float The time to wait between retrying to connect. Defaults to 1.0
|
116
|
+
# @raise [Beaneater::NotConnected] If allq fails to connect after multiple attempts.
|
117
|
+
# @return Connection This Connection is returned if the connection to allq is open or was able to be reconnected
|
118
|
+
def ensure_connected!(max_retries = 4, retry_delay = 1.0)
|
119
|
+
return self if connected?
|
120
|
+
|
121
|
+
begin
|
122
|
+
reconnect!
|
123
|
+
return self
|
124
|
+
|
125
|
+
rescue Exception => e
|
126
|
+
if max_retries > 0
|
127
|
+
max_retries -= 1
|
128
|
+
sleep retry_delay
|
129
|
+
retry
|
130
|
+
else # stop retrying
|
131
|
+
raise e
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns the allq queue addresses
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# allq_addresses => ["127.0.0.1:11300"]
|
140
|
+
#
|
141
|
+
def allq_addresses
|
142
|
+
uri = self.url.is_a?(Array) ? self.url.first : self.url
|
143
|
+
allq_host_and_port(uri)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a host and port based on the uri_string given
|
147
|
+
#
|
148
|
+
# @example
|
149
|
+
# allq_host_and_port("allq://127.0.0.1") => "127.0.0.1:11300"
|
150
|
+
#
|
151
|
+
def allq_host_and_port(uri_string)
|
152
|
+
uri = URI.parse(uri_string)
|
153
|
+
raise(BadURL, uri_string) if uri.scheme != 'allq'.freeze
|
154
|
+
"#{uri.host}:#{uri.port || 11300}"
|
155
|
+
end
|
156
|
+
end # Connection
|
157
|
+
end # Backburner
|