action_subscriber 3.0.2-java → 4.0.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +41 -7
- data/action_subscriber.gemspec +0 -1
- data/bin/action_subscriber +7 -15
- data/lib/action_subscriber.rb +10 -49
- data/lib/action_subscriber/babou.rb +7 -43
- data/lib/action_subscriber/base.rb +0 -70
- data/lib/action_subscriber/bunny/subscriber.rb +43 -41
- data/lib/action_subscriber/configuration.rb +1 -7
- data/lib/action_subscriber/dsl.rb +95 -3
- data/lib/action_subscriber/march_hare/subscriber.rb +41 -51
- data/lib/action_subscriber/route.rb +0 -1
- data/lib/action_subscriber/route_set.rb +5 -19
- data/lib/action_subscriber/version.rb +1 -1
- data/spec/integration/around_filters_spec.rb +1 -1
- data/spec/integration/at_least_once_spec.rb +1 -1
- data/spec/integration/at_most_once_spec.rb +45 -9
- data/spec/integration/automatic_reconnect_spec.rb +28 -28
- data/spec/integration/basic_subscriber_spec.rb +5 -18
- data/spec/integration/custom_actions_spec.rb +1 -1
- data/spec/integration/custom_headers_spec.rb +3 -11
- data/spec/integration/decoding_payloads_spec.rb +2 -2
- data/spec/integration/manual_acknowledgement_spec.rb +1 -1
- data/spec/integration/multiple_connections_spec.rb +1 -1
- data/spec/lib/action_subscriber/configuration_spec.rb +0 -3
- data/spec/spec_helper.rb +2 -2
- metadata +3 -22
- data/lib/action_subscriber/threadpool.rb +0 -62
- data/spec/integration/multiple_threadpools_spec.rb +0 -29
- data/spec/lib/action_subscriber/threadpool_spec.rb +0 -47
@@ -10,16 +10,13 @@ module ActionSubscriber
|
|
10
10
|
:heartbeat,
|
11
11
|
:host,
|
12
12
|
:hosts,
|
13
|
-
:mode,
|
14
13
|
:password,
|
15
|
-
:pop_interval,
|
16
14
|
:port,
|
17
15
|
:prefetch,
|
18
16
|
:seconds_to_wait_for_graceful_shutdown,
|
19
17
|
:username,
|
20
18
|
:threadpool_size,
|
21
19
|
:timeout,
|
22
|
-
:times_to_pop,
|
23
20
|
:virtual_host
|
24
21
|
|
25
22
|
CONFIGURATION_MUTEX = ::Mutex.new
|
@@ -30,14 +27,11 @@ module ActionSubscriber
|
|
30
27
|
:heartbeat => 5,
|
31
28
|
:host => 'localhost',
|
32
29
|
:hosts => [],
|
33
|
-
:mode => 'subscribe',
|
34
|
-
:pop_interval => 100, # in milliseconds
|
35
30
|
:port => 5672,
|
36
31
|
:prefetch => 2,
|
37
32
|
:seconds_to_wait_for_graceful_shutdown => 30,
|
38
33
|
:threadpool_size => 8,
|
39
34
|
:timeout => 1,
|
40
|
-
:times_to_pop => 8,
|
41
35
|
:username => "guest",
|
42
36
|
:password => "guest",
|
43
37
|
:virtual_host => "/"
|
@@ -115,7 +109,7 @@ module ActionSubscriber
|
|
115
109
|
|
116
110
|
def inspect
|
117
111
|
inspection_string = <<-INSPECT.strip_heredoc
|
118
|
-
Rabbit
|
112
|
+
Rabbit Hosts: #{hosts}
|
119
113
|
Rabbit Port: #{port}
|
120
114
|
Threadpool Size: #{threadpool_size}
|
121
115
|
Low Priority Subscriber: #{allow_low_priority_methods}
|
@@ -2,12 +2,20 @@ module ActionSubscriber
|
|
2
2
|
module DSL
|
3
3
|
def at_least_once!
|
4
4
|
@_acknowledge_messages = true
|
5
|
-
|
5
|
+
@_at_least_once = true
|
6
|
+
end
|
7
|
+
|
8
|
+
def at_least_once?
|
9
|
+
!!@_at_least_once
|
6
10
|
end
|
7
11
|
|
8
12
|
def at_most_once!
|
9
13
|
@_acknowledge_messages = true
|
10
|
-
|
14
|
+
@_at_most_once = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def at_most_once?
|
18
|
+
!!@_at_most_once
|
11
19
|
end
|
12
20
|
|
13
21
|
def acknowledge_messages?
|
@@ -38,6 +46,11 @@ module ActionSubscriber
|
|
38
46
|
|
39
47
|
def manual_acknowledgement!
|
40
48
|
@_acknowledge_messages = true
|
49
|
+
@_manual_acknowedgement = true
|
50
|
+
end
|
51
|
+
|
52
|
+
def manual_acknowledgement?
|
53
|
+
!!@_manual_acknowedgement
|
41
54
|
end
|
42
55
|
|
43
56
|
def no_acknowledgement!
|
@@ -76,7 +89,7 @@ module ActionSubscriber
|
|
76
89
|
@_routing_key_names ||= {}
|
77
90
|
end
|
78
91
|
|
79
|
-
def
|
92
|
+
def _run_action_with_filters(env, action)
|
80
93
|
subscriber_instance = self.new(env)
|
81
94
|
final_block = Proc.new { subscriber_instance.public_send(action) }
|
82
95
|
|
@@ -85,5 +98,84 @@ module ActionSubscriber
|
|
85
98
|
end
|
86
99
|
first_proc.call
|
87
100
|
end
|
101
|
+
|
102
|
+
def _run_action_at_most_once_with_filters(env, action)
|
103
|
+
processed_acknowledgement = false
|
104
|
+
rejected_message = false
|
105
|
+
processed_acknowledgement = env.acknowledge
|
106
|
+
|
107
|
+
_run_action_with_filters(env, action)
|
108
|
+
ensure
|
109
|
+
rejected_message = env.reject if !processed_acknowledgement
|
110
|
+
|
111
|
+
if !rejected_message && !processed_acknowledgement
|
112
|
+
$stdout << <<-UNREJECTABLE
|
113
|
+
CANNOT ACKNOWLEDGE OR REJECT THE MESSAGE
|
114
|
+
|
115
|
+
This is a exceptional state for ActionSubscriber to enter and puts the current
|
116
|
+
Process in the position of "I can't get new work from RabbitMQ, but also
|
117
|
+
can't acknowledge or reject the work that I currently have" ... While rare
|
118
|
+
this state can happen.
|
119
|
+
|
120
|
+
Instead of continuing to try to process the message ActionSubscriber is
|
121
|
+
sending a Kill signal to the current running process to gracefully shutdown
|
122
|
+
so that the RabbitMQ server will purge any outstanding acknowledgements. If
|
123
|
+
you are running a process monitoring tool (like Upstart) the Subscriber
|
124
|
+
process will be restarted and be able to take on new work.
|
125
|
+
|
126
|
+
** Running a process monitoring tool like Upstart is recommended for this reason **
|
127
|
+
UNREJECTABLE
|
128
|
+
|
129
|
+
Process.kill(:TERM, Process.pid)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def _run_action_at_least_once_with_filters(env, action)
|
134
|
+
processed_acknowledgement = false
|
135
|
+
rejected_message = false
|
136
|
+
|
137
|
+
_run_action_with_filters(env, action)
|
138
|
+
|
139
|
+
processed_acknowledgement = env.acknowledge
|
140
|
+
rescue
|
141
|
+
::ActionSubscriber::MessageRetry.redeliver_message_with_backoff(env)
|
142
|
+
processed_acknowledgement = env.acknowledge
|
143
|
+
|
144
|
+
raise
|
145
|
+
ensure
|
146
|
+
rejected_message = env.reject if !processed_acknowledgement
|
147
|
+
|
148
|
+
if !rejected_message && !processed_acknowledgement
|
149
|
+
$stdout << <<-UNREJECTABLE
|
150
|
+
CANNOT ACKNOWLEDGE OR REJECT THE MESSAGE
|
151
|
+
|
152
|
+
This is a exceptional state for ActionSubscriber to enter and puts the current
|
153
|
+
Process in the position of "I can't get new work from RabbitMQ, but also
|
154
|
+
can't acknowledge or reject the work that I currently have" ... While rare
|
155
|
+
this state can happen.
|
156
|
+
|
157
|
+
Instead of continuing to try to process the message ActionSubscriber is
|
158
|
+
sending a Kill signal to the current running process to gracefully shutdown
|
159
|
+
so that the RabbitMQ server will purge any outstanding acknowledgements. If
|
160
|
+
you are running a process monitoring tool (like Upstart) the Subscriber
|
161
|
+
process will be restarted and be able to take on new work.
|
162
|
+
|
163
|
+
** Running a process monitoring tool like Upstart is recommended for this reason **
|
164
|
+
UNREJECTABLE
|
165
|
+
|
166
|
+
Process.kill(:TERM, Process.pid)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def run_action_with_filters(env, action)
|
171
|
+
case
|
172
|
+
when at_least_once?
|
173
|
+
_run_action_at_least_once_with_filters(env, action)
|
174
|
+
when at_most_once?
|
175
|
+
_run_action_at_most_once_with_filters(env, action)
|
176
|
+
else
|
177
|
+
_run_action_with_filters(env, action)
|
178
|
+
end
|
179
|
+
end
|
88
180
|
end
|
89
181
|
end
|
@@ -7,46 +7,48 @@ module ActionSubscriber
|
|
7
7
|
march_hare_consumers.each(&:cancel)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
12
|
-
queue.declare!
|
13
|
-
queue
|
10
|
+
def march_hare_consumers
|
11
|
+
@march_hare_consumers ||= []
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
route
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
def print_subscriptions
|
15
|
+
routes.group_by(&:subscriber).each do |subscriber, routes|
|
16
|
+
logger.info subscriber.name
|
17
|
+
routes.each do |route|
|
18
|
+
executor = ::ActionSubscriber::RabbitConnection.connection_threadpools[route.connection_name]
|
19
|
+
logger.info " -- method: #{route.action}"
|
20
|
+
logger.info " -- connection: #{route.connection_name} (#{executor.get_maximum_pool_size} threads)"
|
21
|
+
logger.info " -- concurrency: #{route.concurrency}"
|
22
|
+
logger.info " -- exchange: #{route.exchange}"
|
23
|
+
logger.info " -- queue: #{route.queue}"
|
24
|
+
logger.info " -- routing_key: #{route.routing_key}"
|
25
|
+
logger.info " -- prefetch: #{route.prefetch} per consumer (#{route.prefetch * route.concurrency} total)"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
def print_threadpool_stats
|
31
|
+
::ActionSubscriber::RabbitConnection.connection_threadpools.each do |name, executor|
|
32
|
+
logger.info "Connection #{name}"
|
33
|
+
logger.info " -- available threads: #{executor.get_maximum_pool_size}"
|
34
|
+
logger.info " -- running thread: #{executor.get_active_count}"
|
35
|
+
logger.info " -- backlog: #{executor.get_queue.size}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_subscriptions!
|
40
|
+
fail ::RuntimeError, "you cannot setup queues multiple times, this should only happen once at startup" unless subscriptions.empty?
|
41
|
+
routes.each do |route|
|
42
|
+
route.concurrency.times do
|
43
|
+
subscriptions << {
|
44
|
+
:route => route,
|
45
|
+
:queue => setup_queue(route),
|
39
46
|
}
|
40
|
-
env = ::ActionSubscriber::Middleware::Env.new(route.subscriber, encoded_payload, properties)
|
41
|
-
enqueue_env(route.threadpool, env)
|
42
47
|
end
|
43
48
|
end
|
44
|
-
|
45
|
-
rescue ::MarchHare::ChannelAlreadyClosed => e
|
46
|
-
# The connection has gone down, we can just try again on the next pop
|
47
49
|
end
|
48
50
|
|
49
|
-
def
|
51
|
+
def start_subscribers!
|
50
52
|
subscriptions.each do |subscription|
|
51
53
|
route = subscription[:route]
|
52
54
|
queue = subscription[:queue]
|
@@ -72,10 +74,6 @@ module ActionSubscriber
|
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
75
|
-
def march_hare_consumers
|
76
|
-
@march_hare_consumers ||= []
|
77
|
-
end
|
78
|
-
|
79
77
|
def wait_to_finish_with_timeout(timeout)
|
80
78
|
wait_loops = 0
|
81
79
|
loop do
|
@@ -83,7 +81,7 @@ module ActionSubscriber
|
|
83
81
|
any_threadpools_busy = false
|
84
82
|
::ActionSubscriber::RabbitConnection.connection_threadpools.each do |name, executor|
|
85
83
|
next if executor.get_active_count <= 0
|
86
|
-
puts " -- Connection #{name} (
|
84
|
+
puts " -- Connection #{name} (active: #{executor.get_active_count}, queued: #{executor.get_queue.size})"
|
87
85
|
any_threadpools_busy = true
|
88
86
|
end
|
89
87
|
if !any_threadpools_busy
|
@@ -98,20 +96,12 @@ module ActionSubscriber
|
|
98
96
|
|
99
97
|
private
|
100
98
|
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def run_env(env)
|
111
|
-
logger.info "RECEIVED #{env.message_id} from #{env.queue}"
|
112
|
-
::ActiveSupport::Notifications.instrument "process_event.action_subscriber", :subscriber => env.subscriber.to_s, :routing_key => env.routing_key, :queue => env.queue do
|
113
|
-
::ActionSubscriber.config.middleware.call(env)
|
114
|
-
end
|
99
|
+
def setup_queue(route)
|
100
|
+
channel = ::ActionSubscriber::RabbitConnection.with_connection(route.connection_name){ |connection| connection.create_channel }
|
101
|
+
exchange = channel.topic(route.exchange)
|
102
|
+
queue = channel.queue(route.queue, :durable => route.durable)
|
103
|
+
queue.bind(exchange, :routing_key => route.routing_key)
|
104
|
+
queue
|
115
105
|
end
|
116
106
|
|
117
107
|
def _normalized_headers(metadata)
|
@@ -23,7 +23,6 @@ module ActionSubscriber
|
|
23
23
|
@queue = attributes.fetch(:queue)
|
24
24
|
@routing_key = attributes.fetch(:routing_key)
|
25
25
|
@subscriber = attributes.fetch(:subscriber)
|
26
|
-
@threadpool = attributes.fetch(:threadpool) { ::ActionSubscriber::Threadpool.pool(:default) }
|
27
26
|
end
|
28
27
|
|
29
28
|
def acknowledgements?
|
@@ -12,31 +12,17 @@ module ActionSubscriber
|
|
12
12
|
@routes = routes
|
13
13
|
end
|
14
14
|
|
15
|
-
def setup_subscriptions!
|
16
|
-
fail ::RuntimeError, "you cannot setup queues multiple times, this should only happen once at startup" unless subscriptions.empty?
|
17
|
-
routes.each do |route|
|
18
|
-
route.concurrency.times do
|
19
|
-
subscriptions << {
|
20
|
-
:route => route,
|
21
|
-
:queue => setup_queue(route),
|
22
|
-
}
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
15
|
private
|
28
16
|
|
29
17
|
def subscriptions
|
30
18
|
@subscriptions ||= []
|
31
19
|
end
|
32
20
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
queue.bind(exchange, :routing_key => route.routing_key)
|
39
|
-
queue
|
21
|
+
def run_env(env)
|
22
|
+
logger.info "RECEIVED #{env.message_id} from #{env.queue}"
|
23
|
+
::ActiveSupport::Notifications.instrument "process_event.action_subscriber", :subscriber => env.subscriber.to_s, :routing_key => env.routing_key, :queue => env.queue do
|
24
|
+
::ActionSubscriber.config.middleware.call(env)
|
25
|
+
end
|
40
26
|
end
|
41
27
|
end
|
42
28
|
end
|
@@ -31,7 +31,7 @@ describe "subscriber filters", :integration => true do
|
|
31
31
|
|
32
32
|
it "runs multiple around filters" do
|
33
33
|
$messages = [] #testing the order of things
|
34
|
-
::ActionSubscriber.
|
34
|
+
::ActionSubscriber.start_subscribers!
|
35
35
|
::ActivePublisher.publish("insta.first", "hEY Guyz!", "events")
|
36
36
|
|
37
37
|
verify_expectation_within(1.0) do
|
@@ -16,7 +16,7 @@ describe "at_least_once! mode", :integration => true do
|
|
16
16
|
let(:subscriber) { GorbyPuffSubscriber }
|
17
17
|
|
18
18
|
it "retries a failed job until it succeeds" do
|
19
|
-
::ActionSubscriber.
|
19
|
+
::ActionSubscriber.start_subscribers!
|
20
20
|
::ActivePublisher.publish("gorby_puff.grumpy", "GrumpFace", "events")
|
21
21
|
|
22
22
|
verify_expectation_within(2.0) do
|
@@ -7,20 +7,56 @@ class PokemonSubscriber < ActionSubscriber::Base
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
class PokemonWithAroundSubscriber < ActionSubscriber::Base
|
11
|
+
around_filter :catch_you_first
|
12
|
+
at_most_once!
|
13
|
+
|
14
|
+
def caught_em_all
|
15
|
+
raise RuntimeError.new("what do I do now?")
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def catch_you_first
|
21
|
+
$messages << "DONE::#{$messages.size}"
|
22
|
+
raise RuntimeError.new("caught you first")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
10
26
|
describe "at_most_once! mode", :integration => true do
|
11
|
-
|
12
|
-
|
13
|
-
|
27
|
+
context "without overriding around_filter" do
|
28
|
+
let(:draw_routes) do
|
29
|
+
::ActionSubscriber.draw_routes do
|
30
|
+
default_routes_for PokemonSubscriber
|
31
|
+
end
|
32
|
+
end
|
33
|
+
let(:subscriber) { PokemonSubscriber }
|
34
|
+
|
35
|
+
it "does not retry a failed message" do
|
36
|
+
::ActionSubscriber.start_subscribers!
|
37
|
+
::ActivePublisher.publish("pokemon.caught_em_all", "All Pokemon have been caught", "events")
|
38
|
+
|
39
|
+
verify_expectation_within(1.0) do
|
40
|
+
expect($messages.size).to eq 1
|
41
|
+
end
|
14
42
|
end
|
15
43
|
end
|
16
|
-
let(:subscriber) { PokemonSubscriber }
|
17
44
|
|
18
|
-
|
19
|
-
|
20
|
-
|
45
|
+
context "with overriding around_filter" do
|
46
|
+
let(:draw_routes) do
|
47
|
+
::ActionSubscriber.draw_routes do
|
48
|
+
default_routes_for PokemonWithAroundSubscriber
|
49
|
+
end
|
50
|
+
end
|
51
|
+
let(:subscriber) { PokemonWithAroundSubscriber }
|
52
|
+
|
53
|
+
it "does not retry a failed message" do
|
54
|
+
::ActionSubscriber.start_subscribers!
|
55
|
+
::ActivePublisher.publish("pokemon_with_around.caught_em_all", "All Pokemon have been caught", "events")
|
21
56
|
|
22
|
-
|
23
|
-
|
57
|
+
verify_expectation_within(1.0) do
|
58
|
+
expect($messages.size).to eq 1
|
59
|
+
end
|
24
60
|
end
|
25
61
|
end
|
26
62
|
end
|
@@ -7,36 +7,36 @@ class GusSubscriber < ActionSubscriber::Base
|
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "Automatically reconnect on connection failure", :integration => true, :slow => true do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
let(:draw_routes) do
|
11
|
+
::ActionSubscriber.draw_routes do
|
12
|
+
default_routes_for GusSubscriber
|
13
|
+
end
|
14
|
+
end
|
15
|
+
let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") }
|
16
|
+
let(:subscriber) { GusSubscriber }
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
it "reconnects when a connection drops" do
|
19
|
+
::ActionSubscriber::start_subscribers!
|
20
|
+
::ActivePublisher.publish("gus.spoke", "First", "events")
|
21
|
+
verify_expectation_within(5.0) do
|
22
|
+
expect($messages).to eq(Set.new(["First"]))
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
close_all_connections!
|
26
|
+
sleep 5.0
|
27
|
+
verify_expectation_within(5.0) do
|
28
|
+
expect(::ActionSubscriber::RabbitConnection.with_connection(:default){|connection| connection.open?}).to eq(true)
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
::ActivePublisher.publish("gus.spoke", "Second", "events")
|
32
|
+
verify_expectation_within(5.0) do
|
33
|
+
expect($messages).to eq(Set.new(["First", "Second"]))
|
34
|
+
end
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
def close_all_connections!
|
38
|
+
http_client.list_connections.each do |conn_info|
|
39
|
+
http_client.close_connection(conn_info.name)
|
40
|
+
end
|
41
|
+
end
|
42
42
|
end
|