action_subscriber 2.0.1-java → 2.1.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/lib/action_subscriber.rb +6 -0
- data/lib/action_subscriber/configuration.rb +23 -2
- data/lib/action_subscriber/publisher/async.rb +31 -0
- data/lib/action_subscriber/publisher/async/in_memory_adapter.rb +153 -0
- data/lib/action_subscriber/rabbit_connection.rb +6 -2
- data/lib/action_subscriber/uri.rb +28 -0
- data/lib/action_subscriber/version.rb +1 -1
- data/spec/lib/action_subscriber/configuration_spec.rb +15 -0
- data/spec/lib/action_subscriber/publisher/async/in_memory_adapter_spec.rb +135 -0
- data/spec/lib/action_subscriber/publisher/async_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8a1a10bb43fef34f63038536e121540df818b14
|
4
|
+
data.tar.gz: 78e17fea12571a3c07037a7123f2e529ee1580a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1392baccf77a3b65c695fcb1ff5234a1ba2ef38ec83f4400f43b617be6659f0b5b31d6348e95b84e6f89bf5279643d10bcae072c4023c0d94531d8d28108e5a
|
7
|
+
data.tar.gz: c55665bfb5d487f800251d50f3737e46143092c539b81b041ffb49d5f50adf7a5a2aaade0dabc7fe3bf659f20f070b53103097958d32cfee766ffee64f76baa6
|
data/lib/action_subscriber.rb
CHANGED
@@ -23,11 +23,13 @@ require "action_subscriber/bunny/subscriber"
|
|
23
23
|
require "action_subscriber/march_hare/subscriber"
|
24
24
|
require "action_subscriber/babou"
|
25
25
|
require "action_subscriber/publisher"
|
26
|
+
require "action_subscriber/publisher/async"
|
26
27
|
require "action_subscriber/route"
|
27
28
|
require "action_subscriber/route_set"
|
28
29
|
require "action_subscriber/router"
|
29
30
|
require "action_subscriber/threadpool"
|
30
31
|
require "action_subscriber/base"
|
32
|
+
require "action_subscriber/uri"
|
31
33
|
|
32
34
|
module ActionSubscriber
|
33
35
|
##
|
@@ -110,6 +112,9 @@ module ActionSubscriber
|
|
110
112
|
# Initialize config object
|
111
113
|
config
|
112
114
|
|
115
|
+
# Intialize async publisher adapter
|
116
|
+
::ActionSubscriber::Publisher::Async.publisher_adapter
|
117
|
+
|
113
118
|
::ActiveSupport.run_load_hooks(:action_subscriber, Base)
|
114
119
|
|
115
120
|
##
|
@@ -135,5 +140,6 @@ end
|
|
135
140
|
require "action_subscriber/railtie" if defined?(Rails)
|
136
141
|
|
137
142
|
at_exit do
|
143
|
+
::ActionSubscriber::Publisher::Async.publisher_adapter.shutdown!
|
138
144
|
::ActionSubscriber::RabbitConnection.publisher_disconnect!
|
139
145
|
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module ActionSubscriber
|
2
2
|
class Configuration
|
3
3
|
attr_accessor :allow_low_priority_methods,
|
4
|
+
:async_publisher,
|
5
|
+
:async_publisher_drop_messages_when_queue_full,
|
6
|
+
:async_publisher_max_queue_size,
|
7
|
+
:async_publisher_supervisor_interval,
|
4
8
|
:decoder,
|
5
9
|
:default_exchange,
|
6
10
|
:error_handler,
|
@@ -8,17 +12,24 @@ module ActionSubscriber
|
|
8
12
|
:host,
|
9
13
|
:hosts,
|
10
14
|
:mode,
|
15
|
+
:password,
|
11
16
|
:pop_interval,
|
12
17
|
:port,
|
13
18
|
:prefetch,
|
14
19
|
:publisher_confirms,
|
15
20
|
:seconds_to_wait_for_graceful_shutdown,
|
21
|
+
:username,
|
16
22
|
:threadpool_size,
|
17
23
|
:timeout,
|
18
|
-
:times_to_pop
|
24
|
+
:times_to_pop,
|
25
|
+
:virtual_host
|
19
26
|
|
20
27
|
DEFAULTS = {
|
21
28
|
:allow_low_priority_methods => false,
|
29
|
+
:async_publisher => 'memory',
|
30
|
+
:async_publisher_drop_messages_when_queue_full => false,
|
31
|
+
:async_publisher_max_queue_size => 1_000_000,
|
32
|
+
:async_publisher_supervisor_interval => 200, # in milliseconds
|
22
33
|
:default_exchange => 'events',
|
23
34
|
:heartbeat => 5,
|
24
35
|
:host => 'localhost',
|
@@ -31,7 +42,10 @@ module ActionSubscriber
|
|
31
42
|
:seconds_to_wait_for_graceful_shutdown => 30,
|
32
43
|
:threadpool_size => 8,
|
33
44
|
:timeout => 1,
|
34
|
-
:times_to_pop => 8
|
45
|
+
:times_to_pop => 8,
|
46
|
+
:username => "guest",
|
47
|
+
:password => "guest",
|
48
|
+
:virtual_host => "/"
|
35
49
|
}
|
36
50
|
|
37
51
|
##
|
@@ -81,6 +95,13 @@ module ActionSubscriber
|
|
81
95
|
self.decoder.merge!(decoders)
|
82
96
|
end
|
83
97
|
|
98
|
+
def connection_string=(url)
|
99
|
+
settings = ::ActionSubscriber::URI.parse_amqp_url(url)
|
100
|
+
settings.each do |key, value|
|
101
|
+
send("#{key}=", value)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
84
105
|
def hosts
|
85
106
|
return @hosts if @hosts.size > 0
|
86
107
|
[ host ]
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActionSubscriber
|
2
|
+
module Publisher
|
3
|
+
# Publish a message asynchronously to RabbitMQ.
|
4
|
+
#
|
5
|
+
# Asynchronous is designed to do two things:
|
6
|
+
# 1. Introduce the idea of a durable retry should the RabbitMQ connection disconnect.
|
7
|
+
# 2. Provide a higher-level pattern for fire-and-forget publishing.
|
8
|
+
#
|
9
|
+
# @param [String] route The routing key to use for this message.
|
10
|
+
# @param [String] payload The message you are sending. Should already be encoded as a string.
|
11
|
+
# @param [String] exchange The exchange you want to publish to.
|
12
|
+
# @param [Hash] options hash to set message parameters (e.g. headers).
|
13
|
+
def self.publish_async(route, payload, exchange_name, options = {})
|
14
|
+
Async.publisher_adapter.publish(route, payload, exchange_name, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
module Async
|
18
|
+
def self.publisher_adapter
|
19
|
+
@publisher_adapter ||= case ::ActionSubscriber.configuration.async_publisher
|
20
|
+
when /memory/i then
|
21
|
+
require "action_subscriber/publisher/async/in_memory_adapter"
|
22
|
+
InMemoryAdapter.new
|
23
|
+
when /redis/i then
|
24
|
+
fail "Not yet implemented"
|
25
|
+
else
|
26
|
+
fail "Unknown adapter '#{::ActionSubscriber.configuration.async_publisher}' provided"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module ActionSubscriber
|
4
|
+
module Publisher
|
5
|
+
module Async
|
6
|
+
class InMemoryAdapter
|
7
|
+
include ::ActionSubscriber::Logging
|
8
|
+
|
9
|
+
attr_reader :async_queue
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
logger.info "Starting in-memory publisher adapter."
|
13
|
+
|
14
|
+
@async_queue = AsyncQueue.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish(route, payload, exchange_name, options = {})
|
18
|
+
message = Message.new(route, payload, exchange_name, options)
|
19
|
+
async_queue.push(message)
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def shutdown!
|
24
|
+
max_wait_time = ::ActionSubscriber.configuration.seconds_to_wait_for_graceful_shutdown
|
25
|
+
started_shutting_down_at = ::Time.now
|
26
|
+
|
27
|
+
logger.info "Draining async publisher in-memory adapter queue before shutdown. Current queue size: #{async_queue.size}."
|
28
|
+
while async_queue.size > 0
|
29
|
+
if (::Time.now - started_shutting_down_at) > max_wait_time
|
30
|
+
logger.info "Forcing async publisher adapter shutdown because graceful shutdown period of #{max_wait_time} seconds was exceeded. Current queue size: #{async_queue.size}."
|
31
|
+
break
|
32
|
+
end
|
33
|
+
|
34
|
+
sleep 0.1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Message
|
39
|
+
attr_reader :route, :payload, :exchange_name, :options
|
40
|
+
|
41
|
+
def initialize(route, payload, exchange_name, options)
|
42
|
+
@route = route
|
43
|
+
@payload = payload
|
44
|
+
@exchange_name = exchange_name
|
45
|
+
@options = options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnableToPersistMessageError < ::StandardError
|
50
|
+
end
|
51
|
+
|
52
|
+
class AsyncQueue
|
53
|
+
include ::ActionSubscriber::Logging
|
54
|
+
|
55
|
+
attr_reader :consumer, :queue, :supervisor
|
56
|
+
|
57
|
+
if ::RUBY_PLATFORM == "java"
|
58
|
+
NETWORK_ERRORS = [::MarchHare::Exception, ::Java::ComRabbitmqClient::AlreadyClosedException, ::Java::JavaIo::IOException].freeze
|
59
|
+
else
|
60
|
+
NETWORK_ERRORS = [::Bunny::Exception, ::Timeout::Error, ::IOError].freeze
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@queue = ::Queue.new
|
65
|
+
create_and_supervise_consumer!
|
66
|
+
end
|
67
|
+
|
68
|
+
def push(message)
|
69
|
+
# Default of 1_000_000 messages.
|
70
|
+
if queue.size > ::ActionSubscriber.configuration.async_publisher_max_queue_size
|
71
|
+
# Drop Messages if the queue is full and we were configured to do so.
|
72
|
+
return if ::ActionSubscriber.configuration.async_publisher_drop_messages_when_queue_full
|
73
|
+
|
74
|
+
# By default we will raise an error to push the responsibility onto the caller.
|
75
|
+
fail UnableToPersistMessageError, "Queue is full, messages will be dropped."
|
76
|
+
end
|
77
|
+
|
78
|
+
queue.push(message)
|
79
|
+
end
|
80
|
+
|
81
|
+
def size
|
82
|
+
queue.size
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def await_network_reconnect
|
88
|
+
sleep ::ActionSubscriber::RabbitConnection::NETWORK_RECOVERY_INTERVAL
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_and_supervise_consumer!
|
92
|
+
@consumer = create_consumer
|
93
|
+
@supervisor = ::Thread.new do
|
94
|
+
loop do
|
95
|
+
unless consumer.alive?
|
96
|
+
# Why might need to requeue the last message.
|
97
|
+
queue.push(@current_message) if @current_message.present?
|
98
|
+
consumer.kill
|
99
|
+
@consumer = create_consumer
|
100
|
+
end
|
101
|
+
|
102
|
+
# Pause before checking the consumer again.
|
103
|
+
sleep supervisor_interval
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_consumer
|
109
|
+
::Thread.new do
|
110
|
+
loop do
|
111
|
+
# Write "current_message" so we can requeue should something happen to the consumer. I don't love this, but it's
|
112
|
+
# better than writing my own `#peek' method.
|
113
|
+
@current_message = message = queue.pop
|
114
|
+
|
115
|
+
begin
|
116
|
+
::ActionSubscriber::Publisher.publish(message.route, message.payload, message.exchange_name, message.options)
|
117
|
+
|
118
|
+
# Reset
|
119
|
+
@current_message = nil
|
120
|
+
rescue *NETWORK_ERRORS
|
121
|
+
# Sleep because the connection is down.
|
122
|
+
await_network_reconnect
|
123
|
+
|
124
|
+
# Requeue and try again.
|
125
|
+
queue.push(message)
|
126
|
+
rescue => unknown_error
|
127
|
+
# Do not requeue the message because something else horrible happened.
|
128
|
+
@current_message = nil
|
129
|
+
|
130
|
+
# Log the error.
|
131
|
+
logger.info unknown_error.class
|
132
|
+
logger.info unknown_error.message
|
133
|
+
logger.info unknown_error.backtrace.join("\n")
|
134
|
+
|
135
|
+
# TODO: Find a way to bubble this out of the thread for logging purposes.
|
136
|
+
# Reraise the error out of the publisher loop. The Supervisor will restart the consumer.
|
137
|
+
raise unknown_error
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def supervisor_interval
|
144
|
+
@supervisor_interval ||= begin
|
145
|
+
interval_in_milliseconds = ::ActionSubscriber.configuration.async_publisher_supervisor_interval
|
146
|
+
interval_in_milliseconds / 1000.0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -4,6 +4,7 @@ module ActionSubscriber
|
|
4
4
|
module RabbitConnection
|
5
5
|
SUBSCRIBER_CONNECTION_MUTEX = ::Mutex.new
|
6
6
|
PUBLISHER_CONNECTION_MUTEX = ::Mutex.new
|
7
|
+
NETWORK_RECOVERY_INTERVAL = 1.freeze
|
7
8
|
|
8
9
|
def self.publisher_connected?
|
9
10
|
publisher_connection.try(:connected?)
|
@@ -61,12 +62,15 @@ module ActionSubscriber
|
|
61
62
|
|
62
63
|
def self.connection_options
|
63
64
|
{
|
65
|
+
:continuation_timeout => ::ActionSubscriber.configuration.timeout * 1_000.0, #convert sec to ms
|
64
66
|
:heartbeat => ::ActionSubscriber.configuration.heartbeat,
|
65
67
|
:hosts => ::ActionSubscriber.configuration.hosts,
|
68
|
+
:pass => ::ActionSubscriber.configuration.password,
|
66
69
|
:port => ::ActionSubscriber.configuration.port,
|
67
|
-
:
|
70
|
+
:user => ::ActionSubscriber.configuration.username,
|
71
|
+
:vhost => ::ActionSubscriber.configuration.virtual_host,
|
68
72
|
:automatically_recover => true,
|
69
|
-
:network_recovery_interval =>
|
73
|
+
:network_recovery_interval => NETWORK_RECOVERY_INTERVAL,
|
70
74
|
:recover_from_connection_close => true,
|
71
75
|
}
|
72
76
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Taken and adapted from https://github.com/ruby-amqp/amq-protocol/blob/master/lib/amq/uri.rb
|
2
|
+
require "cgi"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module ActionSubscriber
|
6
|
+
class URI
|
7
|
+
AMQP_PORTS = {"amqp" => 5672, "amqps" => 5671}.freeze
|
8
|
+
|
9
|
+
def self.parse_amqp_url(connection_string)
|
10
|
+
uri = ::URI.parse(connection_string)
|
11
|
+
raise ArgumentError.new("Connection URI must use amqp or amqps schema (example: amqp://bus.megacorp.internal:5766), learn more at http://bit.ly/ks8MXK") unless %w{amqp amqps}.include?(uri.scheme)
|
12
|
+
|
13
|
+
opts = {}
|
14
|
+
|
15
|
+
opts[:username] = ::CGI::unescape(uri.user) if uri.user
|
16
|
+
opts[:password] = ::CGI::unescape(uri.password) if uri.password
|
17
|
+
opts[:host] = uri.host if uri.host
|
18
|
+
opts[:port] = uri.port || AMQP_PORTS[uri.scheme]
|
19
|
+
|
20
|
+
if uri.path =~ %r{^/(.*)}
|
21
|
+
raise ArgumentError.new("#{uri} has multiple-segment path; please percent-encode any slashes in the vhost name (e.g. /production => %2Fproduction). Learn more at http://bit.ly/amqp-gem-and-connection-uris") if $1.index('/')
|
22
|
+
opts[:virtual_host] = ::CGI::unescape($1)
|
23
|
+
end
|
24
|
+
|
25
|
+
opts
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
describe ::ActionSubscriber::Configuration do
|
2
2
|
describe "default values" do
|
3
3
|
specify { expect(subject.allow_low_priority_methods).to eq(false) }
|
4
|
+
specify { expect(subject.async_publisher).to eq("memory") }
|
5
|
+
specify { expect(subject.async_publisher_drop_messages_when_queue_full).to eq(false) }
|
6
|
+
specify { expect(subject.async_publisher_max_queue_size).to eq(1_000_000) }
|
7
|
+
specify { expect(subject.async_publisher_supervisor_interval).to eq(200) }
|
4
8
|
specify { expect(subject.default_exchange).to eq("events") }
|
5
9
|
specify { expect(subject.heartbeat).to eq(5) }
|
6
10
|
specify { expect(subject.host).to eq("localhost") }
|
@@ -34,4 +38,15 @@ describe ::ActionSubscriber::Configuration do
|
|
34
38
|
}.to raise_error(/The foo decoder was given with arity of 2/)
|
35
39
|
end
|
36
40
|
end
|
41
|
+
|
42
|
+
describe "connection_string" do
|
43
|
+
it "explodes the connection string into the corresponding settings" do
|
44
|
+
subject.connection_string = "amqp://user:pass@host:100/vhost"
|
45
|
+
expect(subject.username).to eq("user")
|
46
|
+
expect(subject.password).to eq("pass")
|
47
|
+
expect(subject.host).to eq("host")
|
48
|
+
expect(subject.port).to eq(100)
|
49
|
+
expect(subject.virtual_host).to eq("vhost")
|
50
|
+
end
|
51
|
+
end
|
37
52
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
describe ::ActionSubscriber::Publisher::Async::InMemoryAdapter do
|
2
|
+
let(:route) { "test" }
|
3
|
+
let(:payload) { "message" }
|
4
|
+
let(:exchange_name) { "place" }
|
5
|
+
let(:options) { { :test => :ok } }
|
6
|
+
let(:message) { described_class::Message.new(route, payload, exchange_name, options) }
|
7
|
+
let(:mock_queue) { double(:push => nil, :size => 0) }
|
8
|
+
|
9
|
+
describe "#publish" do
|
10
|
+
before do
|
11
|
+
allow(described_class::Message).to receive(:new).with(route, payload, exchange_name, options).and_return(message)
|
12
|
+
allow(described_class::AsyncQueue).to receive(:new).and_return(mock_queue)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can publish a message to the queue" do
|
16
|
+
expect(mock_queue).to receive(:push).with(message)
|
17
|
+
subject.publish(route, payload, exchange_name, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#shutdown!" do
|
22
|
+
# This is called when the rspec finishes. I'm sure we can make this a better test.
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "::ActionSubscriber::Publisher::Async::InMemoryAdapter::Message" do
|
26
|
+
specify { expect(message.route).to eq(route) }
|
27
|
+
specify { expect(message.payload).to eq(payload) }
|
28
|
+
specify { expect(message.exchange_name).to eq(exchange_name) }
|
29
|
+
specify { expect(message.options).to eq(options) }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "::ActionSubscriber::Publisher::Async::InMemoryAdapter::AsyncQueue" do
|
33
|
+
subject { described_class::AsyncQueue.new }
|
34
|
+
|
35
|
+
describe ".initialize" do
|
36
|
+
it "creates a supervisor" do
|
37
|
+
expect_any_instance_of(described_class::AsyncQueue).to receive(:create_and_supervise_consumer!)
|
38
|
+
subject
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#create_and_supervise_consumer!" do
|
43
|
+
it "creates a supervisor" do
|
44
|
+
expect_any_instance_of(described_class::AsyncQueue).to receive(:create_consumer)
|
45
|
+
subject
|
46
|
+
end
|
47
|
+
|
48
|
+
it "restarts the consumer when it dies" do
|
49
|
+
consumer = subject.consumer
|
50
|
+
consumer.kill
|
51
|
+
|
52
|
+
verify_expectation_within(0.1) do
|
53
|
+
expect(consumer).to_not be_alive
|
54
|
+
end
|
55
|
+
|
56
|
+
verify_expectation_within(0.3) do
|
57
|
+
expect(subject.consumer).to be_alive
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#create_consumer" do
|
63
|
+
it "can successfully publish a message" do
|
64
|
+
expect(::ActionSubscriber::Publisher).to receive(:publish).with(route, payload, exchange_name, options)
|
65
|
+
subject.push(message)
|
66
|
+
sleep 0.1 # Await results
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when network error occurs" do
|
70
|
+
let(:error) { described_class::AsyncQueue::NETWORK_ERRORS.first }
|
71
|
+
before { allow(::ActionSubscriber::Publisher).to receive(:publish).and_raise(error) }
|
72
|
+
|
73
|
+
it "requeues the message" do
|
74
|
+
consumer = subject.consumer
|
75
|
+
expect(consumer).to be_alive
|
76
|
+
expect(subject).to receive(:await_network_reconnect).at_least(:once)
|
77
|
+
subject.push(message)
|
78
|
+
sleep 0.1 # Await results
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when an unknown error occurs" do
|
83
|
+
before { allow(::ActionSubscriber::Publisher).to receive(:publish).and_raise(ArgumentError) }
|
84
|
+
|
85
|
+
it "kills the consumer" do
|
86
|
+
consumer = subject.consumer
|
87
|
+
expect(consumer).to be_alive
|
88
|
+
subject.push(message)
|
89
|
+
sleep 0.1 # Await results
|
90
|
+
expect(consumer).to_not be_alive
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#push" do
|
96
|
+
after { ::ActionSubscriber.configuration.async_publisher_max_queue_size = 1000 }
|
97
|
+
after { ::ActionSubscriber.configuration.async_publisher_drop_messages_when_queue_full = false }
|
98
|
+
|
99
|
+
context "when the queue has room" do
|
100
|
+
before { allow(::Queue).to receive(:new).and_return(mock_queue) }
|
101
|
+
|
102
|
+
it "successfully adds to the queue" do
|
103
|
+
expect(mock_queue).to receive(:push).with(message)
|
104
|
+
subject.push(message)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when the queue is full" do
|
109
|
+
before { ::ActionSubscriber.configuration.async_publisher_max_queue_size = -1 }
|
110
|
+
|
111
|
+
context "and we're dropping messages" do
|
112
|
+
before { ::ActionSubscriber.configuration.async_publisher_drop_messages_when_queue_full = true }
|
113
|
+
|
114
|
+
it "adding to the queue should not raise an error" do
|
115
|
+
expect { subject.push(message) }.to_not raise_error
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "and we're not dropping messages" do
|
120
|
+
before { ::ActionSubscriber.configuration.async_publisher_drop_messages_when_queue_full = false }
|
121
|
+
|
122
|
+
it "adding to the queue should raise error back to caller" do
|
123
|
+
expect { subject.push(message) }.to raise_error(described_class::UnableToPersistMessageError)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#size" do
|
130
|
+
it "can return the size of the queue" do
|
131
|
+
expect(subject.size).to eq(0)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe ::ActionSubscriber::Publisher::Async do
|
2
|
+
|
3
|
+
before { described_class.instance_variable_set(:@publisher_adapter, nil) }
|
4
|
+
after { ::ActionSubscriber.configuration.async_publisher = "memory" }
|
5
|
+
|
6
|
+
let(:mock_adapter) { double(:publish => nil) }
|
7
|
+
|
8
|
+
describe ".publish_async" do
|
9
|
+
before { allow(described_class).to receive(:publisher_adapter).and_return(mock_adapter) }
|
10
|
+
|
11
|
+
it "calls through the adapter" do
|
12
|
+
expect(mock_adapter).to receive(:publish).with("1", "2", "3", { "four" => "five" })
|
13
|
+
::ActionSubscriber::Publisher.publish_async("1", "2", "3", { "four" => "five" })
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when an in-memory adapter is selected" do
|
18
|
+
before { ::ActionSubscriber.configuration.async_publisher = "memory" }
|
19
|
+
|
20
|
+
it "Creates an in-memory publisher" do
|
21
|
+
expect(described_class.publisher_adapter).to be_an(::ActionSubscriber::Publisher::Async::InMemoryAdapter)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when an redis adapter is selected" do
|
26
|
+
before { ::ActionSubscriber.configuration.async_publisher = "redis" }
|
27
|
+
|
28
|
+
it "raises an error" do
|
29
|
+
expect { described_class.publisher_adapter }.to raise_error("Not yet implemented")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when some random adapter is selected" do
|
34
|
+
before { ::ActionSubscriber.configuration.async_publisher = "yolo" }
|
35
|
+
|
36
|
+
it "raises an error" do
|
37
|
+
expect { described_class.publisher_adapter }.to raise_error("Unknown adapter 'yolo' provided")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
CHANGED
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: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: java
|
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: 2016-
|
15
|
+
date: 2016-02-08 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
@@ -213,6 +213,8 @@ files:
|
|
213
213
|
- lib/action_subscriber/middleware/router.rb
|
214
214
|
- lib/action_subscriber/middleware/runner.rb
|
215
215
|
- lib/action_subscriber/publisher.rb
|
216
|
+
- lib/action_subscriber/publisher/async.rb
|
217
|
+
- lib/action_subscriber/publisher/async/in_memory_adapter.rb
|
216
218
|
- lib/action_subscriber/rabbit_connection.rb
|
217
219
|
- lib/action_subscriber/railtie.rb
|
218
220
|
- lib/action_subscriber/route.rb
|
@@ -221,6 +223,7 @@ files:
|
|
221
223
|
- lib/action_subscriber/rspec.rb
|
222
224
|
- lib/action_subscriber/subscribable.rb
|
223
225
|
- lib/action_subscriber/threadpool.rb
|
226
|
+
- lib/action_subscriber/uri.rb
|
224
227
|
- lib/action_subscriber/version.rb
|
225
228
|
- routing.md
|
226
229
|
- spec/integration/around_filters_spec.rb
|
@@ -244,6 +247,8 @@ files:
|
|
244
247
|
- spec/lib/action_subscriber/middleware/error_handler_spec.rb
|
245
248
|
- spec/lib/action_subscriber/middleware/router_spec.rb
|
246
249
|
- spec/lib/action_subscriber/middleware/runner_spec.rb
|
250
|
+
- spec/lib/action_subscriber/publisher/async/in_memory_adapter_spec.rb
|
251
|
+
- spec/lib/action_subscriber/publisher/async_spec.rb
|
247
252
|
- spec/lib/action_subscriber/publisher_spec.rb
|
248
253
|
- spec/lib/action_subscriber/router_spec.rb
|
249
254
|
- spec/lib/action_subscriber/subscribable_spec.rb
|
@@ -270,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
275
|
version: '0'
|
271
276
|
requirements: []
|
272
277
|
rubyforge_project:
|
273
|
-
rubygems_version: 2.
|
278
|
+
rubygems_version: 2.4.8
|
274
279
|
signing_key:
|
275
280
|
specification_version: 4
|
276
281
|
summary: ActionSubscriber is a DSL that allows a rails app to consume messages from a RabbitMQ broker.
|
@@ -296,6 +301,8 @@ test_files:
|
|
296
301
|
- spec/lib/action_subscriber/middleware/error_handler_spec.rb
|
297
302
|
- spec/lib/action_subscriber/middleware/router_spec.rb
|
298
303
|
- spec/lib/action_subscriber/middleware/runner_spec.rb
|
304
|
+
- spec/lib/action_subscriber/publisher/async/in_memory_adapter_spec.rb
|
305
|
+
- spec/lib/action_subscriber/publisher/async_spec.rb
|
299
306
|
- spec/lib/action_subscriber/publisher_spec.rb
|
300
307
|
- spec/lib/action_subscriber/router_spec.rb
|
301
308
|
- spec/lib/action_subscriber/subscribable_spec.rb
|