queueing_rabbit 0.1.3 → 0.2.0

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