active_publisher 1.0.3 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ac1af1debd71a77adb6f64eec91a8b8f80cd623
4
- data.tar.gz: 4dfe6761ddc756f032c111d44278bc7ff3337816
3
+ metadata.gz: f577f113f070b334e4762f94a70721b7fe17d9fd
4
+ data.tar.gz: cfdf0492ae07520b1a9a975878e17d645c3749b6
5
5
  SHA512:
6
- metadata.gz: bd6f6846845e788f6a86f4d6f872506cdca7e6c0abce164866e55ab0e718dd8d16f0dcf4a3d8cb472d18ce66bf073ab309f2db1a1e286a6015c91c11d991d4b6
7
- data.tar.gz: 1b7e17a5de175b94c995b7e16c38dd9d027c357c25f46bb6ce50185535883e5c62ab717bc0371c4473e03298650022802af528d06fe5c40e57e4cc80403ef820
6
+ metadata.gz: '09e5b37d3ea189ca48b0b94bce04fbd30f3a6243a6db08fe069591de2a1a5a8b5bddfe1a29a52fad8a791fc48539280842e8deec5b15b202f8304902f654c634'
7
+ data.tar.gz: b021c99fa4881392492b97a85a9b3b9886c3ec01e3fdc8e25f9eb88a6187c6ca1b181c7b3557102105aa48c820679ccc661dbb9af669d845deac249e00d5a5a4
data/README.md CHANGED
@@ -51,6 +51,8 @@ Defaults for the configuration are:
51
51
  :heartbeat => 5,
52
52
  :host => "localhost",
53
53
  :hosts => [],
54
+ :max_async_publisher_lag_time => 10,
55
+ :network_recovery_interval => 1,
54
56
  :password => "guest",
55
57
  :port => 5672,
56
58
  :publisher_confirms => false,
@@ -110,4 +112,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
110
112
  ## License
111
113
 
112
114
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
113
-
@@ -27,7 +27,8 @@ Gem::Specification.new do |spec|
27
27
  end
28
28
 
29
29
  spec.add_dependency 'activesupport', '>= 3.2'
30
- spec.add_dependency 'multi_op_queue', '>= 0.1.2'
30
+ spec.add_dependency 'concurrent-ruby'
31
+ spec.add_dependency 'multi_op_queue', '>= 0.2.0'
31
32
 
32
33
  spec.add_development_dependency "bundler"
33
34
  spec.add_development_dependency "pry"
@@ -1,6 +1,7 @@
1
1
  require "active_publisher/message"
2
2
  require "active_publisher/async/in_memory_adapter/async_queue"
3
3
  require "active_publisher/async/in_memory_adapter/consumer_thread"
4
+ require "concurrent"
4
5
  require "multi_op_queue"
5
6
 
6
7
  module ActivePublisher
@@ -60,20 +60,25 @@ module ActivePublisher
60
60
 
61
61
  def create_and_supervise_consumer!
62
62
  @consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
63
- @supervisor = ::Thread.new do
64
- loop do
65
- unless consumer.alive?
66
- consumer.kill
67
- @consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
68
- end
69
63
 
70
- # Notify the current queue size.
71
- ::ActiveSupport::Notifications.instrument "async_queue_size.active_publisher", queue.size
64
+ supervisor_task = ::Concurrent::TimerTask.new(:execution_interval => supervisor_interval) do
65
+ current_time = ::Time.now
66
+
67
+ # Consumer is lagging if it does not "tick" at least once every 10 seconds.
68
+ seconds_since_last_tick = current_time - consumer.last_tick_at
69
+ consumer_is_lagging = seconds_since_last_tick > ::ActivePublisher.configuration.max_async_publisher_lag_time
70
+ logger.error "ActivePublisher consumer is lagging. Last consumer tick was #{seconds_since_last_tick} seconds ago." if consumer_is_lagging
72
71
 
73
- # Pause before checking the consumer again.
74
- sleep supervisor_interval
72
+ # Check to see if we should restart the consumer.
73
+ if !consumer.alive? || consumer_is_lagging
74
+ consumer.kill
75
+ @consumer = ::ActivePublisher::Async::InMemoryAdapter::ConsumerThread.new(queue)
75
76
  end
77
+
78
+ # Notify the current queue size.
79
+ ::ActiveSupport::Notifications.instrument "async_queue_size.active_publisher", queue.size
76
80
  end
81
+ supervisor_task.execute
77
82
  end
78
83
  end
79
84
 
@@ -2,17 +2,27 @@ module ActivePublisher
2
2
  module Async
3
3
  module InMemoryAdapter
4
4
  class ConsumerThread
5
- attr_reader :thread, :queue, :sampled_queue_size
5
+ attr_reader :thread, :queue, :sampled_queue_size, :last_tick_at
6
6
 
7
7
  if ::RUBY_PLATFORM == "java"
8
- NETWORK_ERRORS = [::MarchHare::Exception, ::Java::ComRabbitmqClient::AlreadyClosedException, ::Java::JavaIo::IOException].freeze
8
+ NETWORK_ERRORS = [::MarchHare::NetworkException, ::MarchHare::ConnectionRefused,
9
+ ::Java::ComRabbitmqClient::AlreadyClosedException, ::Java::JavaIo::IOException].freeze
9
10
  else
10
- NETWORK_ERRORS = [::Bunny::Exception, ::Timeout::Error, ::IOError].freeze
11
+ NETWORK_ERRORS = [::Bunny::NetworkFailure, ::Bunny::TCPConnectionFailed, ::Bunny::ConnectionTimeout,
12
+ ::Timeout::Error, ::IOError].freeze
13
+ end
14
+
15
+ if ::RUBY_PLATFORM == "java"
16
+ PRECONDITION_ERRORS = [::MarchHare::PreconditionFailed]
17
+ else
18
+ PRECONDITION_ERRORS = [::Bunny::PreconditionFailed]
11
19
  end
12
20
 
13
21
  def initialize(listen_queue)
14
22
  @queue = listen_queue
15
23
  @sampled_queue_size = queue.size
24
+
25
+ update_last_tick_at
16
26
  start_thread
17
27
  end
18
28
 
@@ -41,38 +51,46 @@ module ActivePublisher
41
51
  channel
42
52
  end
43
53
 
54
+ def update_last_tick_at
55
+ @last_tick_at = ::Time.now
56
+ end
57
+
44
58
  def start_thread
45
59
  return if alive?
46
60
  @thread = ::Thread.new do
47
61
  loop do
48
62
  # Sample the queue size so we don't shutdown when messages are in flight.
49
63
  @sampled_queue_size = queue.size
50
- current_messages = queue.pop_up_to(50)
64
+ current_messages = queue.pop_up_to(50, :timeout => 0.1)
65
+ update_last_tick_at
66
+ # If the queue is empty, we should continue to update to "last_tick_at" time.
67
+ next if current_messages.nil?
68
+
69
+ # We only look at active publisher messages. Everything else is dropped.
70
+ current_messages.select! { |message| message.is_a?(::ActivePublisher::Message) }
51
71
 
52
72
  begin
53
73
  @channel ||= make_channel
54
74
 
55
75
  # Only open a single connection for each group of messages to an exchange
56
76
  current_messages.group_by(&:exchange_name).each do |exchange_name, messages|
57
- begin
58
- current_messages -= messages
59
- publish_all(@channel, exchange_name, messages)
60
- ensure
61
- current_messages.concat(messages)
62
- end
77
+ publish_all(@channel, exchange_name, messages)
78
+ current_messages -= messages
63
79
  end
64
80
  rescue *NETWORK_ERRORS
65
81
  # Sleep because connection is down
66
82
  await_network_reconnect
67
-
68
- # Requeue and try again.
69
- queue.concat(current_messages)
70
83
  rescue => unknown_error
71
84
  ::ActivePublisher.configuration.error_handler.call(unknown_error, {:number_of_messages => current_messages.size})
72
85
  current_messages.each do |message|
73
86
  # Degrade to single message publish ... or at least attempt to
74
87
  begin
75
88
  ::ActivePublisher.publish(message.route, message.payload, message.exchange_name, message.options)
89
+ current_messages.delete(message)
90
+ rescue *PRECONDITION_ERRORS => error
91
+ # Delete messages if rabbitmq cannot declare the exchange (or somet other precondition failed).
92
+ ::ActivePublisher.configuration.error_handler.call(error, {:reason => "precondition failed", :message => message})
93
+ current_messages.delete(message)
76
94
  rescue => individual_error
77
95
  ::ActivePublisher.configuration.error_handler.call(individual_error, {:route => message.route, :payload => message.payload, :exchange_name => message.exchange_name, :options => message.options})
78
96
  end
@@ -81,6 +99,9 @@ module ActivePublisher
81
99
  # TODO: Find a way to bubble this out of the thread for logging purposes.
82
100
  # Reraise the error out of the publisher loop. The Supervisor will restart the consumer.
83
101
  raise unknown_error
102
+ ensure
103
+ # Always requeue anything that gets stuck.
104
+ queue.concat(current_messages) unless current_messages.nil?
84
105
  end
85
106
  end
86
107
  end
@@ -89,37 +110,23 @@ module ActivePublisher
89
110
  def publish_all(channel, exchange_name, messages)
90
111
  ::ActiveSupport::Notifications.instrument "message_published.active_publisher", :message_count => messages.size do
91
112
  exchange = channel.topic(exchange_name)
92
- potentially_retry = []
93
- loop do
94
- break if messages.empty?
95
- message = messages.shift
96
-
97
- fail ActivePublisher::UnknownMessageClassError, "bulk publish messages must be ActivePublisher::Message" unless message.is_a?(ActivePublisher::Message)
113
+ messages.each do |message|
98
114
  fail ActivePublisher::ExchangeMismatchError, "bulk publish messages must match publish_all exchange_name" if message.exchange_name != exchange_name
99
115
 
100
- begin
101
- options = ::ActivePublisher.publishing_options(message.route, message.options || {})
102
- exchange.publish(message.payload, options)
103
- potentially_retry << message
104
- rescue
105
- messages << message
106
- raise
107
- end
116
+ options = ::ActivePublisher.publishing_options(message.route, message.options || {})
117
+ exchange.publish(message.payload, options)
108
118
  end
109
- wait_for_confirms(channel, messages, potentially_retry)
119
+ wait_for_confirms(channel)
110
120
  end
111
121
  end
112
122
 
113
- def wait_for_confirms(channel, messages, potentially_retry)
123
+ def wait_for_confirms(channel)
114
124
  return true unless channel.using_publisher_confirms?
115
125
  if channel.method(:wait_for_confirms).arity > 0
116
126
  channel.wait_for_confirms(::ActivePublisher.configuration.publisher_confirms_timeout)
117
127
  else
118
128
  channel.wait_for_confirms
119
129
  end
120
- rescue
121
- messages.concat(potentially_retry)
122
- raise
123
130
  end
124
131
  end
125
132
  end
@@ -6,6 +6,7 @@ module ActivePublisher
6
6
  :heartbeat,
7
7
  :host,
8
8
  :hosts,
9
+ :max_async_publisher_lag_time,
9
10
  :network_recovery_interval,
10
11
  :password,
11
12
  :port,
@@ -34,6 +35,7 @@ module ActivePublisher
34
35
  :host => "localhost",
35
36
  :hosts => [],
36
37
  :password => "guest",
38
+ :max_async_publisher_lag_time => 10,
37
39
  :network_recovery_interval => NETWORK_RECOVERY_INTERVAL,
38
40
  :port => 5672,
39
41
  :publisher_confirms => false,
@@ -1,3 +1,3 @@
1
1
  module ActivePublisher
2
- VERSION = "1.0.3"
2
+ VERSION = "1.1.0.pre0"
3
3
  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.0.3
4
+ version: 1.1.0.pre0
5
5
  platform: ruby
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: 2017-12-08 00:00:00.000000000 Z
15
+ date: 2018-01-02 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bunny
@@ -42,20 +42,34 @@ dependencies:
42
42
  - - ">="
43
43
  - !ruby/object:Gem::Version
44
44
  version: '3.2'
45
+ - !ruby/object:Gem::Dependency
46
+ name: concurrent-ruby
47
+ requirement: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ type: :runtime
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
45
59
  - !ruby/object:Gem::Dependency
46
60
  name: multi_op_queue
47
61
  requirement: !ruby/object:Gem::Requirement
48
62
  requirements:
49
63
  - - ">="
50
64
  - !ruby/object:Gem::Version
51
- version: 0.1.2
65
+ version: 0.2.0
52
66
  type: :runtime
53
67
  prerelease: false
54
68
  version_requirements: !ruby/object:Gem::Requirement
55
69
  requirements:
56
70
  - - ">="
57
71
  - !ruby/object:Gem::Version
58
- version: 0.1.2
72
+ version: 0.2.0
59
73
  - !ruby/object:Gem::Dependency
60
74
  name: bundler
61
75
  requirement: !ruby/object:Gem::Requirement
@@ -159,12 +173,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
173
  version: '0'
160
174
  required_rubygems_version: !ruby/object:Gem::Requirement
161
175
  requirements:
162
- - - ">="
176
+ - - ">"
163
177
  - !ruby/object:Gem::Version
164
- version: '0'
178
+ version: 1.3.1
165
179
  requirements: []
166
180
  rubyforge_project:
167
- rubygems_version: 2.6.13
181
+ rubygems_version: 2.5.2
168
182
  signing_key:
169
183
  specification_version: 4
170
184
  summary: Aims to make publishing work across MRI and jRuby painless and add some nice