action_subscriber 3.0.2-java → 4.0.0-java
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.
- 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
|