queue_classic 2.3.0beta → 3.0.0beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|