backburner-allq 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +29 -0
  4. data/CHANGELOG.md +133 -0
  5. data/CONTRIBUTING.md +37 -0
  6. data/Gemfile +4 -0
  7. data/HOOKS.md +99 -0
  8. data/LICENSE +22 -0
  9. data/README.md +658 -0
  10. data/Rakefile +17 -0
  11. data/TODO +4 -0
  12. data/backburner-allq.gemspec +26 -0
  13. data/bin/backburner +7 -0
  14. data/circle.yml +3 -0
  15. data/deploy.sh +3 -0
  16. data/examples/custom.rb +25 -0
  17. data/examples/demo.rb +60 -0
  18. data/examples/god.rb +46 -0
  19. data/examples/hooked.rb +87 -0
  20. data/examples/retried.rb +31 -0
  21. data/examples/simple.rb +43 -0
  22. data/examples/stress.rb +31 -0
  23. data/lib/backburner.rb +75 -0
  24. data/lib/backburner/allq_wrapper.rb +317 -0
  25. data/lib/backburner/async_proxy.rb +25 -0
  26. data/lib/backburner/cli.rb +53 -0
  27. data/lib/backburner/configuration.rb +48 -0
  28. data/lib/backburner/connection.rb +157 -0
  29. data/lib/backburner/helpers.rb +193 -0
  30. data/lib/backburner/hooks.rb +53 -0
  31. data/lib/backburner/job.rb +118 -0
  32. data/lib/backburner/logger.rb +53 -0
  33. data/lib/backburner/performable.rb +95 -0
  34. data/lib/backburner/queue.rb +145 -0
  35. data/lib/backburner/tasks.rb +54 -0
  36. data/lib/backburner/version.rb +3 -0
  37. data/lib/backburner/worker.rb +221 -0
  38. data/lib/backburner/workers/forking.rb +52 -0
  39. data/lib/backburner/workers/simple.rb +29 -0
  40. data/lib/backburner/workers/threading.rb +163 -0
  41. data/lib/backburner/workers/threads_on_fork.rb +263 -0
  42. data/test/async_proxy_test.rb +36 -0
  43. data/test/back_burner_test.rb +88 -0
  44. data/test/connection_test.rb +179 -0
  45. data/test/fixtures/hooked.rb +122 -0
  46. data/test/fixtures/test_fork_jobs.rb +72 -0
  47. data/test/fixtures/test_forking_jobs.rb +56 -0
  48. data/test/fixtures/test_jobs.rb +87 -0
  49. data/test/fixtures/test_queue_settings.rb +14 -0
  50. data/test/helpers/templogger.rb +22 -0
  51. data/test/helpers_test.rb +278 -0
  52. data/test/hooks_test.rb +112 -0
  53. data/test/job_test.rb +185 -0
  54. data/test/logger_test.rb +44 -0
  55. data/test/performable_test.rb +88 -0
  56. data/test/queue_test.rb +69 -0
  57. data/test/test_helper.rb +128 -0
  58. data/test/worker_test.rb +157 -0
  59. data/test/workers/forking_worker_test.rb +181 -0
  60. data/test/workers/simple_worker_test.rb +350 -0
  61. data/test/workers/threading_worker_test.rb +104 -0
  62. data/test/workers/threads_on_fork_worker_test.rb +484 -0
  63. 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