queue_classic_pg2 3.2.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +19 -0
  4. data/CONTRIBUTING.md +17 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +326 -0
  8. data/Rakefile +14 -0
  9. data/changelog +146 -0
  10. data/lib/generators/queue_classic/install_generator.rb +36 -0
  11. data/lib/generators/queue_classic/templates/add_queue_classic.rb +9 -0
  12. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_0.rb +9 -0
  13. data/lib/generators/queue_classic/templates/update_queue_classic_3_0_2.rb +11 -0
  14. data/lib/generators/queue_classic/templates/update_queue_classic_3_1_0.rb +9 -0
  15. data/lib/queue_classic/config.rb +85 -0
  16. data/lib/queue_classic/conn_adapter.rb +111 -0
  17. data/lib/queue_classic/queue.rb +119 -0
  18. data/lib/queue_classic/railtie.rb +9 -0
  19. data/lib/queue_classic/setup.rb +58 -0
  20. data/lib/queue_classic/tasks.rb +49 -0
  21. data/lib/queue_classic/version.rb +3 -0
  22. data/lib/queue_classic/worker.rb +166 -0
  23. data/lib/queue_classic.rb +122 -0
  24. data/queue_classic.gemspec +24 -0
  25. data/sql/create_table.sql +25 -0
  26. data/sql/ddl.sql +78 -0
  27. data/sql/downgrade_from_3_0_0.sql +2 -0
  28. data/sql/downgrade_from_3_1_0.sql +1 -0
  29. data/sql/drop_ddl.sql +3 -0
  30. data/sql/update_to_3_0_0.sql +17 -0
  31. data/sql/update_to_3_1_0.sql +9 -0
  32. data/test/benchmark_test.rb +39 -0
  33. data/test/config_test.rb +121 -0
  34. data/test/helper.rb +61 -0
  35. data/test/helper.sql +25 -0
  36. data/test/lib/queue_classic_rails_connection_test.rb +43 -0
  37. data/test/lib/queue_classic_test.rb +42 -0
  38. data/test/queue_test.rb +208 -0
  39. data/test/worker_test.rb +219 -0
  40. metadata +112 -0
@@ -0,0 +1,85 @@
1
+ module QC
2
+ module Config
3
+ # You can use the APP_NAME to query for
4
+ # postgres related process information in the
5
+ # pg_stat_activity table.
6
+ def app_name
7
+ @app_name ||= ENV["QC_APP_NAME"] || "queue_classic"
8
+ end
9
+
10
+ # Number of seconds to block on the listen chanel for new jobs.
11
+ def wait_time
12
+ @wait_time ||= (ENV["QC_LISTEN_TIME"] || 5).to_i
13
+ end
14
+
15
+ # Why do you want to change the table name?
16
+ # Just deal with the default OK?
17
+ # If you do want to change this, you will
18
+ # need to update the PL/pgSQL lock_head() function.
19
+ # Come on. Don't do it.... Just stick with the default.
20
+ def table_name
21
+ @table_name ||= "queue_classic_jobs"
22
+ end
23
+
24
+ def queue
25
+ @queue = ENV["QUEUE"] || "default"
26
+ end
27
+
28
+ # The default queue used by `QC.enqueue`.
29
+ def default_queue
30
+ @default_queue ||= Queue.new(QC.queue)
31
+ end
32
+
33
+ def default_queue=(queue)
34
+ @default_queue = queue
35
+ end
36
+
37
+ # Each row in the table will have a column that
38
+ # notes the queue. You can point your workers
39
+ # at different queues.
40
+ def queues
41
+ @queues ||= (ENV["QUEUES"] && ENV["QUEUES"].split(",").map(&:strip)) || []
42
+ end
43
+
44
+ # Set this to 1 for strict FIFO.
45
+ # There is nothing special about 9....
46
+ def top_bound
47
+ @top_bound ||= (ENV["QC_TOP_BOUND"] || 9).to_i
48
+ end
49
+
50
+ # Set this variable if you wish for
51
+ # the worker to fork a UNIX process for
52
+ # each locked job. Remember to re-establish
53
+ # any database connections. See the worker
54
+ # for more details.
55
+ def fork_worker?
56
+ @fork_worker ||= (!ENV["QC_FORK_WORKER"].nil?)
57
+ end
58
+
59
+ # The worker class instantiated by QC's rake tasks.
60
+ def default_worker_class
61
+
62
+ @worker_class ||= (ENV["QC_DEFAULT_WORKER_CLASS"] && Kernel.const_get(ENV["QC_DEFAULT_WORKER_CLASS"]) ||
63
+ QC::Worker)
64
+
65
+ end
66
+
67
+ def default_worker_class=(worker_class)
68
+ @worker_class = worker_class
69
+ end
70
+
71
+ # reset memoized configuration
72
+ def reset_config
73
+ # TODO: we might want to think about storing these in a Hash.
74
+ @app_name = nil
75
+ @wait_time = nil
76
+ @table_name = nil
77
+ @queue = nil
78
+ @default_queue = nil
79
+ @queues = nil
80
+ @top_bound = nil
81
+ @fork_worker = nil
82
+ @worker_class = nil
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,111 @@
1
+ require 'uri'
2
+ require 'pg'
3
+
4
+ module QC
5
+ class ConnAdapter
6
+
7
+ attr_accessor :connection
8
+ def initialize(c=nil)
9
+ @connection = c.nil? ? establish_new : validate!(c)
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def execute(stmt, *params)
14
+ @mutex.synchronize do
15
+ QC.log(:at => "exec_sql", :sql => stmt.inspect)
16
+ begin
17
+ params = nil if params.empty?
18
+ r = @connection.exec(stmt, params)
19
+ result = []
20
+ r.each {|t| result << t}
21
+ result.length > 1 ? result : result.pop
22
+ rescue PG::Error => e
23
+ QC.log(:error => e.inspect)
24
+ @connection.reset
25
+ raise
26
+ end
27
+ end
28
+ end
29
+
30
+ def wait(time, *channels)
31
+ @mutex.synchronize do
32
+ listen_cmds = channels.map {|c| 'LISTEN "' + c.to_s + '"'}
33
+ @connection.exec(listen_cmds.join(';'))
34
+ wait_for_notify(time)
35
+ unlisten_cmds = channels.map {|c| 'UNLISTEN "' + c.to_s + '"'}
36
+ @connection.exec(unlisten_cmds.join(';'))
37
+ drain_notify
38
+ end
39
+ end
40
+
41
+ def disconnect
42
+ @mutex.synchronize do
43
+ begin
44
+ @connection.close
45
+ rescue => e
46
+ QC.log(:at => 'disconnect', :error => e.message)
47
+ end
48
+ end
49
+ end
50
+
51
+ def server_version
52
+ @server_version ||= begin
53
+ version = execute("SHOW server_version_num;")["server_version_num"]
54
+ version && version.to_i
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def wait_for_notify(t)
61
+ Array.new.tap do |msgs|
62
+ @connection.wait_for_notify(t) {|event, pid, msg| msgs << msg}
63
+ end
64
+ end
65
+
66
+ def drain_notify
67
+ until @connection.notifies.nil?
68
+ QC.log(:at => "drain_notifications")
69
+ end
70
+ end
71
+
72
+ def validate!(c)
73
+ return c if c.is_a?(PG::Connection)
74
+ err = "connection must be an instance of PG::Connection, but was #{c.class}"
75
+ raise(ArgumentError, err)
76
+ end
77
+
78
+ def establish_new
79
+ QC.log(:at => "establish_conn")
80
+ conn = PG.connect(*normalize_db_url(db_url))
81
+ if conn.status != PG::CONNECTION_OK
82
+ QC.log(:error => conn.error)
83
+ end
84
+ conn.exec("SET application_name = '#{QC.app_name}'")
85
+ conn
86
+ end
87
+
88
+ def normalize_db_url(url)
89
+ host = url.host
90
+ host = host.gsub(/%2F/i, '/') if host
91
+
92
+ [
93
+ host, # host or percent-encoded socket path
94
+ url.port || 5432,
95
+ nil, '', #opts, tty
96
+ url.path.gsub("/",""), # database name
97
+ url.user,
98
+ url.password
99
+ ]
100
+ end
101
+
102
+ def db_url
103
+ return @db_url if defined?(@db_url) && @db_url
104
+ url = ENV["QC_DATABASE_URL"] ||
105
+ ENV["DATABASE_URL"] ||
106
+ raise(ArgumentError, "missing QC_DATABASE_URL or DATABASE_URL")
107
+ @db_url = URI.parse(url)
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,119 @@
1
+ require_relative 'conn_adapter'
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module QC
6
+ # The queue class maps a queue abstraction onto a database table.
7
+ class Queue
8
+
9
+ attr_reader :name, :top_bound
10
+ def initialize(name, top_bound=nil)
11
+ @name = name
12
+ @top_bound = top_bound || QC.top_bound
13
+ end
14
+
15
+ def conn_adapter=(a)
16
+ @adapter = a
17
+ end
18
+
19
+ def conn_adapter
20
+ @adapter ||= QC.default_conn_adapter
21
+ end
22
+
23
+ # enqueue(m,a) inserts a row into the jobs table and trigger a notification.
24
+ # The job's queue is represented by a name column in the row.
25
+ # There is a trigger on the table which will send a NOTIFY event
26
+ # on a channel which corresponds to the name of the queue.
27
+ # The method argument is a string encoded ruby expression. The expression
28
+ # will be separated by a `.` character and then `eval`d.
29
+ # Examples of the method argument include: `puts`, `Kernel.puts`,
30
+ # `MyObject.new.puts`.
31
+ # The args argument will be encoded as JSON and stored as a JSON datatype
32
+ # in the row. (If the version of PG does not support JSON,
33
+ # then the args will be stored as text.
34
+ # The args are stored as a collection and then splatted inside the worker.
35
+ # Examples of args include: `'hello world'`, `['hello world']`,
36
+ # `'hello', 'world'`.
37
+ # This method returns a hash with the id of the enqueued job.
38
+ def enqueue(method, *args)
39
+ QC.log_yield(:measure => 'queue.enqueue') do
40
+ s = "INSERT INTO #{QC.table_name} (q_name, method, args) VALUES ($1, $2, $3) RETURNING id"
41
+ conn_adapter.execute(s, name, method, JSON.dump(args))
42
+ end
43
+ end
44
+
45
+ # enqueue_at(t,m,a) inserts a row into the jobs table representing a job
46
+ # to be executed not before the specified time.
47
+ # The time argument must be a Time object or a float timestamp. The method
48
+ # and args argument must be in the form described in the documentation for
49
+ # the #enqueue method.
50
+ # This method returns a hash with the id of the enqueued job.
51
+ def enqueue_at(timestamp, method, *args)
52
+ offset = Time.at(timestamp).to_i - Time.now.to_i
53
+ enqueue_in(offset, method, *args)
54
+ end
55
+
56
+ # enqueue_in(t,m,a) inserts a row into the jobs table representing a job
57
+ # to be executed not before the specified time offset.
58
+ # The seconds argument must be an integer. The method and args argument
59
+ # must be in the form described in the documentation for the #enqueue
60
+ # method.
61
+ # This method returns a hash with the id of the enqueued job.
62
+ def enqueue_in(seconds, method, *args)
63
+ QC.log_yield(:measure => 'queue.enqueue') do
64
+ s = "INSERT INTO #{QC.table_name} (q_name, method, args, scheduled_at)
65
+ VALUES ($1, $2, $3, now() + interval '#{seconds.to_i} seconds')
66
+ RETURNING id"
67
+ conn_adapter.execute(s, name, method, JSON.dump(args))
68
+ end
69
+ end
70
+
71
+ def lock
72
+ QC.log_yield(:measure => 'queue.lock') do
73
+ s = "SELECT * FROM lock_head($1, $2)"
74
+ if r = conn_adapter.execute(s, name, top_bound)
75
+ {}.tap do |job|
76
+ job[:id] = r["id"]
77
+ job[:q_name] = r["q_name"]
78
+ job[:method] = r["method"]
79
+ job[:args] = JSON.parse(r["args"])
80
+ if r["scheduled_at"]
81
+ job[:scheduled_at] = Time.parse(r["scheduled_at"])
82
+ ttl = Integer((Time.now - job[:scheduled_at]) * 1000)
83
+ QC.measure("time-to-lock=#{ttl}ms source=#{name}")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def unlock(id)
91
+ QC.log_yield(:measure => 'queue.unlock') do
92
+ s = "UPDATE #{QC.table_name} SET locked_at = NULL WHERE id = $1"
93
+ conn_adapter.execute(s, id)
94
+ end
95
+ end
96
+
97
+ def delete(id)
98
+ QC.log_yield(:measure => 'queue.delete') do
99
+ conn_adapter.execute("DELETE FROM #{QC.table_name} WHERE id = $1", id)
100
+ end
101
+ end
102
+
103
+ def delete_all
104
+ QC.log_yield(:measure => 'queue.delete_all') do
105
+ s = "DELETE FROM #{QC.table_name} WHERE q_name = $1"
106
+ conn_adapter.execute(s, name)
107
+ end
108
+ end
109
+
110
+ def count
111
+ QC.log_yield(:measure => 'queue.count') do
112
+ s = "SELECT COUNT(*) FROM #{QC.table_name} WHERE q_name = $1"
113
+ r = conn_adapter.execute(s, name)
114
+ r["count"].to_i
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails/railtie'
2
+
3
+ module QC
4
+ class Railtie < ::Rails::Railtie
5
+ rake_tasks do
6
+ load 'queue_classic/tasks.rb'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ module QC
2
+ module Setup
3
+ Root = File.expand_path("../..", File.dirname(__FILE__))
4
+ SqlFunctions = File.join(Root, "/sql/ddl.sql")
5
+ CreateTable = File.join(Root, "/sql/create_table.sql")
6
+ DropSqlFunctions = File.join(Root, "/sql/drop_ddl.sql")
7
+ UpgradeTo_3_0_0 = File.join(Root, "/sql/update_to_3_0_0.sql")
8
+ DowngradeFrom_3_0_0 = File.join(Root, "/sql/downgrade_from_3_0_0.sql")
9
+ UpgradeTo_3_1_0 = File.join(Root, "/sql/update_to_3_1_0.sql")
10
+ DowngradeFrom_3_1_0 = File.join(Root, "/sql/downgrade_from_3_1_0.sql")
11
+
12
+ def self.create(c = QC::default_conn_adapter.connection)
13
+ conn = QC::ConnAdapter.new(c)
14
+ conn.execute(File.read(CreateTable))
15
+ conn.execute(File.read(SqlFunctions))
16
+ conn.disconnect if c.nil? #Don't close a conn we didn't create.
17
+ end
18
+
19
+ def self.drop(c = QC::default_conn_adapter.connection)
20
+ conn = QC::ConnAdapter.new(c)
21
+ conn.execute("DROP TABLE IF EXISTS queue_classic_jobs CASCADE")
22
+ conn.execute(File.read(DropSqlFunctions))
23
+ conn.disconnect if c.nil? #Don't close a conn we didn't create.
24
+ end
25
+
26
+ def self.update(c = QC::default_conn_adapter.connection)
27
+ conn = QC::ConnAdapter.new(c)
28
+ conn.execute(File.read(UpgradeTo_3_0_0))
29
+ conn.execute(File.read(UpgradeTo_3_1_0))
30
+ conn.execute(File.read(DropSqlFunctions))
31
+ conn.execute(File.read(SqlFunctions))
32
+ end
33
+
34
+ def self.update_to_3_0_0(c = QC::default_conn_adapter.connection)
35
+ conn = QC::ConnAdapter.new(c)
36
+ conn.execute(File.read(UpgradeTo_3_0_0))
37
+ conn.execute(File.read(DropSqlFunctions))
38
+ conn.execute(File.read(SqlFunctions))
39
+ end
40
+
41
+ def self.downgrade_from_3_0_0(c = QC::default_conn_adapter.connection)
42
+ conn = QC::ConnAdapter.new(c)
43
+ conn.execute(File.read(DowngradeFrom_3_0_0))
44
+ end
45
+
46
+ def self.update_to_3_1_0(c = QC::default_conn_adapter.connection)
47
+ conn = QC::ConnAdapter.new(c)
48
+ conn.execute(File.read(UpgradeTo_3_1_0))
49
+ conn.execute(File.read(DropSqlFunctions))
50
+ conn.execute(File.read(SqlFunctions))
51
+ end
52
+
53
+ def self.downgrade_from_3_1_0(c = QC::default_conn_adapter.connection)
54
+ conn = QC::ConnAdapter.new(c)
55
+ conn.execute(File.read(DowngradeFrom_3_1_0))
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,49 @@
1
+ task :environment
2
+
3
+ namespace :jobs do
4
+ desc "Alias for qc:work"
5
+ task :work => "qc:work"
6
+ end
7
+
8
+ namespace :qc do
9
+ desc "Start a new worker for the (default or $QUEUE / $QUEUES) queue"
10
+ task :work => :environment do
11
+ @worker = QC.default_worker_class.new
12
+
13
+ trap('INT') do
14
+ $stderr.puts("Received INT. Shutting down.")
15
+ if !@worker.running
16
+ $stderr.puts("Worker has stopped running. Exit.")
17
+ exit(1)
18
+ end
19
+ @worker.stop
20
+ end
21
+
22
+ trap('TERM') do
23
+ $stderr.puts("Received Term. Shutting down.")
24
+ @worker.stop
25
+ end
26
+
27
+ @worker.start
28
+ end
29
+
30
+ desc "Returns the number of jobs in the (default or $QUEUE / $QUEUES) queue"
31
+ task :count => :environment do
32
+ puts QC.default_queue.count
33
+ end
34
+
35
+ desc "Setup queue_classic tables and functions in database"
36
+ task :create => :environment do
37
+ QC::Setup.create
38
+ end
39
+
40
+ desc "Remove queue_classic tables and functions from database."
41
+ task :drop => :environment do
42
+ QC::Setup.drop
43
+ end
44
+
45
+ desc "Update queue_classic tables and functions in database"
46
+ task :update => :environment do
47
+ QC::Setup.update
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module QC
2
+ VERSION = "3.2.0.RC1"
3
+ end
@@ -0,0 +1,166 @@
1
+ # -*- coding: utf-8 -*-
2
+ require_relative 'queue'
3
+ require_relative 'conn_adapter'
4
+
5
+ module QC
6
+ # A Worker object can process jobs from one or many queues.
7
+ class Worker
8
+
9
+ attr_accessor :queues, :running
10
+
11
+ # Creates a new worker but does not start the worker. See Worker#start.
12
+ # This method takes a single hash argument. The following keys are read:
13
+ # fork_worker:: Worker forks each job execution.
14
+ # wait_interval:: Time to wait between failed lock attempts
15
+ # connection:: PG::Connection object.
16
+ # q_name:: Name of a single queue to process.
17
+ # q_names:: Names of queues to process. Will process left to right.
18
+ # top_bound:: Offset to the head of the queue. 1 == strict FIFO.
19
+ def initialize(args={})
20
+ @fork_worker = args[:fork_worker] || QC.fork_worker?
21
+ @wait_interval = args[:wait_interval] || QC.wait_time
22
+
23
+ if args[:connection]
24
+ @conn_adapter = ConnAdapter.new(args[:connection])
25
+ else
26
+ @conn_adapter = QC.default_conn_adapter
27
+ end
28
+
29
+ @queues = setup_queues(@conn_adapter,
30
+ (args[:q_name] || QC.queue),
31
+ (args[:q_names] || QC.queues),
32
+ (args[:top_bound] || QC.top_bound))
33
+ log(args.merge(:at => "worker_initialized"))
34
+ @running = true
35
+ end
36
+
37
+ # Commences the working of jobs.
38
+ # start() spins on @running –which is initialized as true.
39
+ # This method is the primary entry point to starting the worker.
40
+ # The canonical example of starting a worker is as follows:
41
+ # QC::Worker.new.start
42
+ def start
43
+ QC.unlock_jobs_of_dead_workers
44
+
45
+ while @running
46
+ @fork_worker ? fork_and_work : work
47
+ end
48
+ end
49
+
50
+ # Signals the worker to stop taking new work.
51
+ # This method has no immediate effect. However, there are
52
+ # two loops in the worker (one in #start and another in #lock_job)
53
+ # which check the @running variable to determine if further progress
54
+ # is desirable. In the case that @running is false, the aforementioned
55
+ # methods will short circuit and cause the blocking call to #start
56
+ # to unblock.
57
+ def stop
58
+ @running = false
59
+ end
60
+
61
+ # Calls Worker#work but after the current process is forked.
62
+ # The parent process will wait on the child process to exit.
63
+ def fork_and_work
64
+ cpid = fork {setup_child; work}
65
+ log(:at => :fork, :pid => cpid)
66
+ Process.wait(cpid)
67
+ end
68
+
69
+ # Blocks on locking a job, and once a job is locked,
70
+ # it will process the job.
71
+ def work
72
+ queue, job = lock_job
73
+ if queue && job
74
+ QC.log_yield(:at => "work", :job => job[:id]) do
75
+ process(queue, job)
76
+ end
77
+ end
78
+ end
79
+
80
+ # Attempt to lock a job in the queue's table.
81
+ # If a job can be locked, this method returns an array with
82
+ # 2 elements. The first element is the queue from which the job was locked
83
+ # and the second is a hash representation of the job.
84
+ # If a job is returned, its locked_at column has been set in the
85
+ # job's row. It is the caller's responsibility to delete the job row
86
+ # from the table when the job is complete.
87
+ def lock_job
88
+ log(:at => "lock_job")
89
+ job = nil
90
+ while @running
91
+ @queues.each do |queue|
92
+ if job = queue.lock
93
+ return [queue, job]
94
+ end
95
+ end
96
+ @conn_adapter.wait(@wait_interval, *@queues.map {|q| q.name})
97
+ end
98
+ end
99
+
100
+ # A job is processed by evaluating the target code.
101
+ # if the job is evaluated with no exceptions
102
+ # then it is deleted from the queue.
103
+ # If the job has raised an exception the responsibility of what
104
+ # to do with the job is delegated to Worker#handle_failure.
105
+ # If the job is not finished and an INT signal is trapped,
106
+ # this method will unlock the job in the queue.
107
+ def process(queue, job)
108
+ start = Time.now
109
+ finished = false
110
+ begin
111
+ call(job).tap do
112
+ queue.delete(job[:id])
113
+ finished = true
114
+ end
115
+ rescue => e
116
+ handle_failure(job, e)
117
+ finished = true
118
+ ensure
119
+ if !finished
120
+ queue.unlock(job[:id])
121
+ end
122
+ ttp = Integer((Time.now - start) * 1000)
123
+ QC.measure("time-to-process=#{ttp} source=#{queue.name}")
124
+ end
125
+ end
126
+
127
+ # Each job includes a method column. We will use ruby's eval
128
+ # to grab the ruby object from memory. We send the method to
129
+ # the object and pass the args.
130
+ def call(job)
131
+ args = job[:args]
132
+ receiver_str, _, message = job[:method].rpartition('.')
133
+ receiver = eval(receiver_str)
134
+ receiver.send(message, *args)
135
+ end
136
+
137
+ # This method will be called when an exception
138
+ # is raised during the execution of the job.
139
+ def handle_failure(job,e)
140
+ $stderr.puts("count#qc.job-error=1 job=#{job} error=#{e.inspect} at=#{e.backtrace.first}")
141
+ end
142
+
143
+ # This method should be overriden if
144
+ # your worker is forking and you need to
145
+ # re-establish database connections
146
+ def setup_child
147
+ log(:at => "setup_child")
148
+ end
149
+
150
+ def log(data)
151
+ QC.log(data)
152
+ end
153
+
154
+ private
155
+
156
+ def setup_queues(adapter, queue, queues, top_bound)
157
+ names = queues.length > 0 ? queues : [queue]
158
+ names.map do |name|
159
+ QC::Queue.new(name, top_bound).tap do |q|
160
+ q.conn_adapter = adapter
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+ end