queue_classic 2.3.0beta → 3.0.0beta
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 +4 -4
- data/lib/generators/queue_classic/install_generator.rb +2 -4
- data/lib/queue_classic/conn_adapter.rb +106 -0
- data/lib/queue_classic/queue.rb +17 -24
- data/lib/queue_classic/setup.rb +6 -6
- data/lib/queue_classic/tasks.rb +23 -34
- data/lib/queue_classic/worker.rb +34 -36
- data/lib/queue_classic.rb +40 -2
- data/readme.md +17 -15
- data/test/benchmark_test.rb +16 -12
- data/test/helper.rb +12 -3
- data/test/queue_test.rb +23 -22
- data/test/worker_test.rb +61 -32
- metadata +6 -9
- data/lib/queue_classic/conf.rb +0 -33
- data/lib/queue_classic/conn.rb +0 -106
- data/test/conf_test.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ae7f3fa42909c77c861db2ef125fab2ba01205b
|
4
|
+
data.tar.gz: a41224c39e1966276a5a66403b705426c5d3ff63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0d7b6b9f80e240ce3ac76c3fd488870ca648439c66917eceec454c7595dbbac7193d17a91ea099b763f25e39f76038761521d799ae3eaf4b5f09ed5c64b995
|
7
|
+
data.tar.gz: 92ff9563d1b98f03b096704524059a8d3f72d4ccd2626517af62b5067784641ff4eccb4f8864a9830fdc95ec06a3d8b0e5d6cc04c47656a247c2c0b40ad1d6df
|
@@ -8,8 +8,7 @@ module QC
|
|
8
8
|
|
9
9
|
namespace "queue_classic:install"
|
10
10
|
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
11
|
-
desc 'Generates (but does not run) a migration to add '
|
12
|
-
'a queue_classic table.'
|
11
|
+
desc 'Generates (but does not run) a migration to add a queue_classic table.'
|
13
12
|
|
14
13
|
def self.next_migration_number(dirname)
|
15
14
|
next_migration_number = current_migration_number(dirname) + 1
|
@@ -17,8 +16,7 @@ module QC
|
|
17
16
|
end
|
18
17
|
|
19
18
|
def create_migration_file
|
20
|
-
migration_template 'add_queue_classic.rb',
|
21
|
-
'db/migrate/add_queue_classic.rb'
|
19
|
+
migration_template 'add_queue_classic.rb', 'db/migrate/add_queue_classic.rb'
|
22
20
|
end
|
23
21
|
end
|
24
22
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'uri'
|
3
|
+
require 'pg'
|
4
|
+
|
5
|
+
module QC
|
6
|
+
class ConnAdapter
|
7
|
+
|
8
|
+
attr_accessor :connection
|
9
|
+
def initialize(c=nil)
|
10
|
+
@connection = c.nil? ? establish_new : validate!(c)
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(stmt, *params)
|
15
|
+
@mutex.synchronize do
|
16
|
+
QC.log(:at => "exec_sql", :sql => stmt.inspect)
|
17
|
+
begin
|
18
|
+
params = nil if params.empty?
|
19
|
+
r = @connection.exec(stmt, params)
|
20
|
+
result = []
|
21
|
+
r.each {|t| result << t}
|
22
|
+
result.length > 1 ? result : result.pop
|
23
|
+
rescue PGError => e
|
24
|
+
QC.log(:error => e.inspect)
|
25
|
+
@connection.reset
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait(time, *channels)
|
32
|
+
@mutex.synchronize do
|
33
|
+
listen_cmds = channels.map {|c| 'LISTEN "' + c + '"'}
|
34
|
+
@connection.exec(listen_cmds.join(';'))
|
35
|
+
wait_for_notify(time)
|
36
|
+
unlisten_cmds = channels.map {|c| 'UNLISTEN "' + c +'"'}
|
37
|
+
@connection.exec(unlisten_cmds.join(';'))
|
38
|
+
drain_notify
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def disconnect
|
43
|
+
@mutex.synchronize do
|
44
|
+
begin
|
45
|
+
@connection.close
|
46
|
+
rescue => e
|
47
|
+
QC.log(:at => 'disconnect', :error => e.message)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def wait_for_notify(t)
|
55
|
+
Array.new.tap do |msgs|
|
56
|
+
@connection.wait_for_notify(t) {|event, pid, msg| msgs << msg}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def drain_notify
|
61
|
+
until @connection.notifies.nil?
|
62
|
+
QC.log(:at => "drain_notifications")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate!(c)
|
67
|
+
return c if c.is_a?(PG::Connection)
|
68
|
+
klass = c.class
|
69
|
+
err = "connection must be an instance of PG::Connection, but was #{c}"
|
70
|
+
raise(ArgumentError, err)
|
71
|
+
end
|
72
|
+
|
73
|
+
def establish_new
|
74
|
+
QC.log(:at => "establish_conn")
|
75
|
+
conn = PGconn.connect(*normalize_db_url(db_url))
|
76
|
+
if conn.status != PGconn::CONNECTION_OK
|
77
|
+
QC.log(:error => conn.error)
|
78
|
+
end
|
79
|
+
conn.exec("SET application_name = '#{QC::APP_NAME}'")
|
80
|
+
conn
|
81
|
+
end
|
82
|
+
|
83
|
+
def normalize_db_url(url)
|
84
|
+
host = url.host
|
85
|
+
host = host.gsub(/%2F/i, '/') if host
|
86
|
+
|
87
|
+
[
|
88
|
+
host, # host or percent-encoded socket path
|
89
|
+
url.port || 5432,
|
90
|
+
nil, '', #opts, tty
|
91
|
+
url.path.gsub("/",""), # database name
|
92
|
+
url.user,
|
93
|
+
url.password
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
def db_url
|
98
|
+
return @db_url if @db_url
|
99
|
+
url = ENV["QC_DATABASE_URL"] ||
|
100
|
+
ENV["DATABASE_URL"] ||
|
101
|
+
raise(ArgumentError, "missing QC_DATABASE_URL or DATABASE_URL")
|
102
|
+
@db_url = URI.parse(url)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
data/lib/queue_classic/queue.rb
CHANGED
@@ -1,35 +1,35 @@
|
|
1
1
|
require 'queue_classic'
|
2
|
-
require 'queue_classic/
|
2
|
+
require 'queue_classic/conn_adapter'
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
module QC
|
6
6
|
class Queue
|
7
|
-
TABLE_NAME = "queue_classic_jobs"
|
8
|
-
# Each row in the table will have a column that
|
9
|
-
# notes the queue.
|
10
|
-
QUEUE_NAME = ENV["QUEUE"] || "default"
|
11
|
-
# Set this to 1 for strict FIFO.
|
12
|
-
TOP_BOUND = (ENV["QC_TOP_BOUND"] || 9).to_i
|
13
7
|
|
8
|
+
attr_reader :name, :top_bound
|
9
|
+
def initialize(name, top_bound=nil)
|
10
|
+
@name = name
|
11
|
+
@top_bound = top_bound || QC::TOP_BOUND
|
12
|
+
end
|
13
|
+
|
14
|
+
def conn_adapter=(a)
|
15
|
+
@adapter = a
|
16
|
+
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
@conn = opts[:conn] || Conn.new
|
18
|
-
@name = opts[:name] || QUEUE_NAME
|
19
|
-
@top_bound = opts[:top_bound] || TOP_BOUND
|
18
|
+
def conn_adapter
|
19
|
+
@adapter ||= QC.default_conn_adapter
|
20
20
|
end
|
21
21
|
|
22
22
|
def enqueue(method, *args)
|
23
23
|
QC.log_yield(:measure => 'queue.enqueue') do
|
24
24
|
s="INSERT INTO #{TABLE_NAME} (q_name, method, args) VALUES ($1, $2, $3)"
|
25
|
-
res =
|
25
|
+
res = conn_adapter.execute(s, name, method, JSON.dump(args))
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def lock
|
30
30
|
QC.log_yield(:measure => 'queue.lock') do
|
31
31
|
s = "SELECT * FROM lock_head($1, $2)"
|
32
|
-
if r =
|
32
|
+
if r = conn_adapter.execute(s, name, top_bound)
|
33
33
|
{:id => r["id"],
|
34
34
|
:method => r["method"],
|
35
35
|
:args => JSON.parse(r["args"])}
|
@@ -37,30 +37,23 @@ module QC
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
def wait
|
41
|
-
QC.log_yield(:measure => 'queue.wait') do
|
42
|
-
conn.wait(name)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
40
|
def delete(id)
|
47
41
|
QC.log_yield(:measure => 'queue.delete') do
|
48
|
-
|
49
|
-
conn.execute(s, id)
|
42
|
+
conn_adapter.execute("DELETE FROM #{TABLE_NAME} where id = $1", id)
|
50
43
|
end
|
51
44
|
end
|
52
45
|
|
53
46
|
def delete_all
|
54
47
|
QC.log_yield(:measure => 'queue.delete_all') do
|
55
48
|
s = "DELETE FROM #{TABLE_NAME} WHERE q_name = $1"
|
56
|
-
|
49
|
+
conn_adapter.execute(s, name)
|
57
50
|
end
|
58
51
|
end
|
59
52
|
|
60
53
|
def count
|
61
54
|
QC.log_yield(:measure => 'queue.count') do
|
62
55
|
s = "SELECT COUNT(*) FROM #{TABLE_NAME} WHERE q_name = $1"
|
63
|
-
r =
|
56
|
+
r = conn_adapter.execute(s, name)
|
64
57
|
r["count"].to_i
|
65
58
|
end
|
66
59
|
end
|
data/lib/queue_classic/setup.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'queue_classic/conn'
|
2
|
-
|
3
1
|
module QC
|
4
2
|
module Setup
|
5
3
|
Root = File.expand_path("../..", File.dirname(__FILE__))
|
@@ -7,16 +5,18 @@ module QC
|
|
7
5
|
CreateTable = File.join(Root, "/sql/create_table.sql")
|
8
6
|
DropSqlFunctions = File.join(Root, "/sql/drop_ddl.sql")
|
9
7
|
|
10
|
-
def self.create(
|
11
|
-
conn
|
8
|
+
def self.create(c=nil)
|
9
|
+
conn = QC::ConnAdapter.new(c)
|
12
10
|
conn.execute(File.read(CreateTable))
|
13
11
|
conn.execute(File.read(SqlFunctions))
|
12
|
+
conn.disconnect if c.nil? #Don't close a conn we didn't create.
|
14
13
|
end
|
15
14
|
|
16
|
-
def self.drop(
|
17
|
-
conn
|
15
|
+
def self.drop(c=nil)
|
16
|
+
conn = QC::ConnAdapter.new(c)
|
18
17
|
conn.execute("DROP TABLE IF EXISTS queue_classic_jobs CASCADE")
|
19
18
|
conn.execute(File.read(DropSqlFunctions))
|
19
|
+
conn.disconnect if c.nil? #Don't close a conn we didn't create.
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/queue_classic/tasks.rb
CHANGED
@@ -1,42 +1,31 @@
|
|
1
|
-
|
2
|
-
ActiveSupport::Deprecation.warn <<-MSG
|
3
|
-
queue_classic Rake tasks are now loaded automatically for Rails applications.
|
4
|
-
Loading the tasks yourself is deprecated. Please update your Rakefile and remove:
|
1
|
+
task :environment
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
else
|
11
|
-
task :environment
|
3
|
+
namespace :jobs do
|
4
|
+
desc "Alias for qc:work"
|
5
|
+
task :work => "qc:work"
|
6
|
+
end
|
12
7
|
|
13
|
-
|
14
|
-
|
15
|
-
|
8
|
+
namespace :qc do
|
9
|
+
desc "Start a new worker for the (default or $QUEUE) queue"
|
10
|
+
task :work => :environment do
|
11
|
+
trap('INT') {exit}
|
12
|
+
trap('TERM') {@worker.stop}
|
13
|
+
@worker = QC::Worker.new
|
14
|
+
@worker.start
|
16
15
|
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
trap('TERM') {@worker.stop}
|
23
|
-
@worker = QC::Worker.new
|
24
|
-
@worker.start
|
25
|
-
end
|
26
|
-
|
27
|
-
desc "Returns the number of jobs in the (default or QUEUE) queue"
|
28
|
-
task :count => :environment do
|
29
|
-
puts QC::Worker.new.queue.count
|
30
|
-
end
|
17
|
+
desc "Returns the number of jobs in the (default or QUEUE) queue"
|
18
|
+
task :count => :environment do
|
19
|
+
puts QC::Worker.new.queue.count
|
20
|
+
end
|
31
21
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
22
|
+
desc "Setup queue_classic tables and functions in database"
|
23
|
+
task :create => :environment do
|
24
|
+
QC::Setup.create
|
25
|
+
end
|
36
26
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
27
|
+
desc "Remove queue_classic tables and functions from database."
|
28
|
+
task :drop => :environment do
|
29
|
+
QC::Setup.drop
|
41
30
|
end
|
42
31
|
end
|
data/lib/queue_classic/worker.rb
CHANGED
@@ -1,28 +1,19 @@
|
|
1
|
-
require 'thread'
|
2
1
|
require 'queue_classic'
|
3
2
|
require 'queue_classic/queue'
|
3
|
+
require 'queue_classic/conn_adapter'
|
4
4
|
|
5
5
|
module QC
|
6
6
|
class Worker
|
7
|
-
# Set this variable if you wish for
|
8
|
-
# the worker to fork a UNIX process for
|
9
|
-
# each locked job. Remember to re-establish
|
10
|
-
# any database connections. See the worker
|
11
|
-
# for more details.
|
12
|
-
FORK_WORKER = !ENV["QC_FORK_WORKER"].nil?
|
13
|
-
# The worker is capable of processing many jobs at a time.
|
14
|
-
# It uses FORK(2) to accomplish parallel processing. CONCURRENCY
|
15
|
-
# is used to set an uppoer bound on how many worker processes can
|
16
|
-
# run concurrently.
|
17
|
-
CONCURRENCY = Integer(ENV["QC_CONCURRENCY"] || 1)
|
18
7
|
|
19
|
-
attr_accessor :
|
8
|
+
attr_accessor :queues, :running
|
20
9
|
# In the case no arguments are passed to the initializer,
|
21
10
|
# the defaults are pulled from the environment variables.
|
22
11
|
def initialize(args={})
|
23
|
-
@fork_worker = args[:fork_worker] || FORK_WORKER
|
24
|
-
@
|
25
|
-
@
|
12
|
+
@fork_worker = args[:fork_worker] || QC::FORK_WORKER
|
13
|
+
@wait_interval = args[:wait_interval] || QC::WAIT_TIME
|
14
|
+
@conn_adapter = ConnAdapter.new(args[:connection])
|
15
|
+
@queues = setup_queues(@conn_adapter,
|
16
|
+
args[:q_name], args[:q_names], args[:top_bound])
|
26
17
|
log(args.merge(:at => "worker_initialized"))
|
27
18
|
@running = true
|
28
19
|
end
|
@@ -47,26 +38,17 @@ module QC
|
|
47
38
|
# Define setup_child to hook into the forking process.
|
48
39
|
# Using setup_child is good for re-establishing database connections.
|
49
40
|
def fork_and_work
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
begin
|
54
|
-
cpid = fork {setup_child; work}
|
55
|
-
log(:at => :fork, :pid => cpid)
|
56
|
-
Process.wait(cpid)
|
57
|
-
ensure
|
58
|
-
# Once we are done with our work and our process has exited,
|
59
|
-
# we can allow another process to run.
|
60
|
-
@limiter.deq
|
61
|
-
end
|
62
|
-
end
|
41
|
+
cpid = fork {setup_child; work}
|
42
|
+
log(:at => :fork, :pid => cpid)
|
43
|
+
Process.wait(cpid)
|
63
44
|
end
|
64
45
|
|
65
46
|
# This method will lock a job & process the job.
|
66
47
|
def work
|
67
|
-
|
48
|
+
queue, job = lock_job
|
49
|
+
if queue && job
|
68
50
|
QC.log_yield(:at => "work", :job => job[:id]) do
|
69
|
-
process(job)
|
51
|
+
process(queue, job)
|
70
52
|
end
|
71
53
|
end
|
72
54
|
end
|
@@ -78,23 +60,26 @@ module QC
|
|
78
60
|
log(:at => "lock_job")
|
79
61
|
job = nil
|
80
62
|
while @running
|
81
|
-
|
82
|
-
|
63
|
+
@queues.each do |queue|
|
64
|
+
if job = queue.lock
|
65
|
+
return [queue, job]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
@conn_adapter.wait(@wait_interval, *@queues.map {|q| q.name})
|
83
69
|
end
|
84
|
-
job
|
85
70
|
end
|
86
71
|
|
87
72
|
# A job is processed by evaluating the target code.
|
88
73
|
# Errors are delegated to the handle_failure method.
|
89
74
|
# Also, this method will make the best attempt to delete the job
|
90
75
|
# from the queue before returning.
|
91
|
-
def process(job)
|
76
|
+
def process(queue, job)
|
92
77
|
begin
|
93
78
|
call(job)
|
94
79
|
rescue => e
|
95
80
|
handle_failure(job, e)
|
96
81
|
ensure
|
97
|
-
|
82
|
+
queue.delete(job[:id])
|
98
83
|
log(:at => "delete_job", :job => job[:id])
|
99
84
|
end
|
100
85
|
end
|
@@ -126,5 +111,18 @@ module QC
|
|
126
111
|
QC.log(data)
|
127
112
|
end
|
128
113
|
|
114
|
+
private
|
115
|
+
|
116
|
+
def setup_queues(adapter, q_name, q_names, top_bound)
|
117
|
+
name = q_name || QC::QUEUE
|
118
|
+
names = q_names || QC::QUEUES
|
119
|
+
names << name unless names.include?(name)
|
120
|
+
names.map do |name|
|
121
|
+
QC::Queue.new(name, top_bound).tap do |q|
|
122
|
+
q.conn_adapter = adapter
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
129
127
|
end
|
130
128
|
end
|
data/lib/queue_classic.rb
CHANGED
@@ -1,4 +1,36 @@
|
|
1
1
|
module QC
|
2
|
+
# You can use the APP_NAME to query for
|
3
|
+
# postgres related process information in the
|
4
|
+
# pg_stat_activity table.
|
5
|
+
APP_NAME = ENV["QC_APP_NAME"] || "queue_classic"
|
6
|
+
|
7
|
+
# Number of seconds to block on the listen chanel for new jobs.
|
8
|
+
WAIT_TIME = (ENV["QC_LISTEN_TIME"] || 5).to_i
|
9
|
+
|
10
|
+
# Why do you want to change the table name?
|
11
|
+
# Just deal with the default OK?
|
12
|
+
# If you do want to change this, you will
|
13
|
+
# need to update the PL/pgSQL lock_head() function.
|
14
|
+
# Come on. Don't do it.... Just stick with the default.
|
15
|
+
TABLE_NAME = "queue_classic_jobs"
|
16
|
+
|
17
|
+
# Each row in the table will have a column that
|
18
|
+
# notes the queue. You can point your workers
|
19
|
+
# at different queues but only one at a time.
|
20
|
+
QUEUE = ENV["QUEUE"] || "default"
|
21
|
+
QUEUES = ENV["QUEUES"] || []
|
22
|
+
|
23
|
+
# Set this to 1 for strict FIFO.
|
24
|
+
# There is nothing special about 9....
|
25
|
+
TOP_BOUND = (ENV["QC_TOP_BOUND"] || 9).to_i
|
26
|
+
|
27
|
+
# Set this variable if you wish for
|
28
|
+
# the worker to fork a UNIX process for
|
29
|
+
# each locked job. Remember to re-establish
|
30
|
+
# any database connections. See the worker
|
31
|
+
# for more details.
|
32
|
+
FORK_WORKER = !ENV["QC_FORK_WORKER"].nil?
|
33
|
+
|
2
34
|
# Defer method calls on the QC module to the
|
3
35
|
# default queue. This facilitates QC.enqueue()
|
4
36
|
def self.method_missing(sym, *args, &block)
|
@@ -15,7 +47,13 @@ module QC
|
|
15
47
|
end
|
16
48
|
|
17
49
|
def self.default_queue
|
18
|
-
@default_queue ||=
|
50
|
+
@default_queue ||= begin
|
51
|
+
Queue.new(QUEUE)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.default_conn_adapter
|
56
|
+
@conn_adapter ||= ConnAdapter.new
|
19
57
|
end
|
20
58
|
|
21
59
|
def self.log_yield(data)
|
@@ -37,7 +75,7 @@ module QC
|
|
37
75
|
if block_given?
|
38
76
|
start = Time.now
|
39
77
|
result = yield
|
40
|
-
data.merge(:elapsed => Integer((Time.now -
|
78
|
+
data.merge(:elapsed => Integer((Time.now - t0)*1000))
|
41
79
|
end
|
42
80
|
data.reduce(out=String.new) do |s, tup|
|
43
81
|
s << [tup.first, tup.last].join("=") << " "
|
data/readme.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# queue_classic
|
2
2
|
|
3
|
-
v2.2.
|
3
|
+
Stable: [v2.2.3](https://github.com/ryandotsmith/queue_classic/tree/v2.2.3)
|
4
|
+
Latest: v3.0.0beta
|
4
5
|
|
5
6
|
queue_classic provides a simple interface to a PostgreSQL-backed message queue. queue_classic specializes in concurrent locking and minimizing database load while providing a simple, intuitive developer experience. queue_classic assumes that you are already using PostgreSQL in your production environment and that adding another dependency (e.g. redis, beanstalkd, 0mq) is undesirable.
|
6
7
|
|
@@ -9,12 +10,13 @@ Features:
|
|
9
10
|
* Leverage of PostgreSQL's listen/notify & row locking.
|
10
11
|
* Support for multiple queues with heterogeneous workers.
|
11
12
|
* JSON data format.
|
12
|
-
*
|
13
|
-
*
|
13
|
+
* Forking workers.
|
14
|
+
* Workers can work multiple queues.
|
15
|
+
* [Fuzzy-FIFO support](http://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/Lock_Free.pdf).
|
14
16
|
|
15
17
|
Contents:
|
16
18
|
|
17
|
-
* [Documentation](http://rubydoc.info/gems/queue_classic/2.2.
|
19
|
+
* [Documentation](http://rubydoc.info/gems/queue_classic/2.2.3/frames)
|
18
20
|
* [Usage](#usage)
|
19
21
|
* [Setup](#setup)
|
20
22
|
* [Configuration](#configuration)
|
@@ -56,17 +58,11 @@ p_queue.enqueue("Kernel.puts", ["hello", "world"])
|
|
56
58
|
|
57
59
|
### Working Jobs
|
58
60
|
|
59
|
-
There are two ways to work jobs. The first approach is to use the Rake task. The second approach is to use a custom executable.
|
60
|
-
|
61
|
-
* `$CONCURRENCY=1` - The number of child processes to run concurrently.
|
62
|
-
* `$FORK_WORKER=false` - Fork on each job execution. Enabled if `$CONCURRENCY` > 1
|
63
|
-
* `$QUEUE=default` - The name of the queue to process.
|
64
|
-
* `$TOP_BOUND=9` - The section of the queue that is elgible for dequeue operations. Setting this value to 1 will ensure a strict FIFO ordering.
|
61
|
+
There are two ways to work jobs. The first approach is to use the Rake task. The second approach is to use a custom executable.
|
65
62
|
|
66
63
|
#### Rake Task
|
67
64
|
|
68
|
-
Require queue_classic in your Rakefile.
|
69
|
-
be loaded automatically.
|
65
|
+
Require queue_classic in your Rakefile.
|
70
66
|
|
71
67
|
```ruby
|
72
68
|
require 'queue_classic'
|
@@ -79,10 +75,16 @@ Start the worker via the Rakefile.
|
|
79
75
|
$ bundle exec rake qc:work
|
80
76
|
```
|
81
77
|
|
82
|
-
Setup a worker to work a non-default queue
|
78
|
+
Setup a worker to work a non-default queue.
|
79
|
+
|
80
|
+
```bash
|
81
|
+
$ QUEUE="priority_queue" bundle exec rake qc:work
|
82
|
+
```
|
83
|
+
|
84
|
+
Setup a worker to work multiple queues.
|
83
85
|
|
84
86
|
```bash
|
85
|
-
$
|
87
|
+
$ QUEUE="priority_queue, secondary_queue" bundle exec rake qc:work
|
86
88
|
```
|
87
89
|
|
88
90
|
#### Custom Worker
|
@@ -135,7 +137,7 @@ Declare dependencies in Gemfile.
|
|
135
137
|
|
136
138
|
```ruby
|
137
139
|
source "http://rubygems.org"
|
138
|
-
gem "queue_classic", "2.2.
|
140
|
+
gem "queue_classic", "2.2.3"
|
139
141
|
```
|
140
142
|
|
141
143
|
By default, queue_classic will use the QC_DATABASE_URL falling back on DATABASE_URL. The URL must be in the following format: `postgres://username:password@localhost/database_name`. If you use Heroku's PostgreSQL service, this will already be set. If you don't want to set this variable, you can set the connection in an initializer. **QueueClassic will maintain its own connection to the database.** This may double the number of connections to your database. Set QC::Conn.connection to share the connection between Rails & QueueClassic
|
data/test/benchmark_test.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require File.expand_path("../helper.rb", __FILE__)
|
2
|
-
Thread.abort_on_exception = true
|
3
2
|
|
4
3
|
if ENV["QC_BENCHMARK"]
|
5
4
|
class BenchmarkTest < QCTest
|
@@ -7,26 +6,31 @@ if ENV["QC_BENCHMARK"]
|
|
7
6
|
def test_enqueue
|
8
7
|
n = 10_000
|
9
8
|
start = Time.now
|
10
|
-
n.times
|
9
|
+
n.times do
|
10
|
+
QC.enqueue("1.odd?", [])
|
11
|
+
end
|
11
12
|
assert_equal(n, QC.count)
|
13
|
+
|
12
14
|
elapsed = Time.now - start
|
13
15
|
assert_in_delta(4, elapsed, 1)
|
14
16
|
end
|
15
17
|
|
16
18
|
def test_dequeue
|
17
|
-
|
18
|
-
worker =
|
19
|
-
|
20
|
-
n
|
21
|
-
|
22
|
-
|
23
|
-
assert_equal(n,
|
19
|
+
worker = QC::Worker.new
|
20
|
+
worker.running = true
|
21
|
+
n = 10_000
|
22
|
+
n.times do
|
23
|
+
QC.enqueue("1.odd?", [])
|
24
|
+
end
|
25
|
+
assert_equal(n, QC.count)
|
24
26
|
|
25
27
|
start = Time.now
|
26
|
-
n.times
|
27
|
-
|
28
|
+
n.times do
|
29
|
+
worker.work
|
30
|
+
end
|
28
31
|
elapsed = Time.now - start
|
29
|
-
|
32
|
+
|
33
|
+
assert_equal(0, QC.count)
|
30
34
|
assert_in_delta(10, elapsed, 3)
|
31
35
|
end
|
32
36
|
|
data/test/helper.rb
CHANGED
@@ -9,10 +9,19 @@ require "minitest/autorun"
|
|
9
9
|
|
10
10
|
class QCTest < Minitest::Test
|
11
11
|
|
12
|
+
def setup
|
13
|
+
init_db
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
QC.delete_all
|
18
|
+
end
|
19
|
+
|
12
20
|
def init_db
|
13
|
-
c = QC::
|
14
|
-
|
15
|
-
QC::Setup.
|
21
|
+
c = QC::ConnAdapter.new
|
22
|
+
c.execute("SET client_min_messages TO 'warning'")
|
23
|
+
QC::Setup.drop(c.connection)
|
24
|
+
QC::Setup.create(c.connection)
|
16
25
|
c.execute(File.read('./test/helper.sql'))
|
17
26
|
c.disconnect
|
18
27
|
end
|
data/test/queue_test.rb
CHANGED
@@ -2,13 +2,7 @@ require File.expand_path("../helper.rb", __FILE__)
|
|
2
2
|
|
3
3
|
class QueueTest < QCTest
|
4
4
|
|
5
|
-
|
6
|
-
init_db
|
7
|
-
end
|
8
|
-
|
9
|
-
def teardown
|
10
|
-
QC.conn.disconnect
|
11
|
-
end
|
5
|
+
ResetError = Class.new(PGError)
|
12
6
|
|
13
7
|
def test_enqueue
|
14
8
|
QC.enqueue("Klass.method")
|
@@ -52,8 +46,8 @@ class QueueTest < QCTest
|
|
52
46
|
end
|
53
47
|
|
54
48
|
def test_delete_all_by_queue_name
|
55
|
-
p_queue = QC::Queue.new(
|
56
|
-
s_queue = QC::Queue.new(
|
49
|
+
p_queue = QC::Queue.new("priority_queue")
|
50
|
+
s_queue = QC::Queue.new("secondary_queue")
|
57
51
|
p_queue.enqueue("Klass.method")
|
58
52
|
s_queue.enqueue("Klass.method")
|
59
53
|
assert_equal(1, p_queue.count)
|
@@ -61,19 +55,27 @@ class QueueTest < QCTest
|
|
61
55
|
p_queue.delete_all
|
62
56
|
assert_equal(0, p_queue.count)
|
63
57
|
assert_equal(1, s_queue.count)
|
64
|
-
ensure
|
65
|
-
p_queue.conn.disconnect
|
66
|
-
s_queue.conn.disconnect
|
67
58
|
end
|
68
59
|
|
69
60
|
def test_queue_instance
|
70
|
-
queue = QC::Queue.new(
|
61
|
+
queue = QC::Queue.new("queue_classic_jobs")
|
71
62
|
queue.enqueue("Klass.method")
|
72
63
|
assert_equal(1, queue.count)
|
73
64
|
queue.delete(queue.lock[:id])
|
74
65
|
assert_equal(0, queue.count)
|
75
|
-
|
76
|
-
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_repair_after_error
|
69
|
+
queue = QC::Queue.new("queue_classic_jobs")
|
70
|
+
queue.conn_adapter = QC::ConnAdapter.new
|
71
|
+
queue.enqueue("Klass.method")
|
72
|
+
assert_equal(1, queue.count)
|
73
|
+
conn = queue.conn_adapter.connection
|
74
|
+
def conn.exec(*args); raise(PGError); end
|
75
|
+
def conn.reset(*args); raise(ResetError) end
|
76
|
+
# We ensure that the reset method is called on the connection.
|
77
|
+
assert_raises(PG::Error, ResetError) {queue.enqueue("Klass.other_method")}
|
78
|
+
queue.conn_adapter.disconnect
|
77
79
|
end
|
78
80
|
|
79
81
|
def test_custom_default_queue
|
@@ -97,17 +99,16 @@ class QueueTest < QCTest
|
|
97
99
|
end
|
98
100
|
|
99
101
|
def test_enqueue_triggers_notify
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
adapter = QC.default_conn_adapter
|
103
|
+
adapter.execute('LISTEN "' + QC::QUEUE + '"')
|
104
|
+
adapter.send(:drain_notify)
|
105
|
+
|
106
|
+
msgs = adapter.send(:wait_for_notify, 0.25)
|
104
107
|
assert_equal(0, msgs.length)
|
105
108
|
|
106
109
|
QC.enqueue("Klass.method")
|
107
|
-
msgs =
|
110
|
+
msgs = adapter.send(:wait_for_notify, 0.25)
|
108
111
|
assert_equal(1, msgs.length)
|
109
|
-
ensure
|
110
|
-
c.disconnect
|
111
112
|
end
|
112
113
|
|
113
114
|
end
|
data/test/worker_test.rb
CHANGED
@@ -14,26 +14,19 @@ end
|
|
14
14
|
class TestWorker < QC::Worker
|
15
15
|
attr_accessor :failed_count
|
16
16
|
|
17
|
-
def initialize(
|
18
|
-
super(
|
17
|
+
def initialize(args={})
|
18
|
+
super(args.merge(:connection => QC.default_conn_adapter.connection))
|
19
19
|
@failed_count = 0
|
20
20
|
end
|
21
21
|
|
22
22
|
def handle_failure(job,e)
|
23
23
|
@failed_count += 1
|
24
|
+
super
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
28
|
class WorkerTest < QCTest
|
28
29
|
|
29
|
-
def setup
|
30
|
-
init_db
|
31
|
-
end
|
32
|
-
|
33
|
-
def teardown
|
34
|
-
QC.conn.disconnect
|
35
|
-
end
|
36
|
-
|
37
30
|
def test_work
|
38
31
|
QC.enqueue("TestObject.no_args")
|
39
32
|
worker = TestWorker.new
|
@@ -53,7 +46,7 @@ class WorkerTest < QCTest
|
|
53
46
|
def test_failed_job_is_logged
|
54
47
|
output = capture_debug_output do
|
55
48
|
QC.enqueue("TestObject.not_a_method")
|
56
|
-
|
49
|
+
TestWorker.new.work
|
57
50
|
end
|
58
51
|
expected_output = /lib=queue-classic at=handle_failure job={:id=>"\d+", :method=>"TestObject.not_a_method", :args=>\[\]} error=#<NoMethodError: undefined method `not_a_method' for TestObject:Module>/
|
59
52
|
assert_match(expected_output, output, "=== debug output ===\n #{output}")
|
@@ -102,40 +95,76 @@ class WorkerTest < QCTest
|
|
102
95
|
end
|
103
96
|
|
104
97
|
def test_work_custom_queue
|
105
|
-
p_queue = QC::Queue.new(
|
98
|
+
p_queue = QC::Queue.new("priority_queue")
|
106
99
|
p_queue.enqueue("TestObject.two_args", "1", 2)
|
107
|
-
worker = TestWorker.new(:
|
100
|
+
worker = TestWorker.new(q_name: "priority_queue")
|
108
101
|
r = worker.work
|
109
102
|
assert_equal(["1", 2], r)
|
110
103
|
assert_equal(0, worker.failed_count)
|
111
|
-
worker.stop
|
112
|
-
p_queue.conn.disconnect
|
113
104
|
end
|
114
105
|
|
115
106
|
def test_worker_listens_on_chan
|
116
|
-
p_queue = QC::Queue.new(
|
107
|
+
p_queue = QC::Queue.new("priority_queue")
|
108
|
+
# Use a new connection because the default connection
|
109
|
+
# will be locked by the sleeping worker.
|
110
|
+
p_queue.conn_adapter = QC::ConnAdapter.new
|
111
|
+
# The wait interval is extreme to demonstrate
|
112
|
+
# that the worker is in fact being activated by a NOTIFY.
|
113
|
+
worker = TestWorker.new(:q_name => "priority_queue", :wait_interval => 100)
|
114
|
+
t = Thread.new do
|
115
|
+
r = worker.work
|
116
|
+
assert_equal(["1", 2], r)
|
117
|
+
assert_equal(0, worker.failed_count)
|
118
|
+
end
|
119
|
+
sleep(0.5) #Give the thread some time to start the worker.
|
117
120
|
p_queue.enqueue("TestObject.two_args", "1", 2)
|
118
|
-
|
119
|
-
|
120
|
-
:listening_worker => true)
|
121
|
-
r = worker.work
|
122
|
-
assert_equal(["1", 2], r)
|
123
|
-
assert_equal(0, worker.failed_count)
|
124
|
-
worker.stop
|
125
|
-
p_queue.conn.disconnect
|
121
|
+
p_queue.conn_adapter.disconnect
|
122
|
+
t.join
|
126
123
|
end
|
127
124
|
|
128
125
|
def test_worker_ueses_one_conn
|
129
126
|
QC.enqueue("TestObject.no_args")
|
130
127
|
worker = TestWorker.new
|
131
128
|
worker.work
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
129
|
+
assert_equal(
|
130
|
+
1,
|
131
|
+
QC.default_conn_adapter.execute("SELECT count(*) from pg_stat_activity where datname = current_database()")["count"].to_i,
|
132
|
+
"Multiple connections found -- are there open connections to #{ QC.default_conn_adapter.send(:db_url) } in other terminals?"
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_worker_can_work_multiple_queues
|
137
|
+
p_queue = QC::Queue.new("priority_queue")
|
138
|
+
p_queue.enqueue("TestObject.two_args", "1", 2)
|
139
|
+
|
140
|
+
s_queue = QC::Queue.new("secondary_queue")
|
141
|
+
s_queue.enqueue("TestObject.two_args", "1", 2)
|
142
|
+
|
143
|
+
worker = TestWorker.new(:q_names => ["priority_queue", "secondary_queue"])
|
144
|
+
|
145
|
+
2.times do
|
146
|
+
r = worker.work
|
147
|
+
assert_equal(["1", 2], r)
|
148
|
+
assert_equal(0, worker.failed_count)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_worker_works_multiple_queue_left_to_right
|
153
|
+
l_queue = QC::Queue.new("left_queue")
|
154
|
+
r_queue = QC::Queue.new("right_queue")
|
155
|
+
|
156
|
+
3.times { l_queue.enqueue("TestObject.two_args", "1", 2) }
|
157
|
+
3.times { r_queue.enqueue("TestObject.two_args", "1", 2) }
|
158
|
+
|
159
|
+
worker = TestWorker.new(:q_names => ["left_queue", "right_queue"])
|
160
|
+
|
161
|
+
worker.work
|
162
|
+
assert_equal(2, l_queue.count)
|
163
|
+
assert_equal(3, r_queue.count)
|
164
|
+
|
165
|
+
worker.work
|
166
|
+
assert_equal(1, l_queue.count)
|
167
|
+
assert_equal(3, r_queue.count)
|
140
168
|
end
|
169
|
+
|
141
170
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: queue_classic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Smith (♠ ace hacker)
|
@@ -16,18 +16,18 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.17.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.17.0
|
27
27
|
description: queue_classic is a queueing library for Ruby apps. (Rails, Sinatra, Etc...)
|
28
28
|
queue_classic features asynchronous job polling, database maintained locks and no
|
29
29
|
ridiculous dependencies. As a matter of fact, queue_classic only requires pg.
|
30
|
-
email:
|
30
|
+
email: r@32k.io
|
31
31
|
executables: []
|
32
32
|
extensions: []
|
33
33
|
extra_rdoc_files: []
|
@@ -38,8 +38,7 @@ files:
|
|
38
38
|
- sql/drop_ddl.sql
|
39
39
|
- lib/generators/queue_classic/install_generator.rb
|
40
40
|
- lib/generators/queue_classic/templates/add_queue_classic.rb
|
41
|
-
- lib/queue_classic/
|
42
|
-
- lib/queue_classic/conn.rb
|
41
|
+
- lib/queue_classic/conn_adapter.rb
|
43
42
|
- lib/queue_classic/queue.rb
|
44
43
|
- lib/queue_classic/railtie.rb
|
45
44
|
- lib/queue_classic/setup.rb
|
@@ -47,7 +46,6 @@ files:
|
|
47
46
|
- lib/queue_classic/worker.rb
|
48
47
|
- lib/queue_classic.rb
|
49
48
|
- test/benchmark_test.rb
|
50
|
-
- test/conf_test.rb
|
51
49
|
- test/helper.rb
|
52
50
|
- test/queue_test.rb
|
53
51
|
- test/worker_test.rb
|
@@ -74,9 +72,8 @@ rubyforge_project:
|
|
74
72
|
rubygems_version: 2.0.3
|
75
73
|
signing_key:
|
76
74
|
specification_version: 4
|
77
|
-
summary:
|
75
|
+
summary: Simple, efficient worker queue for Ruby & PostgreSQL.
|
78
76
|
test_files:
|
79
77
|
- test/benchmark_test.rb
|
80
|
-
- test/conf_test.rb
|
81
78
|
- test/queue_test.rb
|
82
79
|
- test/worker_test.rb
|
data/lib/queue_classic/conf.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
module QC
|
4
|
-
module Conf
|
5
|
-
|
6
|
-
def self.env(k); ENV[k]; end
|
7
|
-
def self.env!(k); env(k) || raise("Must set #{k}."); end
|
8
|
-
|
9
|
-
def self.debug?
|
10
|
-
!env('DEBUG').nil?
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.db_url
|
14
|
-
url = env("QC_DATABASE_URL") ||
|
15
|
-
env("DATABASE_URL") ||
|
16
|
-
raise(ArgumentError, "Must set QC_DATABASE_URL or DATABASE_URL.")
|
17
|
-
URI.parse(url)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.normalized_db_url(url=nil)
|
21
|
-
url ||= db_url
|
22
|
-
host = url.host
|
23
|
-
host = host.gsub(/%2F/i, '/') if host
|
24
|
-
[host, # host or percent-encoded socket path
|
25
|
-
url.port || 5432,
|
26
|
-
nil, '', #opts, tty
|
27
|
-
url.path.gsub("/",""), # database name
|
28
|
-
url.user,
|
29
|
-
url.password]
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
end
|
data/lib/queue_classic/conn.rb
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
require 'queue_classic/conf'
|
2
|
-
require 'thread'
|
3
|
-
require 'uri'
|
4
|
-
require 'pg'
|
5
|
-
|
6
|
-
module QC
|
7
|
-
class Conn
|
8
|
-
# Number of seconds to block on the listen chanel for new jobs.
|
9
|
-
WAIT_TIME = (ENV["QC_LISTEN_TIME"] || 5).to_i
|
10
|
-
# You can use the APP_NAME to query for
|
11
|
-
# postgres related process information in the
|
12
|
-
# pg_stat_activity table.
|
13
|
-
APP_NAME = ENV["QC_APP_NAME"] || "queue_classic"
|
14
|
-
|
15
|
-
|
16
|
-
def self.connect
|
17
|
-
QC.log(:at => "establish_conn")
|
18
|
-
conn = PGconn.connect(*Conf.normalized_db_url)
|
19
|
-
if conn.status != PGconn::CONNECTION_OK
|
20
|
-
log(:error => conn.error)
|
21
|
-
end
|
22
|
-
if !Conf.debug?
|
23
|
-
conn.exec("SET client_min_messages TO 'warning'")
|
24
|
-
end
|
25
|
-
conn.exec("SET application_name = '#{APP_NAME}'")
|
26
|
-
conn
|
27
|
-
end
|
28
|
-
|
29
|
-
def initialize
|
30
|
-
@c = self.class.connect
|
31
|
-
@max_attempts = 2
|
32
|
-
end
|
33
|
-
|
34
|
-
def execute(stmt, *params)
|
35
|
-
QC.log(:measure => "conn.exec", :sql => stmt.inspect) do
|
36
|
-
with_retry(@max_attempts) do
|
37
|
-
params = nil if params.empty?
|
38
|
-
r = @c.exec(stmt, params)
|
39
|
-
result = []
|
40
|
-
r.each {|t| result << t}
|
41
|
-
result.length > 1 ? result : result.pop
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def wait(chan)
|
47
|
-
with_retry(@max_attempts) do
|
48
|
-
execute('LISTEN "' + chan + '"')
|
49
|
-
wait_for_notify(WAIT_TIME)
|
50
|
-
execute('UNLISTEN "' + chan + '"')
|
51
|
-
drain_notify
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def reconnect
|
56
|
-
disconnect
|
57
|
-
@c = self.class.connect
|
58
|
-
end
|
59
|
-
|
60
|
-
def disconnect
|
61
|
-
begin @c && @c.finish
|
62
|
-
ensure @c = nil
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def abort_open_transaction
|
67
|
-
if @c.transaction_status != PGconn::PQTRANS_IDLE
|
68
|
-
@c.exec('ROLLBACK')
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def with_retry(n)
|
75
|
-
completed = false
|
76
|
-
attempts = 0
|
77
|
-
result = nil
|
78
|
-
last_error = nil
|
79
|
-
until completed || attempts == n
|
80
|
-
attempts += 1
|
81
|
-
begin
|
82
|
-
result = yield
|
83
|
-
completed = true
|
84
|
-
rescue => e
|
85
|
-
QC.log(:error => e.class, :at => 'conn-retry', :attempts => attempts)
|
86
|
-
last_error = e
|
87
|
-
reconnect
|
88
|
-
end
|
89
|
-
end
|
90
|
-
completed ? result : raise(last_error)
|
91
|
-
end
|
92
|
-
|
93
|
-
def wait_for_notify(t)
|
94
|
-
Array.new.tap do |msgs|
|
95
|
-
@c.wait_for_notify(t) {|event, pid, msg| msgs << msg}
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def drain_notify
|
100
|
-
until @c.notifies.nil?
|
101
|
-
QC.log(:at => "drain_notifications")
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
end
|
data/test/conf_test.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require File.expand_path("../helper.rb", __FILE__)
|
2
|
-
|
3
|
-
class ConfTest < QCTest
|
4
|
-
|
5
|
-
def setup
|
6
|
-
init_db
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_extracts_the_segemnts_to_connect
|
10
|
-
database_url = "postgres://ryan:secret@localhost:1234/application_db"
|
11
|
-
normalized = QC::Conf.normalized_db_url(URI.parse(database_url))
|
12
|
-
assert_equal ["localhost",
|
13
|
-
1234,
|
14
|
-
nil, "",
|
15
|
-
"application_db",
|
16
|
-
"ryan",
|
17
|
-
"secret"], normalized
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_regression_database_url_without_host
|
21
|
-
database_url = "postgres:///my_db"
|
22
|
-
normalized = QC::Conf.normalized_db_url(URI.parse(database_url))
|
23
|
-
assert_equal [nil, 5432, nil, "", "my_db", nil, nil], normalized
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|