pgq 0.1
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +44 -0
- data/LICENSE +20 -0
- data/README.md +207 -0
- data/Rakefile +9 -0
- data/init.rb +1 -0
- data/lib/generators/pgq/add_generator.rb +40 -0
- data/lib/generators/pgq/add_templates/migration.rb +9 -0
- data/lib/generators/pgq/add_templates/pgq_class.rb +10 -0
- data/lib/generators/pgq/add_templates/spec.rb +8 -0
- data/lib/generators/pgq/config_generator.rb +18 -0
- data/lib/generators/pgq/config_templates/pgq.rb +12 -0
- data/lib/pgq.rb +9 -0
- data/lib/pgq/api.rb +88 -0
- data/lib/pgq/consumer.rb +23 -0
- data/lib/pgq/consumer_base.rb +175 -0
- data/lib/pgq/consumer_group.rb +27 -0
- data/lib/pgq/event.rb +43 -0
- data/lib/pgq/marshal64_coder.rb +13 -0
- data/lib/pgq/railtie.rb +9 -0
- data/lib/pgq/utils.rb +127 -0
- data/lib/pgq/version.rb +3 -0
- data/lib/pgq/worker.rb +83 -0
- data/lib/tasks/pgq.rake +64 -0
- data/pgq.gemspec +28 -0
- data/spec/consumer_base_spec.rb +149 -0
- data/spec/consumer_group_spec.rb +37 -0
- data/spec/consumer_spec.rb +53 -0
- data/spec/event_spec.rb +32 -0
- data/spec/marshal64_coder_spec.rb +22 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/worker_spec.rb +53 -0
- metadata +157 -0
data/lib/tasks/pgq.rake
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
namespace :pgq do
|
2
|
+
|
3
|
+
desc "Start worker params: QUEUES, LOGGER, WATCH_FILE"
|
4
|
+
task :worker => [:environment] do
|
5
|
+
queues = ENV['QUEUES']
|
6
|
+
logger_file = ENV['LOGGER'] || Rails.root.join("log/pgq_#{queues}.log")
|
7
|
+
watch_file = ENV['WATCH_FILE'] || Rails.root.join("tmp/pgq_#{queues}.stop")
|
8
|
+
logger = Logger.new(logger_file)
|
9
|
+
w = Pgq::Worker.new(:logger => logger, :queues => queues, :watch_file => watch_file)
|
10
|
+
w.run
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Install PgQ to database RAILS_ENV"
|
14
|
+
task :install do
|
15
|
+
Dir["#{Rails.root}/config/pgq_#{Rails.env}*.ini"].each do |conf|
|
16
|
+
ENV['PGQFILE']=conf
|
17
|
+
Rake::Task['pgq:install_from_file'].invoke
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Install PgQ from file ENV['PGQFILE'] to database RAILS_ENV"
|
22
|
+
task :install_from_file do
|
23
|
+
puts "No file specified" and return if !ENV['PGQFILE'] || ENV['PGQFILE'].empty?
|
24
|
+
|
25
|
+
conf = ENV['PGQFILE']
|
26
|
+
|
27
|
+
puts "installing pgq, running: pgqadm.py #{conf} install"
|
28
|
+
|
29
|
+
output = `which pgqadm.py && pgqadm.py #{conf} install 2>&1 || which pgqadm && pgqadm #{conf} install 2>&1`
|
30
|
+
puts output
|
31
|
+
if output =~ /pgq is installed/ || output =~ /Reading from.*?pgq.sql$/
|
32
|
+
puts "PgQ installed successfully"
|
33
|
+
else
|
34
|
+
raise "Something went wrong(see above)... Check that you install skytools package and create #{conf}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
namespace :ticker do
|
40
|
+
|
41
|
+
desc "Start PgQ ticker daemon"
|
42
|
+
task :start do
|
43
|
+
conf = Rails.root.join("config/pgq_#{Rails.env}.ini")
|
44
|
+
output = `which pgqadm.py && pgqadm.py #{conf} -d ticker 2>&1 || which pgqadm && pgqadm #{conf} -d ticker 2>&1`
|
45
|
+
if output.empty?
|
46
|
+
puts "ticker daemon started"
|
47
|
+
else
|
48
|
+
puts output
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Stop PgQ ticker daemon"
|
53
|
+
task :stop do
|
54
|
+
conf = Rails.root.join("config/pgq_#{Rails.env}.ini")
|
55
|
+
output = `which pgqadm.py && pgqadm.py #{conf} -s 2>&1 || which pgqadm && pgqadm #{conf} -s 2>&1`
|
56
|
+
if output.empty?
|
57
|
+
puts "ticker daemon stoped"
|
58
|
+
else
|
59
|
+
puts output
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/pgq.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + "/lib/pgq/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = %q{pgq}
|
7
|
+
s.version = Pgq::VERSION
|
8
|
+
|
9
|
+
s.authors = ["Makarchev Konstantin"]
|
10
|
+
s.autorequire = %q{init}
|
11
|
+
|
12
|
+
s.description = %q{Queues system for AR/Rails based on PgQ Skytools for PostgreSQL, like Resque on Redis. Rails 3! only tested.}
|
13
|
+
s.summary = %q{Queues system for AR/Rails based on PgQ Skytools for PostgreSQL, like Resque on Redis. Rails 3! only tested.}
|
14
|
+
|
15
|
+
s.email = %q{kostya27@gmail.com}
|
16
|
+
s.homepage = %q{http://github.com/kostya/pgq}
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency 'activesupport', ">=2.3.2"
|
24
|
+
s.add_dependency 'activerecord', ">=2.3.2"
|
25
|
+
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "rake"
|
28
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class PgqTata < Pgq::ConsumerBase
|
4
|
+
end
|
5
|
+
|
6
|
+
class PgqTata2 < Pgq::ConsumerBase
|
7
|
+
@queue_name = "huhu"
|
8
|
+
end
|
9
|
+
|
10
|
+
class PgqTata3 < Pgq::ConsumerBase
|
11
|
+
set_queue_name 'hehu'
|
12
|
+
|
13
|
+
def self.next_queue_name
|
14
|
+
'tutturu'
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform(name, a, b)
|
18
|
+
$a = a
|
19
|
+
$b = b
|
20
|
+
10
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Pgq::ConsumerBase do
|
26
|
+
before :each do
|
27
|
+
@consumer = PgqTata.new
|
28
|
+
@consumer2 = PgqTata2.new
|
29
|
+
@consumer3 = PgqTata3.new
|
30
|
+
|
31
|
+
@coder = @consumer.coder
|
32
|
+
end
|
33
|
+
|
34
|
+
it "queue_name" do
|
35
|
+
PgqTata.queue_name.should == 'tata'
|
36
|
+
@consumer.queue_name.should == 'tata'
|
37
|
+
|
38
|
+
PgqTata2.queue_name.should == 'huhu'
|
39
|
+
@consumer2.queue_name.should == 'huhu'
|
40
|
+
|
41
|
+
PgqTata3.queue_name.should == 'hehu'
|
42
|
+
@consumer3.queue_name.should == 'hehu'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "default encode/decode" do
|
46
|
+
data = {:a => [1, 2, 3, {:b => 'c'}]}
|
47
|
+
s = @coder.dump(data)
|
48
|
+
@coder.load(s).should == data
|
49
|
+
end
|
50
|
+
|
51
|
+
it "enqueue all cases" do
|
52
|
+
PgqTata.database.should_receive(:pgq_insert_event).with('tata', 'method1', @coder.dump([1,2,3]))
|
53
|
+
PgqTata.enqueue(:method1, 1, 2, 3)
|
54
|
+
|
55
|
+
PgqTata2.database.should_receive(:pgq_insert_event).with('huhu', 'method1', @coder.dump([[1,2,3]]))
|
56
|
+
PgqTata2.enqueue(:method1, [1, 2, 3])
|
57
|
+
|
58
|
+
PgqTata3.database.should_receive(:pgq_insert_event).with('tutturu', 'method1', @coder.dump([]))
|
59
|
+
PgqTata3.enqueue(:method1)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
describe "consuming" do
|
64
|
+
before :each do
|
65
|
+
@data = [1, 2, 3]
|
66
|
+
@event = {'ev_type' => 'bla', 'ev_data' => @coder.dump(@data)}
|
67
|
+
end
|
68
|
+
|
69
|
+
it "perform_batch should_receive perform_events" do
|
70
|
+
@consumer.should_receive(:get_batch_events).and_return([@event])
|
71
|
+
@consumer.should_not_receive(:all_events_failed)
|
72
|
+
@consumer.should_receive(:perform_events) do |events|
|
73
|
+
events.size.should == 1
|
74
|
+
ev = events.first
|
75
|
+
ev.type.should == 'bla'
|
76
|
+
ev.data.should == @data
|
77
|
+
end
|
78
|
+
@consumer.should_receive(:finish_batch).with(1)
|
79
|
+
@consumer.perform_batch.should == 1
|
80
|
+
end
|
81
|
+
|
82
|
+
it "perform_batch raises" do
|
83
|
+
@consumer.should_receive(:get_batch_events).and_return([@event])
|
84
|
+
@consumer.should_receive(:perform_events).and_raise(:wow)
|
85
|
+
@consumer.should_receive(:all_events_failed)
|
86
|
+
@consumer.should_receive(:finish_batch).with(1)
|
87
|
+
@consumer.perform_batch.should == 1
|
88
|
+
end
|
89
|
+
|
90
|
+
it "perform_batch empty" do
|
91
|
+
@consumer.should_receive(:get_batch_events).and_return([])
|
92
|
+
@consumer.should_not_receive(:all_events_failed)
|
93
|
+
@consumer.should_not_receive(:perform_events)
|
94
|
+
@consumer.should_receive(:finish_batch).with(0)
|
95
|
+
@consumer.perform_batch.should == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "actions with events" do
|
101
|
+
before :each do
|
102
|
+
@data = [1, 2, 3]
|
103
|
+
@event = Pgq::Event.new(@consumer, {'ev_type' => 'bla', 'ev_data' => @coder.dump(@data)})
|
104
|
+
@events = [@event]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "all_events_failed" do
|
108
|
+
@event.should_receive(:failed!).with(an_instance_of(String))
|
109
|
+
@consumer.all_events_failed(@events, Exception.new('wow'))
|
110
|
+
end
|
111
|
+
|
112
|
+
it "perform_events" do
|
113
|
+
@consumer.should_receive(:perform_event).with(@event)
|
114
|
+
@consumer.perform_events(@events)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "perform_event ok" do
|
118
|
+
@consumer.should_receive(:perform).with('bla', *@data)
|
119
|
+
@consumer.perform_event(@event)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "perform_event raised" do
|
123
|
+
@consumer.should_receive(:perform).with('bla', *@data).and_throw(:wow)
|
124
|
+
@event.should_receive(:failed!).with(an_instance_of(String))
|
125
|
+
@consumer.perform_event(@event)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "migration" do
|
131
|
+
it "up" do
|
132
|
+
Pgq::ConsumerBase.database.should_receive(:pgq_add_queue).with('super', Pgq::ConsumerBase.consumer_name)
|
133
|
+
Pgq::ConsumerBase.add_queue("super")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "down" do
|
137
|
+
Pgq::ConsumerBase.database.should_receive(:pgq_remove_queue).with('super', Pgq::ConsumerBase.consumer_name)
|
138
|
+
Pgq::ConsumerBase.remove_queue("super")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should proxy consumer" do
|
143
|
+
PgqTata3.proxy(:ptest)
|
144
|
+
PgqTata3.ptest(111, 'abc').should == 10
|
145
|
+
$a.should == 111
|
146
|
+
$b.should == 'abc'
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class PgqGr < Pgq::ConsumerGroup
|
4
|
+
def perform_group opts; end
|
5
|
+
def hahah; end
|
6
|
+
def bla; end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Pgq::ConsumerGroup do
|
10
|
+
before :each do
|
11
|
+
@consumer = PgqGr.new
|
12
|
+
@consumer.stub!(:get_next_batch).and_return ''
|
13
|
+
@consumer.stub!(:finish_batch).and_return ''
|
14
|
+
@coder = @consumer.coder
|
15
|
+
end
|
16
|
+
|
17
|
+
it "queue_name" do
|
18
|
+
PgqGr.queue_name.should == 'gr'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should call consume" do
|
22
|
+
@consumer.should_receive(:perform_group) do |h|
|
23
|
+
ev1 = h['hahah']
|
24
|
+
ev1.size.should == 2
|
25
|
+
ev1.first.data.should == '1'
|
26
|
+
ev1.second.data.should == '3'
|
27
|
+
|
28
|
+
ev2 = h['bla']
|
29
|
+
ev2.size.should == 1
|
30
|
+
ev2.first.data.should == '2'
|
31
|
+
end
|
32
|
+
@consumer.should_receive(:get_batch_events).and_return([{'ev_type' => 'hahah', 'ev_data' => @coder.dump('1')},
|
33
|
+
{'ev_type' => 'bla', 'ev_data' => @coder.dump('2')}, {'ev_type' => 'hahah', 'ev_data' => @coder.dump('3')}])
|
34
|
+
@consumer.perform_batch
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class PgqHaha < Pgq::Consumer
|
4
|
+
|
5
|
+
def ptest2(a, b)
|
6
|
+
$a = a
|
7
|
+
$b = b
|
8
|
+
10
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe PgqHaha do
|
14
|
+
before :each do
|
15
|
+
@consumer = PgqHaha.new
|
16
|
+
@coder = @consumer.coder
|
17
|
+
@data = [1,2,[3,4], nil]
|
18
|
+
|
19
|
+
@consumer.stub!(:get_next_batch).and_return ''
|
20
|
+
@consumer.stub!(:finish_batch).and_return ''
|
21
|
+
end
|
22
|
+
|
23
|
+
it "queue name" do
|
24
|
+
@consumer.queue_name.should == 'haha'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "missing method should insert event" do
|
28
|
+
PgqHaha.should_receive(:enqueue).with(:bla, 1,2,[3,4], nil)
|
29
|
+
PgqHaha.bla 1, 2, [3, 4], nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should enqueue with add_event" do
|
33
|
+
PgqHaha.should_receive(:enqueue).with(:bla, 1,2,[3,4], nil)
|
34
|
+
PgqHaha.add_event :bla, 1, 2, [3, 4], nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it "magick perform" do
|
38
|
+
@consumer.should_receive(:bla) do |*h|
|
39
|
+
h.should == @data
|
40
|
+
end
|
41
|
+
|
42
|
+
@consumer.should_receive(:get_batch_events).and_return([{'ev_type' => 'bla', 'ev_data' => @coder.dump(@data)}])
|
43
|
+
@consumer.perform_batch
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should proxy consumer" do
|
47
|
+
PgqHaha.proxy(:ptest2)
|
48
|
+
PgqHaha.ptest2(111, 'abc').should == 10
|
49
|
+
$a.should == 111
|
50
|
+
$b.should == 'abc'
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Pgq::Event do
|
4
|
+
before :each do
|
5
|
+
kl = Class.new(Pgq::ConsumerGroup) do
|
6
|
+
end
|
7
|
+
@consumer = kl.new
|
8
|
+
@consumer.stub!(:get_next_batch).and_return ''
|
9
|
+
@consumer.stub!(:finish_batch).and_return ''
|
10
|
+
@coder = @consumer.coder
|
11
|
+
|
12
|
+
@ev = Pgq::Event.new(@consumer, {'ev_type' => 'haha', 'ev_data' => @coder.dump('aaaaaaa'), 'ev_id' => 123})
|
13
|
+
end
|
14
|
+
|
15
|
+
it "parse data" do
|
16
|
+
@ev.type.should == 'haha'
|
17
|
+
@ev.data.should == 'aaaaaaa'
|
18
|
+
@ev.id.should == 123
|
19
|
+
@ev.consumer.should == @consumer
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should failed!" do
|
23
|
+
@consumer.should_receive(:event_failed).with(123, an_instance_of(String))
|
24
|
+
@ev.failed!
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should retry!" do
|
28
|
+
@consumer.should_receive(:event_retry).with(123)
|
29
|
+
@ev.retry!
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Pgq::Marshal64Coder do
|
4
|
+
before :each do
|
5
|
+
@coder = Pgq::Marshal64Coder
|
6
|
+
end
|
7
|
+
|
8
|
+
it "work" do
|
9
|
+
data = {:a => [1, 2, 3, {:b => 'c'}]}
|
10
|
+
s = @coder.dump(data)
|
11
|
+
@coder.load(s).should == data
|
12
|
+
end
|
13
|
+
|
14
|
+
it "work on large data" do
|
15
|
+
data = {}
|
16
|
+
10000.times{|i| data["#{i}".to_sym] = [i, "i_#{i}"]}
|
17
|
+
|
18
|
+
s = @coder.dump(data)
|
19
|
+
@coder.load(s).should == data
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/worker_spec.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Pgq::Worker do
|
4
|
+
class PgqBla < Pgq::Consumer
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should find class not in hash" do
|
8
|
+
Pgq::Worker.predict_queue_class('bla').should == PgqBla
|
9
|
+
Pgq::Worker.predict_queue_class('bla_1').should == PgqBla
|
10
|
+
Pgq::Worker.predict_queue_class('bla_2').should == PgqBla
|
11
|
+
Pgq::Worker.predict_queue_class('bla3').should == PgqBla
|
12
|
+
Pgq::Worker.predict_queue_class('bla_').should == PgqBla
|
13
|
+
Pgq::Worker.predict_queue_class('pgq_bla_22').should == PgqBla
|
14
|
+
|
15
|
+
Pgq::Worker.predict_queue_class('blah').should == nil
|
16
|
+
Pgq::Worker.predict_queue_class('').should == nil
|
17
|
+
Pgq::Worker.predict_queue_class(nil).should == nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_batch
|
21
|
+
processed_count = 0
|
22
|
+
|
23
|
+
@consumers.each do |consumer|
|
24
|
+
processed_count += consumer.perform_batch
|
25
|
+
|
26
|
+
if @watch_file && File.exists?(@watch_file)
|
27
|
+
logger.info "Found file #{@watch_file}, now exiting"
|
28
|
+
File.unlink(@watch_file)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
processed_count
|
34
|
+
end
|
35
|
+
|
36
|
+
it "initialize" do
|
37
|
+
@w = Pgq::Worker.new :queues => ['bla']
|
38
|
+
|
39
|
+
@w.consumers.size.should == 1
|
40
|
+
cons = @w.consumers.first
|
41
|
+
cons.class.should == PgqBla
|
42
|
+
cons.queue_name.should == 'bla'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "process_batch" do
|
46
|
+
@w = Pgq::Worker.new :queues => ['bla']
|
47
|
+
cons = @w.consumers.first
|
48
|
+
|
49
|
+
cons.should_receive(:perform_batch).and_return(10)
|
50
|
+
@w.process_batch.should == 10
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|