queueing_rabbit 0.1.3 → 0.2.0

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.
Files changed (33) hide show
  1. data/lib/queueing_rabbit/client/amqp.rb +46 -40
  2. data/lib/queueing_rabbit/client/bunny.rb +20 -23
  3. data/lib/queueing_rabbit/extensions/direct_exchange.rb +26 -0
  4. data/lib/queueing_rabbit/extensions/new_relic.rb +19 -4
  5. data/lib/queueing_rabbit/extensions/retryable.rb +22 -0
  6. data/lib/queueing_rabbit/job.rb +56 -11
  7. data/lib/queueing_rabbit/jobs/abstract_job.rb +27 -0
  8. data/lib/queueing_rabbit/jobs/json_job.rb +19 -0
  9. data/lib/queueing_rabbit/version.rb +1 -1
  10. data/lib/queueing_rabbit/worker.rb +14 -4
  11. data/lib/queueing_rabbit.rb +21 -6
  12. data/queueing_rabbit.gemspec +6 -17
  13. data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +36 -16
  14. data/spec/integration/asynchronous_publishing_and_consuming_with_retries_spec.rb +103 -0
  15. data/spec/integration/direct_exchange_asynchronous_publishing_and_consuming_spec.rb +93 -0
  16. data/spec/integration/jobs/print_line_job.rb +5 -11
  17. data/spec/integration/json_job_asynchronous_publishing_and_consuming_spec.rb +99 -0
  18. data/spec/integration/persistent_asynchronous_publishing_and_consuming_spec.rb +96 -0
  19. data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +13 -17
  20. data/spec/integration/synchronous_publishing_spec.rb +6 -2
  21. data/spec/spec_helper.rb +4 -1
  22. data/spec/unit/queueing_rabbit/client/amqp_spec.rb +80 -51
  23. data/spec/unit/queueing_rabbit/client/bunny_spec.rb +53 -8
  24. data/spec/unit/queueing_rabbit/extensions/direct_exchange_spec.rb +20 -0
  25. data/spec/unit/queueing_rabbit/extensions/new_relic_spec.rb +36 -0
  26. data/spec/unit/queueing_rabbit/extensions/retryable_spec.rb +36 -0
  27. data/spec/unit/queueing_rabbit/job_spec.rb +76 -4
  28. data/spec/unit/queueing_rabbit/jobs/abstract_job_spec.rb +73 -0
  29. data/spec/unit/queueing_rabbit/jobs/json_job_spec.rb +22 -0
  30. data/spec/unit/queueing_rabbit/worker_spec.rb +33 -23
  31. data/spec/unit/queueing_rabbit_spec.rb +55 -25
  32. metadata +111 -90
  33. data/spec/support/shared_examples.rb +0 -37
@@ -1,3 +1,3 @@
1
1
  module QueueingRabbit
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -69,14 +69,24 @@ module QueueingRabbit
69
69
  end
70
70
 
71
71
  def run_job(conn, job)
72
- conn.open_channel(job.channel_options) do |channel, _|
73
- conn.listen_queue(channel, job.queue_name, job.queue_options) do |args|
74
- info "performing job #{job} with arguments #{args.inspect}"
75
- job.perform(args)
72
+ QueueingRabbit.follow_job_requirements(job) do |_, _, queue|
73
+ conn.listen_queue(queue, job.listening_options) do |payload, metadata|
74
+ info "performing job #{job}"
75
+ invoke_job(job, payload, metadata)
76
76
  end
77
77
  end
78
78
  end
79
79
 
80
+ def invoke_job(job, payload, metadata)
81
+ if job.respond_to?(:perform)
82
+ job.perform(payload, metadata)
83
+ elsif job <= QueueingRabbit::AbstractJob
84
+ job.new(payload, metadata).perform
85
+ else
86
+ error "do not know how to perform job #{job}"
87
+ end
88
+ end
89
+
80
90
  def sync_stdio
81
91
  $stdout.sync = true
82
92
  $stderr.sync = true
@@ -7,7 +7,11 @@ require "queueing_rabbit/client/callbacks"
7
7
  require "queueing_rabbit/client/amqp"
8
8
  require "queueing_rabbit/client/bunny"
9
9
  require "queueing_rabbit/extensions/new_relic"
10
+ require "queueing_rabbit/extensions/retryable"
11
+ require "queueing_rabbit/extensions/direct_exchange"
10
12
  require "queueing_rabbit/job"
13
+ require "queueing_rabbit/jobs/abstract_job"
14
+ require "queueing_rabbit/jobs/json_job"
11
15
  require "queueing_rabbit/worker"
12
16
 
13
17
  module QueueingRabbit
@@ -29,24 +33,35 @@ module QueueingRabbit
29
33
  def connection
30
34
  @connection ||= connect
31
35
  end
36
+ alias_method :conn, :connection
32
37
 
33
38
  def drop_connection
34
39
  @connection = nil
35
40
  end
36
41
 
37
- def enqueue(job, arguments = {})
38
- info "enqueueing job #{job} with arguments: #{arguments.inspect}."
42
+ def enqueue(job, payload = nil, options = {})
43
+ info "enqueueing job #{job}"
39
44
 
40
- connection.open_channel(job.channel_options) do |c, _|
41
- connection.define_queue(c, job.queue_name, job.queue_options)
42
- connection.enqueue(c, job.queue_name, arguments)
43
- c.close
45
+ follow_job_requirements(job) do |channel, exchange, _|
46
+ conn.enqueue(exchange, payload, options)
47
+ channel.close
44
48
  end
45
49
 
46
50
  true
47
51
  end
48
52
  alias_method :publish, :enqueue
49
53
 
54
+ def follow_job_requirements(job)
55
+ conn.open_channel(job.channel_options) do |ch, _|
56
+ conn.define_exchange(ch, job.exchange_name, job.exchange_options) do |ex|
57
+ conn.define_queue(ch, job.queue_name, job.queue_options) do |q|
58
+ conn.bind_queue(q, ex, job.binding_options) if job.bind_queue?
59
+ yield ch, ex, q if block_given?
60
+ end
61
+ end
62
+ end
63
+ end
64
+
50
65
  def queue_size(job)
51
66
  size = 0
52
67
  connection.open_channel(job.channel_options) do |c, _|
@@ -17,19 +17,17 @@ Gem::Specification.new do |gem|
17
17
  gem.extra_rdoc_files = [ "LICENSE", "README.md" ]
18
18
  gem.rdoc_options = ["--charset=UTF-8"]
19
19
 
20
- gem.add_dependency "amqp", ">= 0.9.0"
21
- gem.add_dependency "bunny", ">= 0.9.0.pre7"
20
+ gem.add_dependency "amqp", "~> 1.0.0"
21
+ gem.add_dependency "bunny", "~> 0.10.0"
22
22
  gem.add_dependency "rake", ">= 0"
23
23
  gem.add_dependency "json", ">= 0"
24
24
 
25
25
  gem.description = <<description
26
26
  QueueingRabbit is a Ruby library providing convenient object-oriented syntax
27
- for managing background jobs using AMQP. All jobs' argumets are serialized
28
- to JSON and transfered to jobs using AMQP message payload. The library
29
- implements amqp and bunny gems as adapters making it possible to use
30
- synchronous publishing and asynchronous consuming, which might be useful for
31
- Rails app running on non-EventMachine based application servers (i. e.
32
- Passenger).
27
+ for managing background jobs using AMQP. The library implements amqp and
28
+ bunny gems as adapters making it possible to use synchronous publishing and
29
+ asynchronous consuming, which might be useful for Rails app running on
30
+ non-EventMachine based application servers (i. e. Passenger).
33
31
 
34
32
  Any Ruby class or Module can be transformed into QueueingRabbit's background
35
33
  job by including QueueingRabbit::Job module. It is also possible to inherit
@@ -37,14 +35,5 @@ Gem::Specification.new do |gem|
37
35
 
38
36
  The library is bundled with a Rake task which is capable of starting a
39
37
  worker processing a specified list of jobs.
40
-
41
- To achieve the required simplicity the gem doesn't try to support all
42
- features of AMQP protocol. It uses a restricted subset instead:
43
-
44
- * Only a single direct exchange is used
45
- * Every job is consumed using a separate channel
46
- * All jobs are consumed with acknowledgements
47
- * ACK is only sent to the broker if a job was processed successfully
48
- * Currently all messages are published with persistent option
49
38
  description
50
39
  end
@@ -5,42 +5,60 @@ describe "Asynchronous publishing and consuming example" do
5
5
  include_context "Evented spec"
6
6
  include_context "StringIO logger"
7
7
 
8
- let(:job) { PrintLineJob }
9
-
10
8
  before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
11
9
  after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
12
10
 
13
11
  context "basic consuming" do
12
+ let(:line) { "Hello, world!" }
14
13
  let(:connection) { QueueingRabbit.connection }
15
- let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
14
+ let(:job) { PrintLineJob }
16
15
  let(:io) { StringIO.new }
17
- let(:line) { "Hello, world!" }
16
+ let(:worker) { QueueingRabbit::Worker.new(job.to_s) }
18
17
 
19
18
  before(:each) do
20
19
  QueueingRabbit.drop_connection
21
- PrintLineJob.io = io
20
+ end
21
+
22
+ before do
23
+ job.io = io
22
24
  end
23
25
 
24
26
  it "processes enqueued jobs" do
25
27
  em {
26
28
  QueueingRabbit.connect
27
- queue_size = nil
29
+ @queue_size = nil
30
+
31
+ def request_queue_size
32
+ connection.open_channel do |c, _|
33
+ connection.define_queue(c, job.queue_name, job.queue_options).status do |s, _|
34
+ @queue_size = s
35
+ end
36
+ end
37
+ end
28
38
 
29
39
  delayed(0.5) {
30
- 3.times { QueueingRabbit.enqueue(job, :line => line) }
40
+ 3.times { job.enqueue(line) }
31
41
  }
32
42
 
33
- delayed(1.0) { worker.work }
43
+ delayed(1.5) {
44
+ request_queue_size
45
+ }
34
46
 
35
47
  delayed(2.0) {
36
- connection.open_channel do |c, _|
37
- connection.define_queue(c, :print_line_job, job.queue_options).status do |s, _|
38
- queue_size = s
39
- end
40
- end
48
+ @queue_size.should == 3
49
+ }
50
+
51
+ delayed(2.5) {
52
+ worker.work
53
+ }
54
+
55
+ delayed(3.5) {
56
+ request_queue_size
41
57
  }
42
58
 
43
- done(3.0) { queue_size.should == 0 }
59
+ done(4.0) {
60
+ @queue_size.should be_zero
61
+ }
44
62
  }
45
63
  end
46
64
 
@@ -48,9 +66,11 @@ describe "Asynchronous publishing and consuming example" do
48
66
  em {
49
67
  QueueingRabbit.connect
50
68
 
51
- delayed(0.5) { QueueingRabbit.enqueue(job, :line => line) }
69
+ delayed(0.5) { job.enqueue(line) }
52
70
 
53
- delayed(1.0) { worker.work }
71
+ delayed(1.0) {
72
+ worker.work
73
+ }
54
74
 
55
75
  done(2.0) {
56
76
  io.string.should include(line)
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Asynchronous publishing and consuming with retries" do
5
+ include_context "Evented spec"
6
+ include_context "StringIO logger"
7
+
8
+ before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
9
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
10
+
11
+ context "basic consuming" do
12
+ let(:line) { "Hello, world!" }
13
+ let(:connection) { QueueingRabbit.connection }
14
+ let(:job) {
15
+ Class.new(QueueingRabbit::AbstractJob) do
16
+ class << self
17
+ attr_accessor :io, :times_executed
18
+ end
19
+
20
+ include QueueingRabbit::JobExtensions::Retryable
21
+ queue 'retryable_print_line_job'
22
+
23
+ def perform
24
+ self.class.times_executed = self.class.times_executed.to_i + 1
25
+ if retries > 2
26
+ self.class.io << 'Hello, world!'
27
+ else
28
+ retry_upto(3)
29
+ end
30
+ end
31
+ end
32
+ }
33
+ let(:job_name) { 'RetryablePrintLineJob' }
34
+ let(:io) { StringIO.new }
35
+ let(:worker) { QueueingRabbit::Worker.new(job_name) }
36
+
37
+ before(:each) do
38
+ QueueingRabbit.drop_connection
39
+ end
40
+
41
+ before do
42
+ job.io = io
43
+ stub_const(job_name, job)
44
+ end
45
+
46
+ it "processes enqueued jobs" do
47
+ em {
48
+ QueueingRabbit.connect
49
+ @queue_size = nil
50
+
51
+ def request_queue_size
52
+ connection.open_channel do |c, _|
53
+ connection.define_queue(c, job.queue_name, job.queue_options).status do |s, _|
54
+ @queue_size = s
55
+ end
56
+ end
57
+ end
58
+
59
+ delayed(0.5) {
60
+ 3.times { job.enqueue(line) }
61
+ }
62
+
63
+ delayed(1.5) {
64
+ request_queue_size
65
+ }
66
+
67
+ delayed(2.0) {
68
+ @queue_size.should == 3
69
+ }
70
+
71
+ delayed(2.5) {
72
+ worker.work
73
+ }
74
+
75
+ delayed(3.5) {
76
+ request_queue_size
77
+ }
78
+
79
+ done(4.0) {
80
+ @queue_size.should be_zero
81
+ job.times_executed.should == 12
82
+ }
83
+ }
84
+ end
85
+
86
+ it "actually outputs the line" do
87
+ em {
88
+ QueueingRabbit.connect
89
+
90
+ delayed(0.5) { job.enqueue(line) }
91
+
92
+ delayed(1.0) {
93
+ worker.work
94
+ }
95
+
96
+ done(2.0) {
97
+ io.string.should include(line)
98
+ }
99
+ }
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Asynchronous publishing and consuming using direct exchange" do
5
+ include_context "Evented spec"
6
+ include_context "StringIO logger"
7
+
8
+ before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
9
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
10
+
11
+ context "basic consuming" do
12
+ let(:line) { "Hello, world!" }
13
+ let(:connection) { QueueingRabbit.connection }
14
+ let(:job) {
15
+ Class.new(PrintLineJob) do
16
+ include QueueingRabbit::JobExtensions::DirectExchange
17
+ queue 'print_line_job_direct_exchange'
18
+ exchange 'queueing_rabbit.test', :type => :direct
19
+ end
20
+ }
21
+ let(:job_name) { 'PrintLineDirectExchangeJob' }
22
+ let(:io) { StringIO.new }
23
+ let(:worker) { QueueingRabbit::Worker.new(job_name) }
24
+
25
+ before do
26
+ stub_const(job_name, job)
27
+ end
28
+
29
+ before(:each) do
30
+ QueueingRabbit.drop_connection
31
+ end
32
+
33
+ before do
34
+ job.io = io
35
+ end
36
+
37
+ it "processes enqueued jobs" do
38
+ em {
39
+ QueueingRabbit.connect
40
+ @queue_size = nil
41
+
42
+ def request_queue_size
43
+ connection.open_channel do |c, _|
44
+ connection.define_queue(c, job.queue_name, job.queue_options).status do |s, _|
45
+ @queue_size = s
46
+ end
47
+ end
48
+ end
49
+
50
+ delayed(0.5) {
51
+ 3.times { job.enqueue(line) }
52
+ }
53
+
54
+ delayed(1.5) {
55
+ request_queue_size
56
+ }
57
+
58
+ delayed(2.0) {
59
+ @queue_size.should == 3
60
+ }
61
+
62
+ delayed(2.5) {
63
+ worker.work
64
+ }
65
+
66
+ delayed(3.5) {
67
+ request_queue_size
68
+ }
69
+
70
+ done(4.0) {
71
+ @queue_size.should be_zero
72
+ }
73
+ }
74
+ end
75
+
76
+ it "actually outputs the line" do
77
+ em {
78
+ QueueingRabbit.connect
79
+
80
+ delayed(0.5) { job.enqueue(line) }
81
+
82
+ delayed(1.0) {
83
+ worker.work
84
+ }
85
+
86
+ done(2.0) {
87
+ io.string.should include(line)
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end
93
+
@@ -1,17 +1,11 @@
1
- class PrintLineJob
2
- extend QueueingRabbit::Job
3
-
1
+ class PrintLineJob < QueueingRabbit::AbstractJob
4
2
  class << self
5
- attr_writer :io
3
+ attr_accessor :io
6
4
  end
7
5
 
8
- queue :print_line_job, :durable => true
9
-
10
- def self.perform(arguments = {})
11
- self.io.puts arguments[:line]
12
- end
6
+ queue :print_line_job
13
7
 
14
- def self.io
15
- @io ||= STDOUT
8
+ def perform
9
+ self.class.io.puts payload
16
10
  end
17
11
  end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+ require 'integration/jobs/print_line_job'
3
+
4
+ describe "Asynchronous publishing and consuming with JSON serialization" do
5
+ include_context "Evented spec"
6
+ include_context "StringIO logger"
7
+
8
+ before(:all) { QueueingRabbit.client = QueueingRabbit::Client::AMQP }
9
+ after(:all) { QueueingRabbit.client = QueueingRabbit.default_client }
10
+
11
+ context "basic consuming" do
12
+ let(:line) { "Hello, world!" }
13
+ let(:connection) { QueueingRabbit.connection }
14
+ let(:job) {
15
+ Class.new(QueueingRabbit::JSONJob) do
16
+ class << self
17
+ attr_accessor :io
18
+ end
19
+
20
+ def perform
21
+ self.class.io.puts arguments[:line]
22
+ end
23
+ end
24
+
25
+ }
26
+ let(:job_name) { 'PrintLineFromJSONJob' }
27
+ let(:io) { StringIO.new }
28
+ let(:worker) { QueueingRabbit::Worker.new(job_name) }
29
+
30
+ before do
31
+ stub_const(job_name, job)
32
+ job.io = io
33
+ end
34
+
35
+ before(:each) do
36
+ QueueingRabbit.drop_connection
37
+ end
38
+
39
+ before do
40
+ job.io = io
41
+ end
42
+
43
+ it "processes enqueued jobs" do
44
+ em {
45
+ QueueingRabbit.connect
46
+ @queue_size = nil
47
+
48
+ def request_queue_size
49
+ connection.open_channel do |c, _|
50
+ connection.define_queue(c, job.queue_name, job.queue_options).status do |s, _|
51
+ @queue_size = s
52
+ end
53
+ end
54
+ end
55
+
56
+ delayed(0.5) {
57
+ 3.times { job.enqueue(:line => line) }
58
+ }
59
+
60
+ delayed(1.5) {
61
+ request_queue_size
62
+ }
63
+
64
+ delayed(2.0) {
65
+ @queue_size.should == 3
66
+ }
67
+
68
+ delayed(2.5) {
69
+ worker.work
70
+ }
71
+
72
+ delayed(3.5) {
73
+ request_queue_size
74
+ }
75
+
76
+ done(4.0) {
77
+ @queue_size.should be_zero
78
+ }
79
+ }
80
+ end
81
+
82
+ it "actually outputs the line" do
83
+ em {
84
+ QueueingRabbit.connect
85
+
86
+ delayed(0.5) { job.enqueue(:line => line) }
87
+
88
+ delayed(1.0) {
89
+ worker.work
90
+ }
91
+
92
+ done(2.0) {
93
+ io.string.should include(line)
94
+ }
95
+ }
96
+ end
97
+ end
98
+ end
99
+