active_publisher 1.2.0.pre-java → 1.2.5-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
  SHA256:
3
- metadata.gz: 205bf97d0f41e320989b1399fcb27607d0a8090c67de4cefa91077b999738244
4
- data.tar.gz: fb7f739ec964c77f51554d14c53afc200d1aa8adce0a5c114189dbe1943424cb
3
+ metadata.gz: 7a878924c71e32c34435e80052a8f241b357d2f7b70f3582f7b8920b942e63c0
4
+ data.tar.gz: 60b4eb97d20e389ce4865ef393d69a7d49bdac2e47a3aa26eb6e7ca32e459335
5
5
  SHA512:
6
- metadata.gz: 77f537b065063574df42f2a2926048bed6e69fc2f72608b9be5030d5af3ab78bb1c8c67ed1369911422b7b04a757191d2c5f1c5aeb1b4149518574d185306482
7
- data.tar.gz: 4624d5868cc61dc97b5cbd6acd61e798f6b5c02f567f6baf3279831e79b5c0f1ed6bca90a5e1c586d4ed2bb8eb49ae73350d9d3a80bd3433ede1804a8c82a6d2
6
+ metadata.gz: 8f6399701888401993164b31ccb4fff4f14eb13414a7586d7baacac0c49f2c876dfe33035ce091ec4b62ee4a5b64ad961a7deab79691098401388153404c724b
7
+ data.tar.gz: 2a1d1347293457b9e5b663d7f4815a176e8d9afdc80e472893c4f1f5206bdfc057bfb6d692067924727aac1e3a6f42bc2fff20cd13698bca12be61e1b72d84bc
@@ -0,0 +1,56 @@
1
+ # Inspired by: http://mikebian.co/running-tests-against-multiple-ruby-versions-using-circleci/
2
+
3
+ version: 2.1
4
+
5
+ orbs:
6
+ ruby: circleci/ruby@1.1
7
+
8
+ jobs:
9
+ test:
10
+ parallelism: 1
11
+ parameters:
12
+ ruby-image:
13
+ type: string
14
+ docker:
15
+ - image: << parameters.ruby-image >>
16
+ - image: rabbitmq
17
+
18
+ steps:
19
+ - checkout
20
+ - run:
21
+ name: install dockerize
22
+ command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
23
+ environment:
24
+ DOCKERIZE_VERSION: v0.3.0
25
+ - run:
26
+ name: Wait for rabbitmq
27
+ command: dockerize -wait tcp://localhost:5672 -timeout 1m
28
+ - run:
29
+ name: Install bundler
30
+ command: gem install bundler
31
+ - run:
32
+ name: Which bundler?
33
+ command: bundle -v
34
+ - run:
35
+ name: bundle install
36
+ command: bundle install
37
+ - run:
38
+ name: rspec
39
+ command: bundle exec rspec
40
+
41
+ # strangely, there seems to be very little documentation about exactly how martix builds work.
42
+ # By defining a param inside your job definition, Circle CI will automatically spawn a job for
43
+ # unique param value passed via `matrix`. Neat!
44
+ # https://circleci.com/blog/circleci-matrix-jobs/
45
+ workflows:
46
+ build_and_test:
47
+ jobs:
48
+ - test:
49
+ matrix:
50
+ parameters:
51
+ ruby-image:
52
+ - circleci/ruby:2.5
53
+ - circleci/ruby:2.6
54
+ - circleci/ruby:2.7
55
+ - circleci/jruby:9.1
56
+ - circleci/jruby:9.2
@@ -35,6 +35,6 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency "connection_pool"
36
36
  spec.add_development_dependency "fakeredis"
37
37
  spec.add_development_dependency "pry"
38
- spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "rake"
39
39
  spec.add_development_dependency "rspec", "~> 3.2"
40
40
  end
@@ -35,7 +35,7 @@ module ActivePublisher
35
35
  # @param [Hash] options hash to set message parameters (e.g. headers)
36
36
  def self.publish(route, payload, exchange_name, options = {})
37
37
  with_exchange(exchange_name) do |exchange|
38
- ::ActiveSupport::Notifications.instrument "message_published.active_publisher" do
38
+ ::ActiveSupport::Notifications.instrument "message_published.active_publisher", :route => route do
39
39
  exchange.publish(payload, publishing_options(route, options))
40
40
  end
41
41
  end
@@ -51,7 +51,7 @@ module ActivePublisher
51
51
  fail ActivePublisher::ExchangeMismatchError, "bulk publish messages must match publish_all exchange_name" if message.exchange_name != exchange_name
52
52
 
53
53
  begin
54
- ::ActiveSupport::Notifications.instrument "message_published.active_publisher" do
54
+ ::ActiveSupport::Notifications.instrument "message_published.active_publisher", :route => message.route do
55
55
  exchange.publish(message.payload, publishing_options(message.route, message.options || {}))
56
56
  end
57
57
  rescue
@@ -2,7 +2,13 @@ module ActivePublisher
2
2
  module Async
3
3
  module InMemoryAdapter
4
4
  class ConsumerThread
5
- attr_reader :thread, :queue, :sampled_queue_size, :last_tick_at
5
+ attr_reader :channel, :thread, :queue, :sampled_queue_size, :last_tick_at
6
+
7
+ if ::RUBY_PLATFORM == "java"
8
+ CHANNEL_CLOSED_ERRORS = [::MarchHare::ChannelAlreadyClosed]
9
+ else
10
+ CHANNEL_CLOSED_ERRORS = [::Bunny::ChannelAlreadyClosed]
11
+ end
6
12
 
7
13
  if ::RUBY_PLATFORM == "java"
8
14
  NETWORK_ERRORS = [::MarchHare::NetworkException, ::MarchHare::ConnectionRefused,
@@ -45,6 +51,32 @@ module ActivePublisher
45
51
  end
46
52
  end
47
53
 
54
+ def cleanup_up_channel
55
+ return if channel.nil?
56
+ channel.close
57
+ rescue => error
58
+ ::ActivePublisher.configuration.error_handler.call(error, {:status => "Cleaning up the channel"})
59
+ end
60
+
61
+ def handle_current_messages_on_unknown_error(current_messages)
62
+ current_messages.each do |message|
63
+ # Degrade to single message publish ... or at least attempt to
64
+ begin
65
+ ::ActivePublisher.publish(message.route, message.payload, message.exchange_name, message.options)
66
+ current_messages.delete(message)
67
+ rescue *CHANNEL_CLOSED_ERRORS
68
+ # If the channel is bad, raise!
69
+ raise
70
+ rescue *PRECONDITION_ERRORS => error
71
+ # Delete messages if rabbitmq cannot declare the exchange (or somet other precondition failed).
72
+ ::ActivePublisher.configuration.error_handler.call(error, {:reason => "precondition failed", :message => message})
73
+ current_messages.delete(message)
74
+ rescue => other_error
75
+ ::ActivePublisher.configuration.error_handler.call(other_error, {:route => message.route, :payload => message.payload, :exchange_name => message.exchange_name, :options => message.options})
76
+ end
77
+ end
78
+ end
79
+
48
80
  def make_channel
49
81
  channel = ::ActivePublisher::Async::InMemoryAdapter::Channel.new
50
82
  channel.confirm_select if ::ActivePublisher.configuration.publisher_confirms
@@ -57,70 +89,66 @@ module ActivePublisher
57
89
 
58
90
  def start_thread
59
91
  return if alive?
60
- @thread = ::Thread.new do
61
- loop do
62
- # Sample the queue size so we don't shutdown when messages are in flight.
63
- @sampled_queue_size = queue.size
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) }
71
-
72
- begin
73
- @channel ||= make_channel
74
-
75
- # Only open a single connection for each group of messages to an exchange
76
- current_messages.group_by(&:exchange_name).each do |exchange_name, messages|
77
- publish_all(@channel, exchange_name, messages)
78
- current_messages -= messages
79
- end
80
- rescue *NETWORK_ERRORS
81
- # Sleep because connection is down
82
- await_network_reconnect
83
- rescue => unknown_error
84
- ::ActivePublisher.configuration.error_handler.call(unknown_error, {:number_of_messages => current_messages.size})
85
- current_messages.each do |message|
86
- # Degrade to single message publish ... or at least attempt to
87
- begin
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)
94
- rescue => individual_error
95
- ::ActivePublisher.configuration.error_handler.call(individual_error, {:route => message.route, :payload => message.payload, :exchange_name => message.exchange_name, :options => message.options})
96
- end
97
- end
98
-
99
- # TODO: Find a way to bubble this out of the thread for logging purposes.
100
- # Reraise the error out of the publisher loop. The Supervisor will restart the consumer.
101
- raise unknown_error
102
- ensure
103
- # Always requeue anything that gets stuck.
104
- queue.concat(current_messages) unless current_messages.nil?
92
+ @thread = ::Thread.new { start_consuming_thread }
93
+ end
94
+
95
+ def start_consuming_thread
96
+ loop do
97
+ # Sample the queue size so we don't shutdown when messages are in flight.
98
+ @sampled_queue_size = queue.size
99
+ current_messages = queue.pop_up_to(50, :timeout => 0.1)
100
+ update_last_tick_at
101
+ # If the queue is empty, we should continue to update to "last_tick_at" time.
102
+ next if current_messages.nil?
103
+
104
+ @channel ||= make_channel
105
+
106
+ # We only look at active publisher messages. Everything else is dropped.
107
+ current_messages.select! { |message| message.is_a?(::ActivePublisher::Message) }
108
+
109
+ begin
110
+ # Only open a single connection for each group of messages to an exchange
111
+ current_messages.group_by(&:exchange_name).each do |exchange_name, messages|
112
+ publish_all(exchange_name, messages)
113
+ current_messages -= messages
105
114
  end
115
+ rescue *CHANNEL_CLOSED_ERRORS
116
+ # If the channel is bad, raise without sending one-by-one!
117
+ raise
118
+ rescue *NETWORK_ERRORS
119
+ # Sleep because connection is down
120
+ await_network_reconnect
121
+ rescue => unknown_error
122
+ ::ActivePublisher.configuration.error_handler.call(unknown_error, {:number_of_messages => current_messages.size})
123
+
124
+ # Attempt to deliver a message one-by-one. Raise if a closed channel error appears.
125
+ handle_current_messages_on_unknown_error(current_messages)
126
+
127
+ # TODO: Find a way to bubble this out of the thread for logging purposes.
128
+ # Reraise the error out of the publisher loop. The Supervisor will restart the consumer.
129
+ raise unknown_error
130
+ ensure
131
+ # Always requeue anything that gets stuck.
132
+ queue.concat(current_messages) if current_messages && !current_messages.empty?
106
133
  end
107
134
  end
135
+ ensure
136
+ cleanup_up_channel
108
137
  end
109
138
 
110
- def publish_all(channel, exchange_name, messages)
111
- ::ActiveSupport::Notifications.instrument "message_published.active_publisher", :message_count => messages.size do
112
- exchange = channel.topic(exchange_name)
113
- messages.each do |message|
114
- fail ActivePublisher::ExchangeMismatchError, "bulk publish messages must match publish_all exchange_name" if message.exchange_name != exchange_name
115
-
139
+ def publish_all(exchange_name, messages)
140
+ exchange = channel.topic(exchange_name)
141
+ messages.each do |message|
142
+ fail ::ActivePublisher::ExchangeMismatchError, "bulk publish messages must match publish_all exchange_name" if message.exchange_name != exchange_name
143
+ ::ActiveSupport::Notifications.instrument "message_published.active_publisher", :route => message.route, :message_count => 1 do
116
144
  options = ::ActivePublisher.publishing_options(message.route, message.options || {})
117
145
  exchange.publish(message.payload, options)
118
146
  end
119
- wait_for_confirms(channel)
120
147
  end
148
+ wait_for_confirms
121
149
  end
122
150
 
123
- def wait_for_confirms(channel)
151
+ def wait_for_confirms
124
152
  return true unless channel.using_publisher_confirms?
125
153
  channel.wait_for_confirms(::ActivePublisher.configuration.publisher_confirms_timeout)
126
154
  end
@@ -1,3 +1,4 @@
1
+ require "active_publisher"
1
2
  require "active_publisher/message"
2
3
  require "active_publisher/async/redis_adapter/consumer"
3
4
  require "multi_op_queue"
@@ -64,7 +65,7 @@ module ActivePublisher
64
65
  return unless encoded_messages.size > 0
65
66
 
66
67
  redis_pool.with do |redis|
67
- redis.lpush(::ActivePublisher::Async::RedisAdapter::REDIS_LIST_KEY, encoded_messages)
68
+ redis.rpush(::ActivePublisher::Async::RedisAdapter::REDIS_LIST_KEY, encoded_messages)
68
69
  end
69
70
  end
70
71
  end
@@ -32,8 +32,11 @@ module ActivePublisher
32
32
 
33
33
  supervisor_task.execute
34
34
  end
35
- end
36
35
 
36
+ def size
37
+ queue.size
38
+ end
39
+ end
37
40
  end
38
41
  end
39
42
  end
@@ -13,13 +13,14 @@ module ActivePublisher
13
13
  encoded_message = ::Marshal.dump(message)
14
14
 
15
15
  redis_pool.with do |redis|
16
- redis.lpush(list_key, encoded_message)
16
+ redis.rpush(list_key, encoded_message)
17
17
  end
18
18
  end
19
19
 
20
20
  def concat(*messages)
21
21
  messages = messages.flatten
22
22
  messages.compact!
23
+ return if messages.empty?
23
24
 
24
25
  encoded_messages = []
25
26
  messages.each do |message|
@@ -27,7 +28,7 @@ module ActivePublisher
27
28
  end
28
29
 
29
30
  redis_pool.with do |redis|
30
- redis.lpush(list_key, encoded_messages)
31
+ redis.rpush(list_key, encoded_messages)
31
32
  end
32
33
  end
33
34
 
@@ -70,20 +71,34 @@ module ActivePublisher
70
71
  end
71
72
 
72
73
  def shift(number)
73
- messages = []
74
74
  number = [number, size].min
75
+ return [] if number <= 0
76
+
77
+ messages = []
78
+ multi_response = []
75
79
  redis_pool.with do |redis|
76
- redis.pipelined do
77
- number.times do
78
- messages << redis.rpop(list_key)
79
- end
80
+ multi_response = redis.multi do
81
+ redis.lrange(list_key, 0, number - 1)
82
+ redis.ltrim(list_key, number, -1)
80
83
  end
81
84
  end
82
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
+
83
91
  messages = [] if messages.nil?
84
92
  messages = [messages] unless messages.respond_to?(:each)
85
- messages.compact!
86
- messages.map { |message| ::Marshal.load(message.value) }
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
87
102
  end
88
103
 
89
104
  def size
@@ -63,8 +63,8 @@ module ActivePublisher
63
63
 
64
64
  yaml_config = attempt_to_load_yaml_file(env)
65
65
  DEFAULTS.each_pair do |key, value|
66
- setting = cli_options[key] || cli_options[key.to_s] || yaml_config[key] || yaml_config[key.to_s]
67
- ::ActivePublisher.configuration.public_send("#{key}=", setting) if setting
66
+ exists, setting = fetch_config_value(key, cli_options, yaml_config)
67
+ ::ActivePublisher.configuration.public_send("#{key}=", setting) if exists
68
68
  end
69
69
 
70
70
  true
@@ -88,6 +88,15 @@ module ActivePublisher
88
88
  end
89
89
  private_class_method :attempt_to_load_yaml_file
90
90
 
91
+ def self.fetch_config_value(key, cli_options, yaml_config)
92
+ return [true, cli_options[key]] if cli_options.key?(key)
93
+ return [true, cli_options[key.to_s]] if cli_options.key?(key.to_s)
94
+ return [true, yaml_config[key]] if yaml_config.key?(key)
95
+ return [true, yaml_config[key.to_s]] if yaml_config.key?(key.to_s)
96
+ [false, nil]
97
+ end
98
+ private_class_method :fetch_config_value
99
+
91
100
  ##
92
101
  # Instance Methods
93
102
  #
@@ -31,9 +31,22 @@ module ActivePublisher
31
31
  def self.create_connection
32
32
  if ::RUBY_PLATFORM == "java"
33
33
  connection = ::MarchHare.connect(connection_options)
34
+ connection.on_blocked do |reason|
35
+ on_blocked(reason)
36
+ end
37
+ connection.on_unblocked do
38
+ on_unblocked
39
+ end
40
+ connection
34
41
  else
35
42
  connection = ::Bunny.new(connection_options)
36
43
  connection.start
44
+ connection.on_blocked do |blocked_message|
45
+ on_blocked(blocked_message.reason)
46
+ end
47
+ connection.on_unblocked do
48
+ on_unblocked
49
+ end
37
50
  connection
38
51
  end
39
52
  end
@@ -58,5 +71,15 @@ module ActivePublisher
58
71
  }
59
72
  end
60
73
  private_class_method :connection_options
74
+
75
+ def self.on_blocked(reason)
76
+ ::ActiveSupport::Notifications.instrument("connection_blocked.active_publisher", :reason => reason)
77
+ end
78
+ private_class_method :on_blocked
79
+
80
+ def self.on_unblocked
81
+ ::ActiveSupport::Notifications.instrument("connection_unblocked.active_publisher")
82
+ end
83
+ private_class_method :on_unblocked
61
84
  end
62
85
  end
@@ -1,3 +1,3 @@
1
1
  module ActivePublisher
2
- VERSION = "1.2.0.pre"
2
+ VERSION = "1.2.5"
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.2.0.pre
4
+ version: 1.2.5
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-16 00:00:00.000000000 Z
15
+ date: 2020-11-23 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  requirement: !ruby/object:Gem::Requirement
@@ -143,17 +143,17 @@ dependencies:
143
143
  - !ruby/object:Gem::Dependency
144
144
  requirement: !ruby/object:Gem::Requirement
145
145
  requirements:
146
- - - "~>"
146
+ - - ">="
147
147
  - !ruby/object:Gem::Version
148
- version: '10.0'
148
+ version: '0'
149
149
  name: rake
150
150
  prerelease: false
151
151
  type: :development
152
152
  version_requirements: !ruby/object:Gem::Requirement
153
153
  requirements:
154
- - - "~>"
154
+ - - ">="
155
155
  - !ruby/object:Gem::Version
156
- version: '10.0'
156
+ version: '0'
157
157
  - !ruby/object:Gem::Dependency
158
158
  requirement: !ruby/object:Gem::Requirement
159
159
  requirements:
@@ -179,9 +179,9 @@ executables: []
179
179
  extensions: []
180
180
  extra_rdoc_files: []
181
181
  files:
182
+ - ".circleci/config.yml"
182
183
  - ".gitignore"
183
184
  - ".rspec"
184
- - ".travis.yml"
185
185
  - CODE_OF_CONDUCT.md
186
186
  - Gemfile
187
187
  - LICENSE.txt
@@ -219,12 +219,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
219
219
  version: '0'
220
220
  required_rubygems_version: !ruby/object:Gem::Requirement
221
221
  requirements:
222
- - - ">"
222
+ - - ">="
223
223
  - !ruby/object:Gem::Version
224
- version: 1.3.1
224
+ version: '0'
225
225
  requirements: []
226
226
  rubyforge_project:
227
- rubygems_version: 2.6.11
227
+ rubygems_version: 2.7.9
228
228
  signing_key:
229
229
  specification_version: 4
230
230
  summary: Aims to make publishing work across MRI and jRuby painless and add some nice
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - jruby-9.1.12.0
6
- services:
7
- - rabbitmq
8
- sudo: false
9
- cache: bundler