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
@@ -6,12 +6,11 @@ module QueueingRabbit
6
6
 
7
7
  class AMQP
8
8
 
9
- include QueueingRabbit::Serializer
10
9
  include QueueingRabbit::Logging
11
10
  extend QueueingRabbit::Logging
12
11
  extend QueueingRabbit::Client::Callbacks
13
12
 
14
- attr_reader :connection, :exchange_name, :exchange_options
13
+ attr_reader :connection
15
14
 
16
15
  define_callback :on_tcp_failure do |_|
17
16
  fatal "unable to establish TCP connection to broker"
@@ -39,24 +38,33 @@ module QueueingRabbit
39
38
  end
40
39
 
41
40
  def self.connect
42
- self.run_event_machine
41
+ self.ensure_event_machine_is_running
43
42
 
44
- self.new(::AMQP.connect(QueueingRabbit.amqp_uri, connection_options),
45
- QueueingRabbit.amqp_exchange_name,
46
- QueueingRabbit.amqp_exchange_options)
43
+ self.new(::AMQP.connect(QueueingRabbit.amqp_uri, connection_options))
47
44
  end
48
45
 
49
- def self.run_event_machine
50
- return if EM.reactor_running?
46
+ def self.ensure_event_machine_is_running
47
+ run_event_machine unless EM.reactor_running?
48
+ end
51
49
 
50
+ def self.run_event_machine
52
51
  @event_machine_thread = Thread.new do
53
52
  EM.run do
54
53
  QueueingRabbit.trigger_event(:event_machine_started)
55
54
  end
56
55
  end
57
56
 
58
- # Block the control process while EM is starting up
59
- sleep 0.5
57
+ wait_for_event_machine_to_start
58
+ end
59
+
60
+ def self.wait_for_event_machine_to_start
61
+ Timeout.timeout(5) do
62
+ sleep 0.5 until EM.reactor_running?
63
+ end
64
+ rescue Timeout::Error => e
65
+ description = "wait timeout exceeded while starting up EventMachine"
66
+ fatal description
67
+ raise QueueingRabbitError.new(description)
60
68
  end
61
69
 
62
70
  def self.join_event_machine_thread
@@ -74,54 +82,54 @@ module QueueingRabbit
74
82
  end
75
83
 
76
84
  def define_queue(channel, queue_name, options={})
77
- routing_keys = [*options.delete(:routing_keys)] + [queue_name]
78
-
79
85
  channel.queue(queue_name.to_s, options) do |queue|
80
- routing_keys.each do |key|
81
- queue.bind(exchange(channel), :routing_key => key.to_s)
82
- end
86
+ yield queue if block_given?
83
87
  end
84
88
  end
85
89
 
86
- def listen_queue(channel, queue_name, options={}, &block)
87
- define_queue(channel, queue_name, options).
88
- subscribe(:ack => true) do |metadata, payload|
89
- begin
90
- process_message(deserialize(payload), &block)
91
- metadata.ack
92
- rescue JSON::JSONError => e
93
- error "JSON parser error occured: #{e.message}"
94
- debug e
95
- end
90
+ def bind_queue(queue, exchange, options = {})
91
+ queue.bind(exchange, options)
92
+ end
93
+
94
+ def listen_queue(queue, options = {}, &block)
95
+ queue.subscribe(options) do |metadata, payload|
96
+ process_message(payload, metadata, &block)
96
97
  end
97
98
  end
98
99
 
99
- def process_message(arguments)
100
+ def process_message(payload, metadata)
100
101
  begin
101
- yield arguments
102
+ yield payload, metadata
102
103
  rescue => e
103
104
  error "unexpected error #{e.class} occured: #{e.message}"
104
105
  debug e
105
106
  end
106
107
  end
107
108
 
108
- def open_channel(options={})
109
- ::AMQP::Channel.new(connection,
110
- ::AMQP::Channel.next_channel_id,
111
- options) do |c, open_ok|
109
+ def open_channel(options = {})
110
+ channel_id = ::AMQP::Channel.next_channel_id
111
+ ::AMQP::Channel.new(connection, channel_id, options) do |c, open_ok|
112
+ c.confirm_select if !!options[:use_publisher_confirms]
112
113
  c.on_error(&self.class.callback(:on_channel_error))
113
114
  yield c, open_ok
114
115
  end
115
116
  end
116
117
 
117
- def define_exchange(channel, options={})
118
- channel.direct(exchange_name, exchange_options.merge(options))
118
+ def define_exchange(channel, name = '', options = {})
119
+ type = options.delete(:type)
120
+ with_exchange = Proc.new do |exchange, _|
121
+ yield exchange if block_given?
122
+ end
123
+
124
+ if type && type != :default
125
+ channel.send(type.to_sym, name, options, &with_exchange)
126
+ else
127
+ channel.default_exchange.tap(&with_exchange)
128
+ end
119
129
  end
120
- alias_method :exchange, :define_exchange
121
130
 
122
- def enqueue(channel, routing_key, payload)
123
- exchange(channel).publish(serialize(payload), :key => routing_key.to_s,
124
- :persistent => true)
131
+ def enqueue(exchange, payload, options = {})
132
+ exchange.publish(payload, options)
125
133
  end
126
134
  alias_method :publish, :enqueue
127
135
 
@@ -136,10 +144,8 @@ module QueueingRabbit
136
144
  connection.on_recovery(&self.class.callback(:on_tcp_recovery))
137
145
  end
138
146
 
139
- def initialize(connection, exchange_name, exchange_options = {})
147
+ def initialize(connection)
140
148
  @connection = connection
141
- @exchange_name = exchange_name
142
- @exchange_options = exchange_options
143
149
 
144
150
  setup_callbacks
145
151
  end
@@ -6,44 +6,44 @@ module QueueingRabbit
6
6
 
7
7
  class Bunny
8
8
 
9
- include QueueingRabbit::Serializer
10
-
11
- attr_reader :connection, :exchange_name, :exchange_options
9
+ attr_reader :connection
12
10
 
13
11
  def self.connect
14
- self.new(::Bunny.new(QueueingRabbit.amqp_uri),
15
- QueueingRabbit.amqp_exchange_name,
16
- QueueingRabbit.amqp_exchange_options)
12
+ self.new(::Bunny.new(QueueingRabbit.amqp_uri))
17
13
  end
18
14
 
19
15
  def open_channel(options = {})
20
16
  ch = connection.create_channel
17
+ ch.confirm_select if !!options[:use_publisher_confirms]
21
18
  yield ch, nil
22
19
  ch
23
20
  end
24
21
 
25
22
  def define_queue(channel, name, options = {})
26
- routing_keys = ([*options.delete(:routing_keys)] + [name]).compact
27
-
28
23
  queue = channel.queue(name.to_s, options)
29
-
30
- routing_keys.each do |key|
31
- queue.bind(exchange(channel), :routing_key => key.to_s)
32
- end
33
-
24
+ yield queue if block_given?
34
25
  queue
35
26
  end
36
27
 
37
- def enqueue(channel, routing_key, payload)
38
- exchange(channel).publish(serialize(payload), :key => routing_key.to_s,
39
- :persistent => true)
28
+ def bind_queue(queue, exchange, options = {})
29
+ queue.bind(exchange, options)
30
+ end
31
+
32
+ def enqueue(exchange, payload, options = {})
33
+ exchange.publish(payload, options)
40
34
  end
41
35
  alias_method :publish, :enqueue
42
36
 
43
- def define_exchange(channel, options={})
44
- channel.direct(exchange_name, exchange_options.merge(options))
37
+ def define_exchange(channel = nil, name = '', options = {})
38
+ type = options.delete(:type)
39
+
40
+ exchange = type ? channel.send(type.to_sym, name, options) :
41
+ channel.default_exchange
42
+
43
+ yield exchange if block_given?
44
+
45
+ exchange
45
46
  end
46
- alias_method :exchange, :define_exchange
47
47
 
48
48
  def queue_size(queue)
49
49
  queue.status[:message_count]
@@ -51,11 +51,8 @@ module QueueingRabbit
51
51
 
52
52
  private
53
53
 
54
- def initialize(connection, exchange_name, exchange_options)
54
+ def initialize(connection)
55
55
  @connection = connection
56
- @exchange_name = exchange_name
57
- @exchange_options = exchange_options
58
-
59
56
  @connection.start
60
57
  end
61
58
 
@@ -0,0 +1,26 @@
1
+ module QueueingRabbit
2
+
3
+ module JobExtensions
4
+
5
+ module DirectExchange
6
+
7
+ def self.included(klass)
8
+ klass.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def exchange_options
13
+ @exchange_options ||= {}
14
+ @exchange_options.update(:type => :direct)
15
+ end
16
+
17
+ def binding_options
18
+ @binding_options || {:routing_key => queue_name.to_s}
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -1,18 +1,33 @@
1
1
  module QueueingRabbit
2
2
 
3
- module Extensions
3
+ module JobExtensions
4
4
 
5
5
  module NewRelic
6
6
 
7
- def self.included(mod)
8
- mod.class_eval do |klass|
9
- class << klass
7
+ def self.included(klass)
8
+ if klass.respond_to?(:perform)
9
+ add_for_class_method(klass)
10
+ else
11
+ add_for_instance_method(klass)
12
+ end
13
+ end
14
+
15
+ def self.add_for_class_method(klass)
16
+ klass.class_eval do |k|
17
+ class << k
10
18
  include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
11
19
  add_transaction_tracer :perform, :category => :task
12
20
  end
13
21
  end
14
22
  end
15
23
 
24
+ def self.add_for_instance_method(klass)
25
+ klass.class_eval do |k|
26
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
27
+ add_transaction_tracer :perform, :category => :task
28
+ end
29
+ end
30
+
16
31
  end
17
32
 
18
33
  end
@@ -0,0 +1,22 @@
1
+ module QueueingRabbit
2
+
3
+ module JobExtensions
4
+
5
+ module Retryable
6
+
7
+ def retries
8
+ headers['qr_retries'].to_i
9
+ end
10
+
11
+ def retry_upto(max_retries)
12
+ if retries < max_retries
13
+ updated_headers = headers.update('qr_retries' => retries + 1)
14
+ self.class.enqueue(payload, :headers => updated_headers)
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -1,5 +1,15 @@
1
1
  module QueueingRabbit
2
+
2
3
  module Job
4
+
5
+ def queue(*args)
6
+ if args.first.kind_of?(Hash)
7
+ @queue_options = args.first
8
+ else
9
+ @queue_name, @queue_options = args
10
+ end
11
+ end
12
+
3
13
  def queue_name
4
14
  @queue_name ||= self.name.split('::')[-1]
5
15
  end
@@ -8,25 +18,60 @@ module QueueingRabbit
8
18
  @queue_options ||= {}
9
19
  end
10
20
 
11
- def queue(name, options = {})
12
- @queue_name = name
13
- @queue_options = options
14
- end
15
-
16
21
  def queue_size
17
22
  QueueingRabbit.queue_size(self)
18
23
  end
19
24
 
25
+ def channel(options={})
26
+ @channel_options = options
27
+ end
28
+
20
29
  def channel_options
21
30
  @channel_options ||= {}
22
31
  end
23
32
 
24
- def channel(options={})
25
- @channel_options = options
33
+ def exchange(name, options = {})
34
+ @exchange_name = name
35
+ @exchange_options = options
26
36
  end
27
- end
28
37
 
29
- class AbstractJob
30
- extend Job
38
+ def exchange_name
39
+ @exchange_name
40
+ end
41
+
42
+ def exchange_options
43
+ @exchange_options ||= {}
44
+ end
45
+
46
+ def bind(options = {})
47
+ @binding_options = options
48
+ end
49
+
50
+ def binding_options
51
+ @binding_options || nil
52
+ end
53
+
54
+ def bind_queue?
55
+ exchange_options[:type] && exchange_options[:type] != :default && binding_options
56
+ end
57
+
58
+ def listening_options
59
+ @listening_options || {}
60
+ end
61
+
62
+ def listen(options = {})
63
+ @listening_options = options
64
+ end
65
+
66
+ def publishing_defaults(options = {})
67
+ @publishing_defaults ||= options.merge(:routing_key => queue_name.to_s)
68
+ end
69
+
70
+ def enqueue(payload, options = {})
71
+ QueueingRabbit.enqueue(self, payload, publishing_defaults.merge(options))
72
+ end
73
+ alias_method :publish, :enqueue
74
+
31
75
  end
32
- end
76
+
77
+ end
@@ -0,0 +1,27 @@
1
+ module QueueingRabbit
2
+
3
+ class AbstractJob
4
+
5
+ extend Job
6
+
7
+ attr_reader :payload, :metadata
8
+
9
+ def initialize(payload, metadata)
10
+ @payload = payload
11
+ @metadata = metadata
12
+ end
13
+
14
+ def acknowledge
15
+ metadata.ack
16
+ end
17
+
18
+ def headers
19
+ metadata.headers || {}
20
+ end
21
+
22
+ def perform
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,19 @@
1
+ module QueueingRabbit
2
+
3
+ class JSONJob < AbstractJob
4
+
5
+ extend QueueingRabbit::Serializer
6
+
7
+ alias_method :arguments, :payload
8
+
9
+ def self.enqueue(payload, metadata = {})
10
+ super serialize(payload), metadata
11
+ end
12
+
13
+ def initialize(payload, metadata = {})
14
+ super self.class.deserialize(payload), metadata
15
+ end
16
+
17
+ end
18
+
19
+ end