action_subscriber 5.0.3 → 5.1.0.pre
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 +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
|