active_publisher 1.1.1.pre0-java → 1.2.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/active_publisher.gemspec +3 -0
- data/lib/active_publisher/async/in_memory_adapter/async_queue.rb +1 -1
- data/lib/active_publisher/async/in_memory_adapter/consumer_thread.rb +1 -1
- data/lib/active_publisher/async/redis_adapter/consumer.rb +42 -0
- data/lib/active_publisher/async/redis_adapter/redis_multi_pop_queue.rb +113 -0
- data/lib/active_publisher/async/redis_adapter.rb +74 -0
- data/lib/active_publisher/configuration.rb +1 -0
- data/lib/active_publisher/version.rb +1 -1
- metadata +50 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9feb8c420d646d0eda3073f0f1971f9b616087667229ed178bde560b508b7d19
|
4
|
+
data.tar.gz: 5601e3a0bcd7d8497192193bca84bae8153feef915806de4bcc90db966e579f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29faadeaaf24e76b1cf34b19fa7d8c8e0437c2c1427f9fbc698cb9f05719563cb0b869dc111b8e7a4129203b6b6d21cf2ce586e8b1d8c9333d3630681fc0f0df
|
7
|
+
data.tar.gz: 7cf68c45c074c3880c39a3b5ecbd82f54686a648039b47e24e9f0aee84e8e6a98421a71f5cd451990f8dcdab306287517d0817b5976e7bb6f67110c2f440868b
|
data/active_publisher.gemspec
CHANGED
@@ -30,7 +30,10 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.add_dependency 'concurrent-ruby'
|
31
31
|
spec.add_dependency 'multi_op_queue', '>= 0.2.0'
|
32
32
|
|
33
|
+
spec.add_development_dependency "benchmark-ips"
|
33
34
|
spec.add_development_dependency "bundler"
|
35
|
+
spec.add_development_dependency "connection_pool"
|
36
|
+
spec.add_development_dependency "fakeredis"
|
34
37
|
spec.add_development_dependency "pry"
|
35
38
|
spec.add_development_dependency "rake", "~> 10.0"
|
36
39
|
spec.add_development_dependency "rspec", "~> 3.2"
|
@@ -71,7 +71,7 @@ module ActivePublisher
|
|
71
71
|
|
72
72
|
# Check to see if we should restart the consumer.
|
73
73
|
if !consumer.alive? || consumer_is_lagging
|
74
|
-
consumer.kill
|
74
|
+
consumer.kill rescue nil
|
75
75
|
@consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
|
76
76
|
end
|
77
77
|
|
@@ -101,7 +101,7 @@ module ActivePublisher
|
|
101
101
|
raise unknown_error
|
102
102
|
ensure
|
103
103
|
# Always requeue anything that gets stuck.
|
104
|
-
queue.concat(current_messages)
|
104
|
+
queue.concat(current_messages) if current_messages && !current_messages.empty?
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "active_publisher/async/redis_adapter/redis_multi_pop_queue"
|
2
|
+
|
3
|
+
module ActivePublisher
|
4
|
+
module Async
|
5
|
+
module RedisAdapter
|
6
|
+
class Consumer
|
7
|
+
SUPERVISOR_INTERVAL = {
|
8
|
+
:execution_interval => 10, # seconds
|
9
|
+
:timeout_interval => 5, # seconds
|
10
|
+
}
|
11
|
+
|
12
|
+
attr_reader :consumer, :queue, :supervisor
|
13
|
+
|
14
|
+
def initialize(redis_pool)
|
15
|
+
@queue = ::ActivePublisher::Async::RedisAdapter::RedisMultiPopQueue.new(redis_pool, ::ActivePublisher::Async::RedisAdapter::REDIS_LIST_KEY)
|
16
|
+
create_and_supervise_consumer!
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_and_supervise_consumer!
|
20
|
+
@consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
|
21
|
+
|
22
|
+
supervisor_task = ::Concurrent::TimerTask.new(SUPERVISOR_INTERVAL) do
|
23
|
+
# This may also be the place to start additional publishers when we are getting backed up ... ?
|
24
|
+
unless consumer.alive?
|
25
|
+
consumer.kill rescue nil
|
26
|
+
@consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Notify the current queue size.
|
30
|
+
::ActiveSupport::Notifications.instrument "redis_async_queue_size.active_publisher", queue.size
|
31
|
+
end
|
32
|
+
|
33
|
+
supervisor_task.execute
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
queue.size
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ActivePublisher
|
2
|
+
module Async
|
3
|
+
module RedisAdapter
|
4
|
+
class RedisMultiPopQueue
|
5
|
+
attr_reader :list_key, :redis_pool
|
6
|
+
|
7
|
+
def initialize(redis_connection_pool, new_list_key)
|
8
|
+
@redis_pool = redis_connection_pool
|
9
|
+
@list_key = new_list_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(message)
|
13
|
+
encoded_message = ::Marshal.dump(message)
|
14
|
+
|
15
|
+
redis_pool.with do |redis|
|
16
|
+
redis.rpush(list_key, encoded_message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def concat(*messages)
|
21
|
+
messages = messages.flatten
|
22
|
+
messages.compact!
|
23
|
+
return if messages.empty?
|
24
|
+
|
25
|
+
encoded_messages = []
|
26
|
+
messages.each do |message|
|
27
|
+
encoded_messages << ::Marshal.dump(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
redis_pool.with do |redis|
|
31
|
+
redis.rpush(list_key, encoded_messages)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
size <= 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def pop_up_to(num_to_pop, opts = {})
|
40
|
+
case opts
|
41
|
+
when TrueClass, FalseClass
|
42
|
+
non_bock = opts
|
43
|
+
when Hash
|
44
|
+
timeout = opts.fetch(:timeout, nil)
|
45
|
+
non_block = opts.fetch(:non_block, false)
|
46
|
+
end
|
47
|
+
|
48
|
+
queue_size = size
|
49
|
+
if queue_size <= 0
|
50
|
+
if non_block
|
51
|
+
raise ThreadError, "queue empty"
|
52
|
+
else
|
53
|
+
total_waited_time = 0
|
54
|
+
|
55
|
+
loop do
|
56
|
+
total_waited_time += 0.1
|
57
|
+
sleep 0.1
|
58
|
+
queue_size = size
|
59
|
+
|
60
|
+
if queue_size > 0
|
61
|
+
num_to_pop = [num_to_pop, queue_size].min # make sure we don't pop more than size
|
62
|
+
return shift(num_to_pop)
|
63
|
+
end
|
64
|
+
|
65
|
+
return if timeout && total_waited_time > timeout
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
shift(num_to_pop)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def shift(number)
|
74
|
+
number = [number, size].min
|
75
|
+
return [] if number <= 0
|
76
|
+
|
77
|
+
messages = []
|
78
|
+
multi_response = []
|
79
|
+
redis_pool.with do |redis|
|
80
|
+
multi_response = redis.multi do
|
81
|
+
redis.lrange(list_key, 0, number - 1)
|
82
|
+
redis.ltrim(list_key, number, -1)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
messages = multi_response.first
|
87
|
+
success = multi_response.last
|
88
|
+
return [] if multi_response.size != 2 || success.nil?
|
89
|
+
return [] unless success =~ /ok/i
|
90
|
+
|
91
|
+
messages = [] if messages.nil?
|
92
|
+
messages = [messages] unless messages.respond_to?(:each)
|
93
|
+
|
94
|
+
shifted_messages = []
|
95
|
+
messages.each do |message|
|
96
|
+
next if message.nil?
|
97
|
+
|
98
|
+
shifted_messages << ::Marshal.load(message)
|
99
|
+
end
|
100
|
+
|
101
|
+
shifted_messages
|
102
|
+
end
|
103
|
+
|
104
|
+
def size
|
105
|
+
redis_pool.with do |redis|
|
106
|
+
redis.llen(list_key) || 0
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "active_publisher"
|
2
|
+
require "active_publisher/message"
|
3
|
+
require "active_publisher/async/redis_adapter/consumer"
|
4
|
+
require "multi_op_queue"
|
5
|
+
|
6
|
+
module ActivePublisher
|
7
|
+
module Async
|
8
|
+
module RedisAdapter
|
9
|
+
REDIS_LIST_KEY = "ACTIVE_PUBLISHER_LIST".freeze
|
10
|
+
|
11
|
+
def self.new(*args)
|
12
|
+
::ActivePublisher::Async::RedisAdapter::Adapter.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Adapter
|
16
|
+
SUPERVISOR_INTERVAL = {
|
17
|
+
:execution_interval => 1.5, # seconds
|
18
|
+
:timeout_interval => 1, # seconds
|
19
|
+
}
|
20
|
+
include ::ActivePublisher::Logging
|
21
|
+
|
22
|
+
attr_reader :async_queue, :redis_pool, :queue
|
23
|
+
|
24
|
+
def initialize(new_redis_pool)
|
25
|
+
logger.info "Starting redis publisher adapter"
|
26
|
+
# do something with supervision ?
|
27
|
+
@redis_pool = new_redis_pool
|
28
|
+
@async_queue = ::ActivePublisher::Async::RedisAdapter::Consumer.new(redis_pool)
|
29
|
+
@queue = ::MultiOpQueue::Queue.new
|
30
|
+
|
31
|
+
supervisor_task = ::Concurrent::TimerTask.new(SUPERVISOR_INTERVAL) do
|
32
|
+
queue_size = queue.size
|
33
|
+
number_of_times = [queue_size / 50, 1].max # get the max number of times to flush
|
34
|
+
number_of_times = [number_of_times, 5].min # don't allow it to be more than 5 per run
|
35
|
+
number_of_times.times { flush_queue! }
|
36
|
+
end
|
37
|
+
|
38
|
+
supervisor_task.execute
|
39
|
+
end
|
40
|
+
|
41
|
+
def publish(route, payload, exchange_name, options = {})
|
42
|
+
message = ::ActivePublisher::Message.new(route, payload, exchange_name, options)
|
43
|
+
queue << ::Marshal.dump(message)
|
44
|
+
flush_queue! if queue.size >= 20 || options[:flush_queue]
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def shutdown!
|
50
|
+
logger.info "Draining async publisher redis adapter before shutdown."
|
51
|
+
flush_queue! until queue.empty?
|
52
|
+
# Sleeping 2.1 seconds because the most common redis `fsync` command in AOF mode is run every 1 second
|
53
|
+
# this will give at least 1 full `fsync` to run before the process dies
|
54
|
+
sleep 2.1
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def flush_queue!
|
60
|
+
return if queue.empty?
|
61
|
+
encoded_messages = queue.pop_up_to(25, :timeout => 0.001)
|
62
|
+
|
63
|
+
return if encoded_messages.nil?
|
64
|
+
return unless encoded_messages.respond_to?(:each)
|
65
|
+
return unless encoded_messages.size > 0
|
66
|
+
|
67
|
+
redis_pool.with do |redis|
|
68
|
+
redis.rpush(::ActivePublisher::Async::RedisAdapter::REDIS_LIST_KEY, encoded_messages)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_publisher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Brian Stien
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date: 2018-01-
|
15
|
+
date: 2018-01-26 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,6 +70,20 @@ dependencies:
|
|
70
70
|
- - ">="
|
71
71
|
- !ruby/object:Gem::Version
|
72
72
|
version: 0.2.0
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
name: benchmark-ips
|
80
|
+
prerelease: false
|
81
|
+
type: :development
|
82
|
+
version_requirements: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
73
87
|
- !ruby/object:Gem::Dependency
|
74
88
|
requirement: !ruby/object:Gem::Requirement
|
75
89
|
requirements:
|
@@ -84,6 +98,34 @@ dependencies:
|
|
84
98
|
- - ">="
|
85
99
|
- !ruby/object:Gem::Version
|
86
100
|
version: '0'
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
name: connection_pool
|
108
|
+
prerelease: false
|
109
|
+
type: :development
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
name: fakeredis
|
122
|
+
prerelease: false
|
123
|
+
type: :development
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
87
129
|
- !ruby/object:Gem::Dependency
|
88
130
|
requirement: !ruby/object:Gem::Requirement
|
89
131
|
requirements:
|
@@ -154,6 +196,9 @@ files:
|
|
154
196
|
- lib/active_publisher/async/in_memory_adapter/async_queue.rb
|
155
197
|
- lib/active_publisher/async/in_memory_adapter/channel.rb
|
156
198
|
- lib/active_publisher/async/in_memory_adapter/consumer_thread.rb
|
199
|
+
- lib/active_publisher/async/redis_adapter.rb
|
200
|
+
- lib/active_publisher/async/redis_adapter/consumer.rb
|
201
|
+
- lib/active_publisher/async/redis_adapter/redis_multi_pop_queue.rb
|
157
202
|
- lib/active_publisher/configuration.rb
|
158
203
|
- lib/active_publisher/connection.rb
|
159
204
|
- lib/active_publisher/logging.rb
|
@@ -174,12 +219,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
174
219
|
version: '0'
|
175
220
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
221
|
requirements:
|
177
|
-
- - "
|
222
|
+
- - ">="
|
178
223
|
- !ruby/object:Gem::Version
|
179
|
-
version:
|
224
|
+
version: '0'
|
180
225
|
requirements: []
|
181
226
|
rubyforge_project:
|
182
|
-
rubygems_version: 2.6.
|
227
|
+
rubygems_version: 2.6.11
|
183
228
|
signing_key:
|
184
229
|
specification_version: 4
|
185
230
|
summary: Aims to make publishing work across MRI and jRuby painless and add some nice
|