action_subscriber 5.0.3 → 5.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -1
- data/action_subscriber.gemspec +1 -0
- data/lib/action_subscriber/bunny/subscriber.rb +1 -0
- data/lib/action_subscriber/march_hare/subscriber.rb +1 -0
- data/lib/action_subscriber/middleware/active_record/connection_management.rb +1 -1
- data/lib/action_subscriber/middleware/decoder.rb +1 -1
- data/lib/action_subscriber/middleware/env.rb +38 -2
- data/lib/action_subscriber/middleware/error_handler.rb +6 -4
- data/lib/action_subscriber/middleware/router.rb +21 -4
- data/lib/action_subscriber/middleware.rb +19 -3
- data/lib/action_subscriber/railtie.rb +2 -2
- data/lib/action_subscriber/route_set.rb +14 -4
- data/lib/action_subscriber/rspec.rb +5 -0
- data/lib/action_subscriber/version.rb +1 -1
- data/spec/integration/manual_acknowledgement_spec.rb +6 -4
- data/spec/lib/action_subscriber/middleware/error_handler_spec.rb +23 -5
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa70a7b7308a903e30cb492dc4c334cc76180984
|
4
|
+
data.tar.gz: 552c9aaae558ec3fae519c9fcde87dffbc8a6887
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2b5a313624613ae7de8e72dee4cd48b880effecfd7ffcd4472e4d79c4bf4e960e7db09b7574f6a285da8995494fc6618e7b59c1d43865b0fac68df7920ef867
|
7
|
+
data.tar.gz: f3af3c8278b4d886394560bdf404d5487a311635c2232b6e8ae3159cc0f3255c96ff14f60e2ba868c8697500e38cd0eb8e860aa7f9a39a6d832f8d9faba592fd
|
data/README.md
CHANGED
@@ -160,7 +160,7 @@ This also allows the broker to send messages as quickly as it wants down to your
|
|
160
160
|
|
161
161
|
### manual_acknowledgement!
|
162
162
|
|
163
|
-
This mode leaves it up to the subscriber to handle acknowledging or rejecting messages. In your subscriber you can just call <code>acknowledge</code> or <code>
|
163
|
+
This mode leaves it up to the subscriber to handle acknowledging or rejecting messages. In your subscriber you can just call <code>acknowledge</code>, <code>reject</code>, or <code>nack</code>.
|
164
164
|
|
165
165
|
### at_most_once!
|
166
166
|
|
@@ -172,6 +172,11 @@ Rabbit is told to expect message acknowledgements, but sending the acknowledgeme
|
|
172
172
|
We send the acknowledgement right after calling your subscriber.
|
173
173
|
If an error is raised your message will be retried on a sent back to rabbitmq and retried on an exponential backoff schedule.
|
174
174
|
|
175
|
+
### safe_nack
|
176
|
+
If you turn on acknowledgements and a message is not acknowledged by your code manually or using one of the filters above the `ErrorHandler` middleware
|
177
|
+
which wraps the entire block with call <code>nack</code> this is a last resort so the connection does not get backed up in cases of unexpected or
|
178
|
+
unhandled errors.
|
179
|
+
|
175
180
|
### redeliver
|
176
181
|
|
177
182
|
A message can be sent to "redeliver" with `::ActionSubscriber::MessageRetry.redeliver_message_with_backoff` or the DSL method `redeliver` and optionally
|
data/action_subscriber.gemspec
CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency "active_publisher", "~> 0.1.5"
|
34
34
|
spec.add_development_dependency "activerecord", ">= 3.2"
|
35
35
|
spec.add_development_dependency "bundler", ">= 1.6"
|
36
|
+
spec.add_development_dependency "pry-coolline"
|
36
37
|
spec.add_development_dependency "pry-nav"
|
37
38
|
spec.add_development_dependency "rabbitmq_http_api_client", "~> 1.2.0"
|
38
39
|
spec.add_development_dependency "rspec", "~> 3.0"
|
@@ -44,6 +44,7 @@ module ActionSubscriber
|
|
44
44
|
:message_id => properties.message_id,
|
45
45
|
:routing_key => delivery_info.routing_key,
|
46
46
|
:queue => queue.name,
|
47
|
+
:uses_acknowledgements => route.acknowledgements?,
|
47
48
|
}
|
48
49
|
env = ::ActionSubscriber::Middleware::Env.new(route.subscriber, encoded_payload, properties)
|
49
50
|
run_env(env, threadpool)
|
@@ -42,6 +42,7 @@ module ActionSubscriber
|
|
42
42
|
:message_id => metadata.message_id,
|
43
43
|
:routing_key => metadata.routing_key,
|
44
44
|
:queue => queue.name,
|
45
|
+
:uses_acknowledgements => route.acknowledgements?,
|
45
46
|
}
|
46
47
|
env = ::ActionSubscriber::Middleware::Env.new(route.subscriber, encoded_payload, properties)
|
47
48
|
run_env(env, threadpool)
|
@@ -33,27 +33,54 @@ module ActionSubscriber
|
|
33
33
|
@delivery_tag = properties.fetch(:delivery_tag)
|
34
34
|
@encoded_payload = encoded_payload
|
35
35
|
@exchange = properties.fetch(:exchange)
|
36
|
-
@
|
37
|
-
@
|
36
|
+
@has_been_acked = false
|
37
|
+
@has_been_nacked = false
|
38
|
+
@has_been_rejected = false
|
39
|
+
@headers = properties.fetch(:headers, {})
|
40
|
+
@message_id = properties.fetch(:message_id, ::SecureRandom.hex(3))
|
38
41
|
@queue = properties.fetch(:queue)
|
39
42
|
@routing_key = properties.fetch(:routing_key)
|
40
43
|
@subscriber = subscriber
|
44
|
+
@uses_acknowledgements = properties.fetch(:uses_acknowledgements, false)
|
41
45
|
end
|
42
46
|
|
43
47
|
def acknowledge
|
44
48
|
fail ::RuntimeError, "you can't acknowledge messages under the polling API" unless @channel
|
45
49
|
acknowledge_multiple_messages = false
|
50
|
+
@has_been_acked = true
|
46
51
|
@channel.ack(@delivery_tag, acknowledge_multiple_messages)
|
47
52
|
true
|
48
53
|
end
|
49
54
|
|
55
|
+
def nack
|
56
|
+
fail ::RuntimeError, "you can't acknowledge messages under the polling API" unless @channel
|
57
|
+
nack_multiple_messages = false
|
58
|
+
requeue_message = true
|
59
|
+
@has_been_nacked = true
|
60
|
+
@channel.nack(@delivery_tag, nack_multiple_messages, requeue_message)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
50
64
|
def reject
|
51
65
|
fail ::RuntimeError, "you can't acknowledge messages under the polling API" unless @channel
|
52
66
|
requeue_message = true
|
67
|
+
@has_been_rejected = true
|
53
68
|
@channel.reject(@delivery_tag, requeue_message)
|
54
69
|
true
|
55
70
|
end
|
56
71
|
|
72
|
+
def safe_acknowledge
|
73
|
+
acknowledge if uses_acknowledgements? && @channel && !has_used_delivery_tag?
|
74
|
+
end
|
75
|
+
|
76
|
+
def safe_nack
|
77
|
+
nack if uses_acknowledgements? && @channel && !has_used_delivery_tag?
|
78
|
+
end
|
79
|
+
|
80
|
+
def safe_reject
|
81
|
+
reject if uses_acknowledgements? && @channel && !has_used_delivery_tag?
|
82
|
+
end
|
83
|
+
|
57
84
|
def to_hash
|
58
85
|
{
|
59
86
|
:action => action,
|
@@ -65,6 +92,15 @@ module ActionSubscriber
|
|
65
92
|
end
|
66
93
|
alias_method :to_h, :to_hash
|
67
94
|
|
95
|
+
private
|
96
|
+
|
97
|
+
def has_used_delivery_tag?
|
98
|
+
@has_been_acked || @has_been_nacked || @has_been_rejected
|
99
|
+
end
|
100
|
+
|
101
|
+
def uses_acknowledgements?
|
102
|
+
@uses_acknowledgements
|
103
|
+
end
|
68
104
|
end
|
69
105
|
end
|
70
106
|
end
|
@@ -9,8 +9,8 @@ module ActionSubscriber
|
|
9
9
|
|
10
10
|
def call(env)
|
11
11
|
@app.call(env)
|
12
|
-
rescue => error
|
13
|
-
logger.error "FAILED #{env.message_id}"
|
12
|
+
rescue Exception => error # make sure we capture any exception from the top of the hierarchy
|
13
|
+
logger.error { "FAILED #{env.message_id}" }
|
14
14
|
|
15
15
|
# There is more to this rescue than meets the eye. MarchHare's java library will rescue errors
|
16
16
|
# and attempt to close the channel with its default exception handler. To avoid this, we will
|
@@ -18,9 +18,11 @@ module ActionSubscriber
|
|
18
18
|
# it should not re-raise. As a bonus, not killing these threads is better for your runtime :).
|
19
19
|
begin
|
20
20
|
::ActionSubscriber.configuration.error_handler.call(error, env.to_h)
|
21
|
-
rescue =>
|
22
|
-
logger.error "ActionSubscriber error handler raised error, but should never raise. Error: #{
|
21
|
+
rescue Exception => inner_error
|
22
|
+
logger.error { "ActionSubscriber error handler raised error, but should never raise. Error: #{inner_error}" }
|
23
23
|
end
|
24
|
+
ensure
|
25
|
+
env.safe_nack # Make sure we attempt to `nack` a message that did not get processed if something fails
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module ActionSubscriber
|
2
2
|
module Middleware
|
3
3
|
class Router
|
4
|
+
INSTRUMENT_KEY = "process_event.action_subscriber".freeze
|
4
5
|
include ::ActionSubscriber::Logging
|
5
6
|
|
6
7
|
def initialize(app)
|
@@ -8,11 +9,27 @@ module ActionSubscriber
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def call(env)
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
action = env.action
|
13
|
+
message_id = env.message_id
|
14
|
+
queue = env.queue
|
15
|
+
routing_key = env.routing_key
|
16
|
+
subscriber = env.subscriber
|
17
|
+
|
18
|
+
logger.info { "START #{message_id} #{subscriber}##{action}" }
|
19
|
+
|
20
|
+
instrument_call(subscriber, routing_key, queue) do
|
21
|
+
subscriber.run_action_with_filters(env, action)
|
22
|
+
end
|
23
|
+
|
24
|
+
logger.info { "FINISHED #{message_id}" }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def instrument_call(subscriber, routing_key, queue)
|
30
|
+
::ActiveSupport::Notifications.instrument INSTRUMENT_KEY, :subscriber => subscriber.to_s, :routing_key => routing_key, :queue => queue do
|
31
|
+
yield
|
14
32
|
end
|
15
|
-
logger.info { "FINISHED #{env.message_id}" }
|
16
33
|
end
|
17
34
|
end
|
18
35
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "action_subscriber/logging"
|
1
2
|
require "action_subscriber/middleware/decoder"
|
2
3
|
require "action_subscriber/middleware/env"
|
3
4
|
require "action_subscriber/middleware/error_handler"
|
@@ -6,11 +7,26 @@ require "action_subscriber/middleware/runner"
|
|
6
7
|
|
7
8
|
module ActionSubscriber
|
8
9
|
module Middleware
|
10
|
+
|
11
|
+
class Builder < ::Middleware::Builder
|
12
|
+
include ::ActionSubscriber::Logging
|
13
|
+
|
14
|
+
def print_middleware_stack
|
15
|
+
logger.info "Middlewares ["
|
16
|
+
|
17
|
+
stack.each do |middleware|
|
18
|
+
logger.info "#{middleware}"
|
19
|
+
end
|
20
|
+
|
21
|
+
logger.info "]"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
9
25
|
def self.initialize_stack
|
10
|
-
builder = ::Middleware::Builder.new(:runner_class => ::ActionSubscriber::Middleware::Runner)
|
26
|
+
builder = ::ActionSubscriber::Middleware::Builder.new(:runner_class => ::ActionSubscriber::Middleware::Runner)
|
11
27
|
|
12
|
-
builder.use ErrorHandler
|
13
|
-
builder.use Decoder
|
28
|
+
builder.use ::ActionSubscriber::Middleware::ErrorHandler
|
29
|
+
builder.use ::ActionSubscriber::Middleware::Decoder
|
14
30
|
|
15
31
|
builder
|
16
32
|
end
|
@@ -6,8 +6,8 @@ module ActionSubscriber
|
|
6
6
|
require "action_subscriber/middleware/active_record/connection_management"
|
7
7
|
require "action_subscriber/middleware/active_record/query_cache"
|
8
8
|
|
9
|
-
::ActionSubscriber.config.middleware.
|
10
|
-
::ActionSubscriber.config.middleware.
|
9
|
+
::ActionSubscriber.config.middleware.insert_after ::ActionSubscriber::Middleware::Decoder, ::ActionSubscriber::Middleware::ActiveRecord::ConnectionManagement
|
10
|
+
::ActionSubscriber.config.middleware.insert_after ::ActionSubscriber::Middleware::ActiveRecord::ConnectionManagement, ::ActionSubscriber::Middleware::ActiveRecord::QueryCache
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -12,7 +12,12 @@ module ActionSubscriber
|
|
12
12
|
@routes = routes
|
13
13
|
end
|
14
14
|
|
15
|
+
def print_middleware_stack
|
16
|
+
::ActionSubscriber.config.middleware.print_middleware_stack
|
17
|
+
end
|
18
|
+
|
15
19
|
def print_subscriptions
|
20
|
+
print_middleware_stack
|
16
21
|
routes.group_by(&:subscriber).each do |subscriber, routes|
|
17
22
|
logger.info subscriber.name
|
18
23
|
routes.each do |route|
|
@@ -39,15 +44,20 @@ module ActionSubscriber
|
|
39
44
|
end
|
40
45
|
|
41
46
|
def wait_to_finish_with_timeout(timeout)
|
47
|
+
finisher_threads = []
|
48
|
+
|
42
49
|
::ActionSubscriber::ThreadPools.threadpools.map do |name, threadpool|
|
43
50
|
logger.info " -- Threadpool #{name} (queued: #{threadpool.queue_length})"
|
44
|
-
::Thread.new do
|
45
|
-
completed =
|
51
|
+
finisher_threads << ::Thread.new(threadpool, timeout, name) do |internal_pool, internal_timeout, internal_name|
|
52
|
+
completed = internal_pool.wait_for_termination(internal_timeout)
|
53
|
+
|
46
54
|
unless completed
|
47
|
-
logger.error " -- FAILED #{
|
55
|
+
logger.error " -- FAILED #{internal_name} did not finish shutting down within #{internal_timeout}sec"
|
48
56
|
end
|
49
57
|
end
|
50
|
-
end
|
58
|
+
end
|
59
|
+
|
60
|
+
finisher_threads.each(&:join)
|
51
61
|
end
|
52
62
|
|
53
63
|
private
|
@@ -7,6 +7,10 @@ module ActionSubscriber
|
|
7
7
|
true
|
8
8
|
end
|
9
9
|
|
10
|
+
def nack(delivery_tag, acknowledge_multiple, requeue_message)
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
10
14
|
def reject(delivery_tag, requeue_message)
|
11
15
|
true
|
12
16
|
end
|
@@ -22,6 +26,7 @@ module ActionSubscriber
|
|
22
26
|
:message_id => "MSG-123",
|
23
27
|
:routing_key => "amigo.user.created",
|
24
28
|
:queue => "test.amigo.user.created",
|
29
|
+
:uses_acknoledgements => false,
|
25
30
|
}.freeze
|
26
31
|
|
27
32
|
# Create a new subscriber instance. Available options are:
|
@@ -3,8 +3,10 @@ class BaconSubscriber < ActionSubscriber::Base
|
|
3
3
|
|
4
4
|
def served
|
5
5
|
$messages << "#{payload}::#{$messages.size}"
|
6
|
-
if $messages.size >
|
6
|
+
if $messages.size > 3
|
7
7
|
acknowledge
|
8
|
+
elsif $messages.size > 2
|
9
|
+
nack
|
8
10
|
else
|
9
11
|
reject
|
10
12
|
end
|
@@ -20,12 +22,12 @@ describe "Manual Message Acknowledgment", :integration => true do
|
|
20
22
|
end
|
21
23
|
let(:subscriber) { BaconSubscriber }
|
22
24
|
|
23
|
-
it "retries rejected messages and stops retrying acknowledged messages" do
|
25
|
+
it "retries rejected/nacked messages and stops retrying acknowledged messages" do
|
24
26
|
::ActionSubscriber.start_subscribers!
|
25
27
|
::ActivePublisher.publish("bacon.served", "BACON!", "events")
|
26
28
|
|
27
|
-
verify_expectation_within(2.
|
28
|
-
expect($messages).to eq(Set.new(["BACON!::0", "BACON!::1", "BACON!::2"]))
|
29
|
+
verify_expectation_within(2.5) do
|
30
|
+
expect($messages).to eq(Set.new(["BACON!::0", "BACON!::1", "BACON!::2", "BACON!::3"]))
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
@@ -7,14 +7,32 @@ describe ActionSubscriber::Middleware::ErrorHandler do
|
|
7
7
|
|
8
8
|
it_behaves_like 'an action subscriber middleware'
|
9
9
|
|
10
|
-
let(:
|
10
|
+
let(:load_error) { ::LoadError.new("Boom!") }
|
11
|
+
let(:runtime_error) { ::RuntimeError.new("Boom!") }
|
11
12
|
|
12
13
|
context "when an exception occurs" do
|
13
|
-
|
14
|
+
context "LoadError" do
|
15
|
+
before { allow(app).to receive(:call).and_raise(load_error) }
|
16
|
+
it "calls the exception handler with a LoadError" do
|
17
|
+
handler = ::ActionSubscriber.configuration.error_handler
|
18
|
+
expect(handler).to receive(:call).with(load_error, env.to_h)
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
subject.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "RuntimError" do
|
25
|
+
before { allow(app).to receive(:call).and_raise(runtime_error) }
|
26
|
+
it "calls the exception handler with a RuntimeError" do
|
27
|
+
handler = ::ActionSubscriber.configuration.error_handler
|
28
|
+
expect(handler).to receive(:call).with(runtime_error, env.to_h)
|
29
|
+
|
30
|
+
subject.call(env)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "calls safe_nack after execution" do
|
35
|
+
expect(env).to receive(:safe_nack)
|
18
36
|
|
19
37
|
subject.call(env)
|
20
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_subscriber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.
|
4
|
+
version: 5.1.0.pre
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Stien
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2018-01-16 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activesupport
|
@@ -126,6 +126,20 @@ dependencies:
|
|
126
126
|
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
128
|
version: '1.6'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: pry-coolline
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
129
143
|
- !ruby/object:Gem::Dependency
|
130
144
|
name: pry-nav
|
131
145
|
requirement: !ruby/object:Gem::Requirement
|
@@ -278,12 +292,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
278
292
|
version: '0'
|
279
293
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
280
294
|
requirements:
|
281
|
-
- - "
|
295
|
+
- - ">"
|
282
296
|
- !ruby/object:Gem::Version
|
283
|
-
version:
|
297
|
+
version: 1.3.1
|
284
298
|
requirements: []
|
285
299
|
rubyforge_project:
|
286
|
-
rubygems_version: 2.
|
300
|
+
rubygems_version: 2.5.1
|
287
301
|
signing_key:
|
288
302
|
specification_version: 4
|
289
303
|
summary: ActionSubscriber is a DSL that allows a rails app to consume messages from
|