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.
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