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.
- data/lib/queueing_rabbit/client/amqp.rb +46 -40
- data/lib/queueing_rabbit/client/bunny.rb +20 -23
- data/lib/queueing_rabbit/extensions/direct_exchange.rb +26 -0
- data/lib/queueing_rabbit/extensions/new_relic.rb +19 -4
- data/lib/queueing_rabbit/extensions/retryable.rb +22 -0
- data/lib/queueing_rabbit/job.rb +56 -11
- data/lib/queueing_rabbit/jobs/abstract_job.rb +27 -0
- data/lib/queueing_rabbit/jobs/json_job.rb +19 -0
- data/lib/queueing_rabbit/version.rb +1 -1
- data/lib/queueing_rabbit/worker.rb +14 -4
- data/lib/queueing_rabbit.rb +21 -6
- data/queueing_rabbit.gemspec +6 -17
- data/spec/integration/asynchronous_publishing_and_consuming_spec.rb +36 -16
- data/spec/integration/asynchronous_publishing_and_consuming_with_retries_spec.rb +103 -0
- data/spec/integration/direct_exchange_asynchronous_publishing_and_consuming_spec.rb +93 -0
- data/spec/integration/jobs/print_line_job.rb +5 -11
- data/spec/integration/json_job_asynchronous_publishing_and_consuming_spec.rb +99 -0
- data/spec/integration/persistent_asynchronous_publishing_and_consuming_spec.rb +96 -0
- data/spec/integration/synchronous_publishing_and_asynchronous_consuming_spec.rb +13 -17
- data/spec/integration/synchronous_publishing_spec.rb +6 -2
- data/spec/spec_helper.rb +4 -1
- data/spec/unit/queueing_rabbit/client/amqp_spec.rb +80 -51
- data/spec/unit/queueing_rabbit/client/bunny_spec.rb +53 -8
- data/spec/unit/queueing_rabbit/extensions/direct_exchange_spec.rb +20 -0
- data/spec/unit/queueing_rabbit/extensions/new_relic_spec.rb +36 -0
- data/spec/unit/queueing_rabbit/extensions/retryable_spec.rb +36 -0
- data/spec/unit/queueing_rabbit/job_spec.rb +76 -4
- data/spec/unit/queueing_rabbit/jobs/abstract_job_spec.rb +73 -0
- data/spec/unit/queueing_rabbit/jobs/json_job_spec.rb +22 -0
- data/spec/unit/queueing_rabbit/worker_spec.rb +33 -23
- data/spec/unit/queueing_rabbit_spec.rb +55 -25
- metadata +111 -90
- 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
|
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.
|
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.
|
50
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
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
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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(
|
100
|
+
def process_message(payload, metadata)
|
100
101
|
begin
|
101
|
-
yield
|
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.
|
110
|
-
|
111
|
-
|
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
|
-
|
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(
|
123
|
-
exchange
|
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
|
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
|
-
|
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
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
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
|
3
|
+
module JobExtensions
|
4
4
|
|
5
5
|
module NewRelic
|
6
6
|
|
7
|
-
def self.included(
|
8
|
-
|
9
|
-
|
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
|
data/lib/queueing_rabbit/job.rb
CHANGED
@@ -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
|
25
|
-
@
|
33
|
+
def exchange(name, options = {})
|
34
|
+
@exchange_name = name
|
35
|
+
@exchange_options = options
|
26
36
|
end
|
27
|
-
end
|
28
37
|
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|