queue_classic 0.3.3.pre → 0.3.5.pre
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.
- data/lib/queue_classic.rb +6 -2
- data/lib/queue_classic/database.rb +40 -46
- data/lib/queue_classic/durable_array.rb +1 -1
- data/lib/queue_classic/logger.rb +17 -0
- data/lib/queue_classic/queue.rb +0 -17
- data/lib/queue_classic/tasks.rb +0 -4
- data/lib/queue_classic/worker.rb +35 -6
- data/test/database_helpers.rb +7 -5
- data/test/durable_array_test.rb +1 -3
- data/test/queue_test.rb +4 -4
- data/test/worker_test.rb +7 -2
- metadata +6 -16
data/lib/queue_classic.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require 'json'
|
2
1
|
require 'pg'
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
3
5
|
require 'uri'
|
4
6
|
|
5
7
|
$: << File.expand_path(__FILE__, 'lib')
|
@@ -7,11 +9,13 @@ $: << File.expand_path(__FILE__, 'lib')
|
|
7
9
|
require 'queue_classic/durable_array'
|
8
10
|
require 'queue_classic/database'
|
9
11
|
require 'queue_classic/worker'
|
12
|
+
require 'queue_classic/logger'
|
10
13
|
require 'queue_classic/queue'
|
11
14
|
require 'queue_classic/job'
|
12
15
|
|
13
16
|
module QC
|
14
|
-
VERBOSE = ENV["VERBOSE"]
|
17
|
+
VERBOSE = ENV["VERBOSE"] || ENV["QC_VERBOSE"]
|
18
|
+
Logger.puts("Logging enabled")
|
15
19
|
|
16
20
|
def self.method_missing(sym, *args, &block)
|
17
21
|
Queue.send(sym, *args, &block)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module QC
|
2
2
|
class Database
|
3
|
+
|
3
4
|
@@connection = nil
|
4
5
|
|
5
6
|
DATABASE_URL = (ENV["QC_DATABASE_URL"] || ENV["DATABASE_URL"])
|
@@ -7,53 +8,52 @@ module QC
|
|
7
8
|
NOTIFY_TIMEOUT = (ENV["QC_NOTIFY_TIMEOUT"] || 10).to_i
|
8
9
|
DEFAULT_QUEUE_NAME = "queue_classic_jobs"
|
9
10
|
|
10
|
-
def self.create_queue(name)
|
11
|
-
db = new(name)
|
12
|
-
db.create_table
|
13
|
-
db.disconnect
|
14
|
-
true
|
15
|
-
end
|
16
|
-
|
17
11
|
attr_reader :table_name
|
18
12
|
|
19
13
|
def initialize(queue_name=nil)
|
14
|
+
log("initialized")
|
15
|
+
|
20
16
|
@top_boundry = MAX_TOP_BOUND
|
17
|
+
log("top_boundry=#{@top_boundry}")
|
18
|
+
|
21
19
|
@table_name = queue_name || DEFAULT_QUEUE_NAME
|
20
|
+
log("table_name=#{@table_name}")
|
21
|
+
|
22
22
|
@db_params = URI.parse(DATABASE_URL)
|
23
|
+
log("uri=#{DATABASE_URL}")
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
-
|
27
|
-
create_table
|
28
|
-
load_functions
|
26
|
+
def set_application_name
|
27
|
+
execute("SET application_name = 'queue_classic'")
|
29
28
|
end
|
30
29
|
|
31
30
|
def listen
|
31
|
+
log("LISTEN")
|
32
32
|
execute("LISTEN queue_classic_jobs")
|
33
33
|
end
|
34
34
|
|
35
35
|
def unlisten
|
36
|
+
log("UNLISTEN")
|
36
37
|
execute("UNLISTEN queue_classic_jobs")
|
37
38
|
end
|
38
39
|
|
39
40
|
def wait_for_notify
|
41
|
+
log("waiting for notify timeout=#{NOTIFY_TIMEOUT}")
|
40
42
|
connection.wait_for_notify(NOTIFY_TIMEOUT)
|
43
|
+
log("done waiting for notify")
|
41
44
|
end
|
42
45
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
def silence_warnings
|
52
|
-
execute("SET client_min_messages TO 'warning'")
|
46
|
+
def execute(sql)
|
47
|
+
log("executing=#{sql}")
|
48
|
+
begin
|
49
|
+
connection.exec(sql)
|
50
|
+
rescue PGError => e
|
51
|
+
log("execute exception=#{e.inspect}")
|
52
|
+
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
56
|
-
connection
|
55
|
+
def connection
|
56
|
+
@@connection ||= connect
|
57
57
|
end
|
58
58
|
|
59
59
|
def disconnect
|
@@ -61,30 +61,20 @@ module QC
|
|
61
61
|
@@connection = nil
|
62
62
|
end
|
63
63
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
silence_warnings unless VERBOSE
|
64
|
+
def connect
|
65
|
+
log("establishing connection")
|
66
|
+
conn = PGconn.connect(
|
67
|
+
@db_params.host,
|
68
|
+
@db_params.port || 5432,
|
69
|
+
nil, '', #opts, tty
|
70
|
+
@db_params.path.gsub("/",""), # database name
|
71
|
+
@db_params.user,
|
72
|
+
@db_params.password
|
73
|
+
)
|
74
|
+
if conn.status != PGconn::CONNECTION_OK
|
75
|
+
log("connection error=#{conn.error}")
|
77
76
|
end
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
def drop_table
|
82
|
-
execute("DROP TABLE IF EXISTS #{@table_name} CASCADE")
|
83
|
-
end
|
84
|
-
|
85
|
-
def create_table
|
86
|
-
execute("CREATE TABLE #{@table_name} (id serial, details text, locked_at timestamp)")
|
87
|
-
execute("CREATE INDEX #{@table_name}_id_idx ON #{@table_name} (id)")
|
77
|
+
conn
|
88
78
|
end
|
89
79
|
|
90
80
|
def load_functions
|
@@ -141,5 +131,9 @@ module QC
|
|
141
131
|
EOD
|
142
132
|
end
|
143
133
|
|
134
|
+
def log(msg)
|
135
|
+
Logger.puts(["database", msg].join(" "))
|
136
|
+
end
|
137
|
+
|
144
138
|
end
|
145
139
|
end
|
data/lib/queue_classic/queue.rb
CHANGED
@@ -32,29 +32,12 @@ module QC
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
module QC
|
36
|
-
module ConnectionHelper
|
37
|
-
|
38
|
-
def connection_status
|
39
|
-
{:total => database.all_conns.count, :waiting => database.waiting_conns.count}
|
40
|
-
end
|
41
|
-
|
42
|
-
def disconnect
|
43
|
-
database.disconnect
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
35
|
module QC
|
50
36
|
class Queue
|
51
37
|
|
52
38
|
include AbstractQueue
|
53
39
|
extend AbstractQueue
|
54
40
|
|
55
|
-
include ConnectionHelper
|
56
|
-
extend ConnectionHelper
|
57
|
-
|
58
41
|
def self.array
|
59
42
|
if defined? @@array
|
60
43
|
@@array
|
data/lib/queue_classic/tasks.rb
CHANGED
data/lib/queue_classic/worker.rb
CHANGED
@@ -4,9 +4,16 @@ module QC
|
|
4
4
|
MAX_LOCK_ATTEMPTS = (ENV["QC_MAX_LOCK_ATTEMPTS"] || 5).to_i
|
5
5
|
|
6
6
|
def initialize
|
7
|
-
|
8
|
-
|
7
|
+
log("worker initialized")
|
8
|
+
log("worker running exp. backoff algorith max_attempts=#{MAX_LOCK_ATTEMPTS}")
|
9
|
+
@running = true
|
10
|
+
|
11
|
+
@queue = QC::Queue.new(ENV["QUEUE"])
|
12
|
+
log("worker table=#{@queue.database.table_name}")
|
13
|
+
|
9
14
|
@fork_worker = ENV["QC_FORK_WORKER"] == "true"
|
15
|
+
log("worker fork=#{@fork_worker}")
|
16
|
+
|
10
17
|
handle_signals
|
11
18
|
end
|
12
19
|
|
@@ -23,6 +30,7 @@ module QC
|
|
23
30
|
trap(sig) do
|
24
31
|
if running?
|
25
32
|
@running = false
|
33
|
+
log("worker running=#{@running}")
|
26
34
|
else
|
27
35
|
raise Interrupt
|
28
36
|
end
|
@@ -30,8 +38,14 @@ module QC
|
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
41
|
+
def setup_child
|
42
|
+
log("forked worker running setup")
|
43
|
+
end
|
44
|
+
|
33
45
|
def start
|
46
|
+
log("worker starting")
|
34
47
|
while running?
|
48
|
+
log("worker running...")
|
35
49
|
if fork_worker?
|
36
50
|
fork_and_work
|
37
51
|
else
|
@@ -41,38 +55,49 @@ module QC
|
|
41
55
|
end
|
42
56
|
|
43
57
|
def fork_and_work
|
44
|
-
@cpid = fork { work }
|
58
|
+
@cpid = fork { setup_child; work }
|
59
|
+
log("worker forked pid=#{@cpid}")
|
45
60
|
Process.wait(@cpid)
|
46
61
|
end
|
47
62
|
|
48
63
|
def work
|
64
|
+
log("worker start working")
|
49
65
|
if job = lock_job
|
66
|
+
log("worker locked job=#{job.id}")
|
50
67
|
begin
|
51
68
|
job.work
|
69
|
+
log("worker finished job=#{job.id}")
|
52
70
|
rescue Object => e
|
71
|
+
log("worker failed job=#{job.id} exception=#{e.inspect}")
|
53
72
|
handle_failure(job,e)
|
54
73
|
ensure
|
55
74
|
@queue.delete(job)
|
75
|
+
log("worker deleted job=#{job.id}")
|
56
76
|
end
|
57
77
|
end
|
58
|
-
job = nil
|
59
|
-
GC.start
|
60
78
|
end
|
61
79
|
|
62
80
|
def lock_job
|
81
|
+
log("worker attempting a lock")
|
63
82
|
attempts = 0
|
64
83
|
job = nil
|
65
84
|
until job
|
66
85
|
job = @queue.dequeue
|
67
86
|
if job.nil?
|
87
|
+
log("worker missed lock attempt=#{attempts}")
|
68
88
|
attempts += 1
|
69
89
|
if attempts < MAX_LOCK_ATTEMPTS
|
70
|
-
|
90
|
+
seconds = 2**attempts
|
91
|
+
log("worker sleeps seconds=#{seconds}")
|
92
|
+
sleep(seconds)
|
93
|
+
log("worker tries again")
|
71
94
|
next
|
72
95
|
else
|
96
|
+
log("worker reached max attempts. max=#{MAX_LOCK_ATTEMPTS}")
|
73
97
|
break
|
74
98
|
end
|
75
99
|
else
|
100
|
+
log("worker successfully locked job")
|
76
101
|
end
|
77
102
|
end
|
78
103
|
job
|
@@ -87,5 +112,9 @@ module QC
|
|
87
112
|
puts "!"
|
88
113
|
end
|
89
114
|
|
115
|
+
def log(msg)
|
116
|
+
Logger.puts(msg)
|
117
|
+
end
|
118
|
+
|
90
119
|
end
|
91
120
|
end
|
data/test/database_helpers.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
module DatabaseHelpers
|
2
2
|
|
3
|
-
def init_db(table_name=
|
4
|
-
database
|
5
|
-
database.
|
6
|
-
database.
|
3
|
+
def init_db(table_name="queue_classic_jobs")
|
4
|
+
database = QC::Database.new(table_name)
|
5
|
+
database.execute("SET client_min_messages TO 'warning'")
|
6
|
+
database.execute("DROP TABLE IF EXISTS #{table_name} CASCADE")
|
7
|
+
database.execute("CREATE TABLE #{table_name} (id serial, details text, locked_at timestamp)")
|
8
|
+
database.load_functions
|
7
9
|
database.disconnect
|
8
|
-
|
10
|
+
database
|
9
11
|
end
|
10
12
|
|
11
13
|
end
|
data/test/durable_array_test.rb
CHANGED
data/test/queue_test.rb
CHANGED
@@ -2,7 +2,7 @@ require File.expand_path("../helper.rb", __FILE__)
|
|
2
2
|
|
3
3
|
context "Queue" do
|
4
4
|
|
5
|
-
setup { init_db }
|
5
|
+
setup { @database = init_db }
|
6
6
|
|
7
7
|
test "Queue class responds to enqueue" do
|
8
8
|
QC::Queue.enqueue("Klass.method")
|
@@ -43,10 +43,10 @@ context "Queue" do
|
|
43
43
|
|
44
44
|
test "queue instance responds to enqueue" do
|
45
45
|
QC::Queue.enqueue("Something.hard_to_find")
|
46
|
-
init_db(:custom_queue_name)
|
46
|
+
tmp_db = init_db(:custom_queue_name)
|
47
47
|
@queue = QC::Queue.new(:custom_queue_name)
|
48
48
|
@queue.enqueue "Klass.method"
|
49
|
-
@queue.disconnect
|
49
|
+
@queue.database.disconnect
|
50
50
|
end
|
51
51
|
|
52
52
|
test "queue only uses 1 connection per class" do
|
@@ -55,7 +55,7 @@ context "Queue" do
|
|
55
55
|
QC::Queue.delete QC::Queue.dequeue
|
56
56
|
QC::Queue.enqueue "Klass.method"
|
57
57
|
QC::Queue.dequeue
|
58
|
-
assert_equal 1,
|
58
|
+
assert_equal 1, @database.execute("SELECT count(*) from pg_stat_activity")[0]["count"].to_i
|
59
59
|
end
|
60
60
|
|
61
61
|
end
|
data/test/worker_test.rb
CHANGED
@@ -23,10 +23,14 @@ end
|
|
23
23
|
context "Worker" do
|
24
24
|
|
25
25
|
setup do
|
26
|
-
init_db
|
26
|
+
@database = init_db
|
27
27
|
@worker = TestWorker.new
|
28
28
|
end
|
29
29
|
|
30
|
+
teardown do
|
31
|
+
@database.disconnect
|
32
|
+
end
|
33
|
+
|
30
34
|
test "working a job" do
|
31
35
|
QC::Queue.enqueue "TestNotifier.deliver", {}
|
32
36
|
|
@@ -46,6 +50,7 @@ context "Worker" do
|
|
46
50
|
test "only makes one connection" do
|
47
51
|
QC.enqueue "TestNotifier.deliver", {}
|
48
52
|
@worker.work
|
49
|
-
assert_equal 1,
|
53
|
+
assert_equal 1, @database.execute("SELECT count(*) from pg_stat_activity")[0]["count"].to_i
|
50
54
|
end
|
55
|
+
|
51
56
|
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: 0.3.
|
4
|
+
version: 0.3.5.pre
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,26 +13,15 @@ date: 2011-08-22 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg
|
16
|
-
requirement: &
|
16
|
+
requirement: &2160828260 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
21
|
+
version: 0.11.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: json
|
27
|
-
requirement: &2153628740 !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
|
-
requirements:
|
30
|
-
- - ! '>='
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '0'
|
33
|
-
type: :runtime
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: *2153628740
|
24
|
+
version_requirements: *2160828260
|
36
25
|
description: Queue Classic (beta) is a queueing library for Ruby apps (Rails, Sinatra,
|
37
26
|
Etc...) Queue Classic features asynchronous job polling, database maintained locks
|
38
27
|
and no ridiculous dependencies. As a matter of fact, Queue Classic only requires
|
@@ -46,6 +35,7 @@ files:
|
|
46
35
|
- lib/queue_classic/database.rb
|
47
36
|
- lib/queue_classic/durable_array.rb
|
48
37
|
- lib/queue_classic/job.rb
|
38
|
+
- lib/queue_classic/logger.rb
|
49
39
|
- lib/queue_classic/queue.rb
|
50
40
|
- lib/queue_classic/tasks.rb
|
51
41
|
- lib/queue_classic/worker.rb
|