active_publisher 1.0.3-java → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c842c3825c85ddd417ae7b44992f0dd116e04952
4
- data.tar.gz: 52903e7be84dc6295e68446fdb80e1d6cf898421
3
+ metadata.gz: bbb7f6d380395ad17ca81b0128f6ff2cec2e0e98
4
+ data.tar.gz: 6ce766a9e768c7adc0834f94dd10473e73a8a144
5
5
  SHA512:
6
- metadata.gz: 7a5e4ec82f01a97f3c904ebaf1f3501a45857a072eb245f24a1a717479483cd4a452d8338beddb9d05bf786164d076a185270daa47a803c2ee36e4efe4598cd6
7
- data.tar.gz: 64521da6574d628d62a26fa98bcbfedf87ac54ddeca47fd9a791b3eaed545fcc867d4861ff6f0f509cfcc9e6f8dd327ac20854bc7321bfbced039ce7dbbbebb8
6
+ metadata.gz: ad04a249d94c9cb8fc91b5c195ddab86e3a6788e0512c9101d6652f0b7e3d5438b100951c50dc9f3c1406c24a3e847168cd6057ae5ba1ea76b0adb1841e084c5
7
+ data.tar.gz: d10b6b66a2239e5076ef55e78dcac184d9937c0f114423a1399adce9812b49f9cbd1428d7a7aa46fe9ac63a4e309c1e2bd23e4a0ef5588d438391a90f6ace209
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"
@@ -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
@@ -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
@@ -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"
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
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: 2017-12-08 00:00:00.000000000 Z
15
+ date: 2018-01-08 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  requirement: !ruby/object:Gem::Requirement
@@ -47,7 +47,21 @@ dependencies:
47
47
  requirements:
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: 0.1.2
50
+ version: '0'
51
+ name: concurrent-ruby
52
+ prerelease: false
53
+ type: :runtime
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ - !ruby/object:Gem::Dependency
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 0.2.0
51
65
  name: multi_op_queue
52
66
  prerelease: false
53
67
  type: :runtime
@@ -55,7 +69,7 @@ dependencies:
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
  requirement: !ruby/object:Gem::Requirement
61
75
  requirements: