protobuf-nats 0.11.0.pre1 → 0.12.0.pre0

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
- SHA1:
3
- metadata.gz: 983e924ae43d3fca4ccdfc73d8715c5c01b3f2bb
4
- data.tar.gz: 9b88a322f6a04be4f2a0392b32555ecc9f29f284
2
+ SHA256:
3
+ metadata.gz: c2df179aaf5b89613d47a629da54127097597956a5154f6628a27155359796f0
4
+ data.tar.gz: 23f4c10524cd40ec892fdfea7e8c5545590fe667111d0683cc24a9be47e32b60
5
5
  SHA512:
6
- metadata.gz: 211f48f2481d731e09593fb926f87cf2d93b92e32ceab6e27952735c6da9ee30ea9f2d2bdb5edd9c3986e8efeb0180782f03451d318f3df086887fe49fe133a2
7
- data.tar.gz: 0e70e256836e424a011cef424b9f589b36c4f3bd1d203c29db9e24e58e885043f6d409af531cfb9b29ec57087c179c5cfb83a29c9045380db3dd9920c95b32ec
6
+ metadata.gz: b4ff63c7224a17541320cbc34e196efead0bf0555f0de5aabf110b2e49bb8c0a45ffc8579c295077ace52b40ade6ee991f332df74dba2e4def0d5a4f69917376
7
+ data.tar.gz: 1772c0bbafddc6364734dbe25d3618e32c93f6d5194241f1bd54e46e3239902916c9195c184ff95953a6db4d28c9e1dd85d756adc4717fda52e5b0bcef76da26
data/.travis.yml CHANGED
@@ -1,12 +1,16 @@
1
1
  sudo: false
2
2
  language: ruby
3
- jdk: openjdk8
3
+ jdk:
4
+ - openjdk8
4
5
  rvm:
5
6
  - 2.3.0
7
+ - 2.7.0
6
8
  - jruby-9.1.7.0
9
+ - jruby-9.2.13.0
7
10
  before_install:
11
+ # Install and start gnatsd
12
+ - ./scripts/install_gnatsd.sh
13
+ - $HOME/nats-server/nats-server &
14
+ # Install deps for project
8
15
  - gem install bundler
9
16
  - gem update --system
10
- - wget https://github.com/nats-io/gnatsd/releases/download/v1.3.0/gnatsd-v1.3.0-linux-amd64.zip
11
- - unzip gnatsd-v1.3.0-linux-amd64.zip
12
- - ./gnatsd-v1.3.0-linux-amd64/gnatsd &
data/README.md CHANGED
@@ -49,6 +49,8 @@ used to allow JVM based servers to warm-up slowly to prevent jolts in runtime pe
49
49
 
50
50
  `PB_NATS_CLIENT_SUBSCRIPTION_POOL_SIZE` - If subscription pooling is desired for the request/response cycle then the pool size maximum should be set; the pool is lazy and therefore will only start new subscriptions as necessary (default: 0)
51
51
 
52
+ `PB_NATS_DISABLE_JNATS` - Disable the default jruby jnats client on the jruby platform, use the nats-pure.rb client instead (default: false).
53
+
52
54
  `PROTOBUF_NATS_CONFIG_PATH` - Custom path to the config yaml (default: "config/protobuf_nats.yml").
53
55
 
54
56
  ### YAML Config
@@ -154,9 +156,11 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
154
156
 
155
157
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
158
 
159
+ The java-nats client is temporarily forked to support jruby > 9.2.10.0. The living branch for that is here: https://github.com/film42/java-nats/tree/jruby-compat. This will be removed when we upgrade to the new nats.java client.
160
+
157
161
  ## Contributing
158
162
 
159
- Bug reports and pull requests are welcome on GitHub at https://github.com/abrandoned/protobuf-nats.
163
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mxenabled/protobuf-nats.
160
164
 
161
165
 
162
166
  ## License
Binary file
@@ -1,11 +1,133 @@
1
1
  require "connection_pool"
2
2
  require "protobuf/nats"
3
+ require "protobuf/nats/platform"
3
4
  require "protobuf/rpc/connectors/base"
4
5
  require "monitor"
5
6
 
6
7
  module Protobuf
7
8
  module Nats
9
+ class ResponseMuxerRequest
10
+ def initialize(muxer, token)
11
+ @muxer = muxer
12
+ @token = token
13
+ end
14
+
15
+ def publish(subject, data)
16
+ @muxer.publish(subject, data, @token)
17
+ end
18
+
19
+ def next_message(timeout)
20
+ @muxer.next_message(@token, timeout)
21
+ end
22
+
23
+ def cleanup
24
+ @muxer.cleanup(@token)
25
+ end
26
+ end
27
+
28
+ class ResponseMuxer
29
+ LOCK = ::Mutex.new
30
+
31
+ def initialize
32
+ @resp_map = Hash.new { |h,k| h[k] = { } }
33
+ end
34
+
35
+ def cleanup(token)
36
+ @resp_sub.synchronize { @resp_map.delete(token) }
37
+ end
38
+
39
+ def next_message(token, timeout)
40
+ ::NATS::MonotonicTime::with_nats_timeout(timeout) do
41
+ @resp_sub.synchronize do
42
+ break if @resp_map[token].key?(:response) &&
43
+ !@resp_map[token][:response].empty?
44
+
45
+ @resp_map[token][:signal].wait(timeout)
46
+ end
47
+ end
48
+
49
+ @resp_sub.synchronize { @resp_map[token][:response].shift }
50
+ end
51
+
52
+ def new_request
53
+ nats = Protobuf::Nats.client_nats_connection
54
+ token = nats.new_inbox.split('.').last
55
+ @resp_sub.synchronize do
56
+ @resp_map[token][:signal] = @resp_sub.new_cond
57
+ end
58
+
59
+ ResponseMuxerRequest.new(self, token)
60
+ end
61
+
62
+ def publish(subject, data, token)
63
+ nats = Protobuf::Nats.client_nats_connection
64
+ reply_to = "#{@resp_inbox_prefix}.#{token}"
65
+ nats.publish(subject, data, reply_to)
66
+ end
67
+
68
+ def restart
69
+ start unless started?
70
+
71
+ LOCK.synchronize do
72
+ @resp_handler&.kill
73
+ @started = false
74
+ end
75
+
76
+ start
77
+ end
78
+
79
+ def start
80
+ return if started?
81
+ LOCK.synchronize do
82
+ # We check this twice in case another thread was waiting for the lock to
83
+ # start this party.
84
+ return if started?
85
+
86
+ nats = ::Protobuf::Nats.client_nats_connection
87
+ return if nats.nil?
88
+
89
+ @resp_inbox_prefix = nats.new_inbox
90
+ @resp_sub = nats.subscribe("#{@resp_inbox_prefix}.*")
91
+ @started = true
92
+ end
93
+
94
+ @resp_handler = Thread.new do
95
+ begin
96
+ loop do
97
+ msg = @resp_sub.pending_queue.pop
98
+ next if msg.nil?
99
+ @resp_sub.synchronize do
100
+ # Decrease pending size since consumed already
101
+ @resp_sub.pending_size -= msg.data.size
102
+ end
103
+ token = msg.subject.split('.').last
104
+
105
+ @resp_sub.synchronize do
106
+ # Reject if the token is missing from the request map
107
+ break unless @resp_map.key?(token)
108
+
109
+ signal = @resp_map[token][:signal]
110
+ @resp_map[token][:response] ||= []
111
+ @resp_map[token][:response] << msg
112
+ signal.signal
113
+ end
114
+ rescue => error
115
+ ::Protobuf::Nats.notify_error_callbacks(error)
116
+ LOCK.synchronize { @started = false }
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def started?
123
+ !!@started
124
+ end
125
+ end
126
+
8
127
  class Client < ::Protobuf::Rpc::Connectors::Base
128
+
129
+ RESPONSE_MUXER = ResponseMuxer.new
130
+
9
131
  # Structure to hold subscription and inbox to use within pool
10
132
  SubscriptionInbox = ::Struct.new(:subscription, :inbox) do
11
133
  def swap(sub_inbox)
@@ -36,6 +158,9 @@ module Protobuf
36
158
 
37
159
  # This will ensure the client is started.
38
160
  ::Protobuf::Nats.start_client_nats_connection
161
+
162
+ # Ensure the response muxer is started
163
+ RESPONSE_MUXER.start
39
164
  end
40
165
 
41
166
  def new_subscription_inbox
@@ -151,12 +276,12 @@ module Protobuf
151
276
  when :ack_timeout
152
277
  ::ActiveSupport::Notifications.instrument "client.request_timeout.protobuf-nats"
153
278
  next if (retries -= 1) > 0
154
- raise ::Protobuf::Nats::Errors::RequestTimeout
279
+ raise ::Protobuf::Nats::Errors::RequestTimeout, formatted_service_and_method_name
155
280
  when :nack
156
281
  ::ActiveSupport::Notifications.instrument "client.request_nack.protobuf-nats"
157
282
  interval = nack_backoff_intervals[nack_retry]
158
283
  nack_retry += 1
159
- raise ::Protobuf::Nats::Errors::RequestTimeout if interval.nil?
284
+ raise ::Protobuf::Nats::Errors::RequestTimeout, formatted_service_and_method_name if interval.nil?
160
285
  sleep((interval + nack_backoff_splay)/1000.0)
161
286
  next
162
287
  end
@@ -165,11 +290,11 @@ module Protobuf
165
290
  end
166
291
 
167
292
  parse_response
168
- rescue ::Protobuf::Nats::Errors::IOException, ::Protobuf::Nats::Errors::IllegalStateException => error
293
+ rescue ::Protobuf::Nats::Errors::IOException => error
169
294
  ::Protobuf::Nats.log_error(error)
170
295
 
171
296
  delay = reconnect_delay
172
- logger.warn "An #{error.class} was raised. We are going to sleep for #{delay} seconds."
297
+ logger.warn "An IOException was raised. We are going to sleep for #{delay} seconds."
173
298
  sleep delay
174
299
 
175
300
  retry if (retries -= 1) > 0
@@ -186,10 +311,16 @@ module Protobuf
186
311
  end
187
312
  end
188
313
 
314
+ def formatted_service_and_method_name
315
+ klass = @options[:service]
316
+ method_name = @options[:method]
317
+ "#{klass}##{method_name}"
318
+ end
319
+
189
320
  # The Java nats client offers better message queueing so we're going to use
190
321
  # that over locking ourselves. This split in code isn't great, but we can
191
322
  # refactor this later.
192
- if defined? JRUBY_VERSION
323
+ if ::Protobuf::Nats.jruby?
193
324
 
194
325
  # This is a request that expects two responses.
195
326
  # 1. An ACK from the server. We use a shorter timeout.
@@ -207,7 +338,7 @@ module Protobuf
207
338
  begin
208
339
  completed_request = false
209
340
 
210
- if !sub_inbox.subscription.is_active # replace the subscription if is has been pooled but is no longer valid (maybe a reconnect)
341
+ if !sub_inbox.subscription.is_valid # replace the subscription if is has been pooled but is no longer valid (maybe a reconnect)
211
342
  nats.unsubscribe(sub_inbox.subscription)
212
343
  sub_inbox.swap(new_subscription_inbox) # this line replaces the sub_inbox in the connection pool if necessary
213
344
  end
@@ -231,7 +362,7 @@ module Protobuf
231
362
  else return :ack_timeout
232
363
  end
233
364
 
234
- fail(::Protobuf::Nats::Errors::ResponseTimeout, subject) unless response
365
+ fail(::Protobuf::Nats::Errors::ResponseTimeout, formatted_service_and_method_name) unless response
235
366
 
236
367
  completed_request = true
237
368
  response
@@ -247,52 +378,49 @@ module Protobuf
247
378
  else
248
379
 
249
380
  def nats_request_with_two_responses(subject, data, opts)
381
+ # Wait for the ACK from the server
382
+ ack_timeout = opts[:ack_timeout] || 5
383
+ # Wait for the protobuf response
384
+ timeout = opts[:timeout] || 60
385
+
250
386
  nats = Protobuf::Nats.client_nats_connection
251
- inbox = nats.new_inbox
252
- lock = ::Monitor.new
253
- received = lock.new_cond
254
- messages = []
255
- first_message = nil
256
- second_message = nil
257
- response = nil
258
-
259
- sid = nats.subscribe(inbox, :max => 2) do |message, _, _|
260
- lock.synchronize do
261
- messages << message
262
- received.signal
263
- end
264
- end
265
387
 
266
- lock.synchronize do
267
- # Publish to server
268
- nats.publish(subject, data, inbox)
388
+ # Publish message with the reply topic pointed at the response muxer.
389
+ req = RESPONSE_MUXER.new_request
390
+ req.publish(subject, data)
269
391
 
270
- # Wait for the ACK from the server
271
- ack_timeout = opts[:ack_timeout] || 5
272
- received.wait(ack_timeout) if messages.empty?
273
- first_message = messages.shift
392
+ # Receive the first message
393
+ begin
394
+ first_message = req.next_message(ack_timeout)
395
+ rescue ::NATS::Timeout => e
396
+ return :ack_timeout
397
+ end
274
398
 
275
- return :ack_timeout if first_message.nil?
276
- return :nack if first_message == ::Protobuf::Nats::Messages::NACK
399
+ # Check for a NACK
400
+ return :nack if first_message.data == ::Protobuf::Nats::Messages::NACK
277
401
 
278
- # Wait for the protobuf response
279
- timeout = opts[:timeout] || 60
280
- received.wait(timeout) if messages.empty?
281
- second_message = messages.shift
402
+ # Receive the second message
403
+ begin
404
+ second_message = req.next_message(timeout)
405
+ rescue ::NATS::Timeout
406
+ # ignore to raise a repsonse timeout below
282
407
  end
283
408
 
409
+ # NOTE: This might be nil, so be careful checking the data value
410
+ second_message_data = second_message&.data
411
+
412
+ # Check messages
284
413
  response = case ::Protobuf::Nats::Messages::ACK
285
- when first_message then second_message
286
- when second_message then first_message
414
+ when first_message.data then second_message_data
415
+ when second_message_data then first_message.data
287
416
  else return :ack_timeout
288
417
  end
289
418
 
290
- fail(::Protobuf::Nats::Errors::ResponseTimeout, subject) unless response
419
+ fail(::Protobuf::Nats::Errors::ResponseTimeout, formatted_service_and_method_name) unless response
291
420
 
292
421
  response
293
422
  ensure
294
- # Ensure we don't leave a subscription sitting around.
295
- nats.unsubscribe(sid) if response.nil?
423
+ req.cleanup if req
296
424
  end
297
425
 
298
426
  end
@@ -40,7 +40,12 @@ module Protobuf
40
40
  config_path = ENV["PROTOBUF_NATS_CONFIG_PATH"] || ::File.join("config", "protobuf_nats.yml")
41
41
  absolute_config_path = ::File.expand_path(config_path)
42
42
  if ::File.exists?(absolute_config_path)
43
- yaml_config = ::YAML.load_file(absolute_config_path)[env]
43
+ # Psych 4 and newer requires unsafe_load_file in order for aliases to be used
44
+ yaml_config = if ::YAML.respond_to?(:unsafe_load_file)
45
+ ::YAML.unsafe_load_file(absolute_config_path)[env]
46
+ else
47
+ ::YAML.load_file(absolute_config_path)[env]
48
+ end
44
49
  end
45
50
 
46
51
  DEFAULTS.each_pair do |key, value|
@@ -1,27 +1,18 @@
1
1
  module Protobuf
2
2
  module Nats
3
3
  module Errors
4
- class Base < ::StandardError
4
+ class ClientError < ::StandardError
5
5
  end
6
6
 
7
- class RequestTimeout < Base
7
+ class RequestTimeout < ClientError
8
8
  end
9
9
 
10
- class ResponseTimeout < Base
10
+ class ResponseTimeout < ClientError
11
11
  end
12
12
 
13
13
  class MriIOException < ::StandardError
14
14
  end
15
15
 
16
- class MriIllegalStateException < ::StandardError
17
- end
18
-
19
- IllegalStateException = if defined? JRUBY_VERSION
20
- java.lang.IllegalStateException
21
- else
22
- MriIllegalStateException
23
- end
24
-
25
16
  IOException = if defined? JRUBY_VERSION
26
17
  java.io.IOException
27
18
  else
@@ -3,82 +3,12 @@ ext_base = ::File.join(::File.dirname(__FILE__), '..', '..', '..', 'ext')
3
3
  require ::File.join(ext_base, "jars/slf4j-api-1.7.25.jar")
4
4
  require ::File.join(ext_base, "jars/slf4j-simple-1.7.25.jar")
5
5
  require ::File.join(ext_base, "jars/gson-2.6.2.jar")
6
- require ::File.join(ext_base, "jars/jnats-2.6.0.jar")
6
+ require ::File.join(ext_base, "jars/jnats-1.1-SNAPSHOT.jar")
7
7
 
8
8
  module Protobuf
9
9
  module Nats
10
10
  class JNats
11
- attr_reader :connection, :dispatcher, :options
12
-
13
- class MessageHandlerProxy
14
- include ::Java::IoNatsClient::MessageHandler
15
-
16
- def self.empty
17
- new {}
18
- end
19
-
20
- def initialize(&block)
21
- @cb = block
22
- end
23
-
24
- def onMessage(message)
25
- @cb.call(message.getData.to_s, message.getReplyTo, message.getSubject)
26
- end
27
- end
28
-
29
- class ConnectionListener
30
- include ::Java::IoNatsClient::ConnectionListener
31
-
32
- def initialize
33
- @on_reconnect_cb = lambda {}
34
- @on_disconnect_cb = lambda {}
35
- @on_close_cb = lambda {}
36
- end
37
-
38
- def on_close(&block); @on_close_cb = block; end
39
- def on_disconnect(&block); @on_disconnect_cb = block; end
40
- def on_reconnect(&block); @on_reconnect_cb = block; end
41
-
42
- def connectionEvent(conn, event_type)
43
- case event_type
44
- when ::Java::IoNatsClient::ConnectionListener::Events::RECONNECTED
45
- @on_reconnect_cb.call
46
- when ::Java::IoNatsClient::ConnectionListener::Events::DISCONNECTED
47
- @on_disconnect_cb.call
48
- when ::Java::IoNatsClient::ConnectionListener::Events::CLOSED
49
- @on_close_cb.call
50
- end
51
- end
52
- end
53
-
54
- class ErrorListener
55
- include ::Java::IoNatsClient::ErrorListener
56
-
57
- def initialize
58
- @on_error_cb = lambda { |_error| }
59
- @on_exception_cb = lambda { |_exception| }
60
- @on_slow_consumer_cb = lambda { |_consumer| }
61
- end
62
-
63
- def on_error(&block)
64
- return if block.nil? || block.arity != 1
65
- @on_error_cb = block
66
- end
67
-
68
- def on_exception(&block)
69
- return if block.nil? || block.arity != 1
70
- @on_exception_cb = block
71
- end
72
-
73
- def on_slow_consumer(&block)
74
- return if block.nil? || block.arity != 1
75
- @on_slow_consumer_cb = block
76
- end
77
-
78
- def errorOccurred(_conn, error); @on_error_cb.call(error); end
79
- def exceptionOccurred(_conn, exception); @on_exception_cb.call(exception); end
80
- def slowConsumerDetected(_conn, consumer); @on_slow_consumer_cb.call(consumer); end
81
- end
11
+ attr_reader :connection, :options
82
12
 
83
13
  class Message
84
14
  attr_reader :data, :subject, :reply
@@ -91,9 +21,10 @@ module Protobuf
91
21
  end
92
22
 
93
23
  def initialize
94
- @connection_listener = ConnectionListener.new
95
- @error_listener = ErrorListener.new
96
-
24
+ @on_error_cb = lambda {|error|}
25
+ @on_reconnect_cb = lambda {}
26
+ @on_disconnect_cb = lambda {}
27
+ @on_close_cb = lambda {}
97
28
  @options = nil
98
29
  @subz_cbs = {}
99
30
  @subz_mutex = ::Mutex.new
@@ -103,26 +34,35 @@ module Protobuf
103
34
  @options ||= options
104
35
 
105
36
  servers = options[:servers] || ["nats://localhost:4222"]
106
- servers = [servers].flatten
107
-
108
- builder = ::Java::IoNatsClient::Options::Builder.new
109
- builder.servers(servers)
110
- builder.maxReconnects(options[:max_reconnect_attempts])
111
- builder.errorListener(@error_listener)
37
+ servers = [servers].flatten.map { |uri_string| java.net.URI.new(uri_string) }
38
+ connection_factory = ::Java::IoNatsClient::ConnectionFactory.new
39
+ connection_factory.setServers(servers)
40
+ connection_factory.setMaxReconnect(options[:max_reconnect_attempts])
112
41
 
113
42
  # Shrink the pending buffer to always raise an error and let the caller retry.
114
43
  if options[:disable_reconnect_buffer]
115
- builder.reconnectBufferSize(1)
44
+ connection_factory.setReconnectBufSize(1)
116
45
  end
117
46
 
47
+ # Setup callbacks
48
+ connection_factory.setDisconnectedCallback { |event| @on_disconnect_cb.call }
49
+ connection_factory.setReconnectedCallback { |_event| @on_reconnect_cb.call }
50
+ connection_factory.setClosedCallback { |_event| @on_close_cb.call }
51
+ connection_factory.setExceptionHandler { |error| @on_error_cb.call(error) }
52
+
118
53
  # Setup ssl context if we're using tls
119
54
  if options[:uses_tls]
120
55
  ssl_context = create_ssl_context(options)
121
- builder.sslContext(ssl_context)
56
+ connection_factory.setSecure(true)
57
+ connection_factory.setSSLContext(ssl_context)
122
58
  end
123
59
 
124
- @connection = ::Java::IoNatsClient::Nats.connect(builder.build)
125
- @dispatcher = @connection.createDispatcher(MessageHandlerProxy.empty)
60
+ @connection = connection_factory.createConnection
61
+
62
+ # We're going to spawn a consumer and supervisor
63
+ @work_queue = @connection.createMsgChannel
64
+ spwan_supervisor_and_consumer
65
+
126
66
  @connection
127
67
  end
128
68
 
@@ -135,56 +75,64 @@ module Protobuf
135
75
 
136
76
  # Do not depend on #close for a graceful disconnect.
137
77
  def close
138
- if @connection
139
- @connection.closeDispatcher(@dispatcher) rescue nil
140
- @connection.close rescue nil
141
- end
142
- @dispatcher = nil
78
+ @connection.close rescue nil
143
79
  @connection = nil
80
+ @supervisor.kill rescue nil
81
+ @supervisor = nil
82
+ @consumer.kill rescue nil
83
+ @supervisor = nil
144
84
  end
145
85
 
146
86
  def flush(timeout_sec = 0.5)
147
- duration = duration_in_ms(timeout_sec * 1000)
148
- connection.flush(duration)
87
+ connection.flush(timeout_sec * 1000)
149
88
  end
150
89
 
151
90
  def next_message(sub, timeout_sec)
152
- duration = duration_in_ms(timeout_sec * 1000)
153
- nats_message = sub.nextMessage(duration)
91
+ nats_message = sub.nextMessage(timeout_sec * 1000)
154
92
  return nil unless nats_message
155
93
  Message.new(nats_message)
156
94
  end
157
95
 
158
96
  def publish(subject, data, mailbox = nil)
159
97
  # The "true" here is to force flush. May not need this.
160
- connection.publish(subject, mailbox, data.to_java_bytes)
161
- connection.flush(nil)
98
+ connection.publish(subject, mailbox, data.to_java_bytes, true)
162
99
  end
163
100
 
164
101
  def subscribe(subject, options = {}, &block)
165
102
  queue = options[:queue]
166
103
  max = options[:max]
167
-
104
+ work_queue = nil
105
+ # We pass our work queue for processing async work because java nats
106
+ # uses a cahced thread pool: 1 thread per async subscription.
107
+ # Sync subs need their own queue so work is not processed async.
108
+ work_queue = block.nil? ? connection.createMsgChannel : @work_queue
109
+ sub = connection.subscribe(subject, queue, nil, work_queue)
110
+
111
+ # Register the block callback. We only lock to save the callback.
168
112
  if block
169
- handler = MessageHandlerProxy.new(&block)
170
- sub = subscribe_using_subscription_dispatcher(subject, queue, handler)
171
- # Auto unsub if max message option was provided.
172
- dispatcher.unsubscribe(sub, max) if max
173
- sub
174
- else
175
- sub = subscribe_using_connection(subject, queue)
176
- sub.unsubscribe(max) if max
177
- sub
113
+ @subz_mutex.synchronize do
114
+ @subz_cbs[sub.getSid] = block
115
+ end
178
116
  end
117
+
118
+ # Auto unsub if max message option was provided.
119
+ sub.autoUnsubscribe(max) if max
120
+
121
+ sub
179
122
  end
180
123
 
181
124
  def unsubscribe(sub)
182
125
  return if sub.nil?
183
- if sub.getDispatcher
184
- dispatcher.unsubscribe(sub)
185
- else
186
- sub.unsubscribe()
126
+
127
+ # Cleanup our async callback
128
+ if @subz_cbs[sub.getSid]
129
+ @subz_mutex.synchronize do
130
+ @subz_cbs.delete(sub.getSid)
131
+ end
187
132
  end
133
+
134
+ # The "true" here is to ignore and invalid conn.
135
+ sub.unsubscribe(true)
188
136
  end
189
137
 
190
138
  def new_inbox
@@ -192,23 +140,69 @@ module Protobuf
192
140
  end
193
141
 
194
142
  def on_reconnect(&cb)
195
- @connection_listener.on_reconnect(&cb)
143
+ @on_reconnect_cb = cb
196
144
  end
197
145
 
198
146
  def on_disconnect(&cb)
199
- @connection_listener.on_disconnect(&cb)
147
+ @on_disconnect_cb = cb
200
148
  end
201
149
 
202
150
  def on_error(&cb)
203
- @error_listener.on_exception(&cb)
151
+ @on_error_cb = cb
204
152
  end
205
153
 
206
154
  def on_close(&cb)
207
- @connection_listener.on_close(&cb)
155
+ @on_close_cb = cb
208
156
  end
209
157
 
210
158
  private
211
159
 
160
+ def spwan_supervisor_and_consumer
161
+ spawn_consumer
162
+ @supervisor = ::Thread.new do
163
+ loop do
164
+ begin
165
+ sleep 1
166
+ next if @consumer && @consumer.alive?
167
+ # We need to recreate the consumer thread
168
+ @consumer.kill if @consumer
169
+ spawn_consumer
170
+ rescue => error
171
+ @on_error_cb.call(error)
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ def spawn_consumer
178
+ @consumer = ::Thread.new do
179
+ loop do
180
+ begin
181
+ message = @work_queue.take
182
+ next unless message
183
+ sub = message.getSubscription
184
+
185
+ # We have to update the subscription stats so we're not considered a slow consumer.
186
+ begin
187
+ sub.lock
188
+ sub.incrPMsgs(-1)
189
+ sub.incrPBytes(-message.getData.length) if message.getData
190
+ sub.incrDelivered(1) unless sub.isClosed
191
+ ensure
192
+ sub.unlock
193
+ end
194
+
195
+ # We don't need t
196
+ callback = @subz_cbs[sub.getSid]
197
+ next unless callback
198
+ callback.call(message.getData.to_s, message.getReplyTo, message.getSubject)
199
+ rescue => error
200
+ @on_error_cb.call(error)
201
+ end
202
+ end
203
+ end
204
+ end
205
+
212
206
  # Jruby-openssl depends on bouncycastle so our lives don't suck super bad
213
207
  def read_pem_object_from_file(path)
214
208
  fail ::ArgumentError, "Tried to read a PEM key or cert with path nil" if path.nil?
@@ -252,30 +246,6 @@ module Protobuf
252
246
  context.init(key_manager.getKeyManagers, trust_manager.getTrustManagers, nil)
253
247
  context
254
248
  end
255
-
256
- def duration_in_ms(ms)
257
- ::Java::JavaTime::Duration.ofMillis(ms)
258
- end
259
-
260
- def subscribe_using_connection(subject, queue)
261
- if queue
262
- connection.subscribe(subject, queue)
263
- else
264
- connection.subscribe(subject)
265
- end
266
- end
267
-
268
- def subscribe_using_subscription_dispatcher(subject, queue, handler)
269
- if queue
270
- dispatcher.java_send(:subscribe,
271
- [::Java::JavaLang::String, ::Java::JavaLang::String, ::Java::IoNatsClient::MessageHandler],
272
- subject, queue, handler)
273
- else
274
- dispatcher.java_send(:subscribe,
275
- [::Java::JavaLang::String, ::Java::IoNatsClient::MessageHandler],
276
- subject, handler)
277
- end
278
- end
279
249
  end
280
250
  end
281
251
  end
@@ -0,0 +1,14 @@
1
+ module Protobuf
2
+ module Nats
3
+ def self.jruby?
4
+ return false if jnats_disabled?
5
+
6
+ defined? JRUBY_VERSION
7
+ end
8
+
9
+ def self.jnats_disabled?
10
+ !!ENV["PB_NATS_DISABLE_JNATS"]
11
+ end
12
+ end
13
+ end
14
+
@@ -6,11 +6,58 @@ require "protobuf/nats/thread_pool"
6
6
 
7
7
  module Protobuf
8
8
  module Nats
9
+ class SuperSubscriptionManager
10
+ def initialize(nats, &cb)
11
+ # Central queue used by all subscriptions
12
+ @pending_queue = ::SizedQueue.new(::NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT)
13
+ @subscriptions = []
14
+ @nats = nats
15
+ @callback = cb
16
+
17
+ # For MRI, reroute the pending queue to the callback
18
+ @pending_queue_handler = Thread.new do
19
+ loop do
20
+ msg = @pending_queue.pop
21
+ @callback.call(msg.data, msg.reply)
22
+ end
23
+ end
24
+ end
25
+
26
+ def queue_subscribe(name)
27
+ if ::Protobuf::Nats.jruby?
28
+ @subscriptions << @nats.subscribe(name, :queue => name) do |request_data, reply_id|
29
+ @callback.call(request_data, reply_id)
30
+ end
31
+ else
32
+ sub = @nats.subscribe(name, :queue => name)
33
+
34
+ # Create a subscription but reset the pending queue to use a central pending queue.
35
+ # NOTE: This is a potential race condition. Chances of the round-trip message to an
36
+ # existing queue before this queue swap happens seems extremely low, but possible.
37
+ sub.pending_queue = @pending_queue
38
+
39
+ @subscriptions << sub
40
+
41
+ sub
42
+ end
43
+ end
44
+
45
+ def unsubscribe_all
46
+ if ::Protobuf::Nats.jruby?
47
+ @subscriptions.each do |subscription_id|
48
+ @nats.unsubscribe(subscription_id)
49
+ end
50
+ else
51
+ @subscriptions.each { |sub| sub.unsubscribe }
52
+ end
53
+ end
54
+ end
55
+
9
56
  class Server
10
57
  include ::Protobuf::Rpc::Server
11
58
  include ::Protobuf::Logging
12
59
 
13
- attr_reader :nats, :thread_pool, :subscriptions
60
+ attr_reader :nats, :thread_pool, :subscription_manager
14
61
 
15
62
  MILLISECOND = 1000
16
63
 
@@ -25,7 +72,11 @@ module Protobuf
25
72
 
26
73
  @thread_pool = ::Protobuf::Nats::ThreadPool.new(@options[:threads], :max_queue => max_queue_size)
27
74
 
28
- @subscriptions = []
75
+ @subscription_manager = SuperSubscriptionManager.new(@nats) do |request_data, reply_id|
76
+ unless enqueue_request(request_data, reply_id)
77
+ logger.error { "Thread pool is full! Dropping message for: #{subscription_key_and_queue}" }
78
+ end
79
+ end
29
80
  @server = options.fetch(:server, ::Socket.gethostname)
30
81
  end
31
82
 
@@ -114,11 +165,7 @@ module Protobuf
114
165
 
115
166
  def subscribe_to_services_once
116
167
  with_each_subscription_key do |subscription_key_and_queue|
117
- subscriptions << nats.subscribe(subscription_key_and_queue, :queue => subscription_key_and_queue) do |request_data, reply_id, _subject|
118
- unless enqueue_request(request_data, reply_id)
119
- logger.error { "Thread pool is full! Dropping message for: #{subscription_key_and_queue}" }
120
- end
121
- end
168
+ subscription_manager.queue_subscribe(subscription_key_and_queue)
122
169
  end
123
170
  end
124
171
 
@@ -233,9 +280,7 @@ module Protobuf
233
280
 
234
281
  def unsubscribe
235
282
  logger.info "Unsubscribing from rpc routes..."
236
- subscriptions.each do |subscription_id|
237
- nats.unsubscribe(subscription_id)
238
- end
283
+ subscription_manager.unsubscribe_all
239
284
  end
240
285
  end
241
286
  end
@@ -1,5 +1,5 @@
1
1
  module Protobuf
2
2
  module Nats
3
- VERSION = "0.11.0.pre1"
3
+ VERSION = "0.12.0.pre0"
4
4
  end
5
5
  end
data/lib/protobuf/nats.rb CHANGED
@@ -6,6 +6,7 @@ require "protobuf/rpc/service_directory"
6
6
 
7
7
  require "nats/io/client"
8
8
 
9
+ require "protobuf/nats/platform"
9
10
  require "protobuf/nats/errors"
10
11
  require "protobuf/nats/client"
11
12
  require "protobuf/nats/server"
@@ -23,7 +24,7 @@ module Protobuf
23
24
  NACK = "\2".freeze
24
25
  end
25
26
 
26
- NatsClient = if defined? JRUBY_VERSION
27
+ NatsClient = if jruby?
27
28
  require "protobuf/nats/jnats"
28
29
  ::Protobuf::Nats::JNats
29
30
  else
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = %q{ ruby-protobuf client/server for nats }
13
13
  spec.description = %q{ ruby-protobuf client/server for nats }
14
- #spec.homepage = "TODO: Put your gem's website or public repo URL here."
14
+ spec.homepage = "https://github.com/mxenabled/protobuf-nats"
15
15
  spec.license = "MIT"
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_runtime_dependency "activesupport", ">= 3.2"
34
34
  spec.add_runtime_dependency "connection_pool"
35
35
  spec.add_runtime_dependency "protobuf", "~> 3.7", ">= 3.7.2"
36
- spec.add_runtime_dependency "nats-pure", "~> 0.3", "< 0.4"
36
+ spec.add_runtime_dependency "nats-pure", "~> 2"
37
37
 
38
38
  spec.add_development_dependency "bundler"
39
39
  spec.add_development_dependency "rake", "~> 10.0"
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ export DEFAULT_NATS_SERVER_VERSION=v2.0.0
6
+
7
+ export NATS_SERVER_VERSION="${NATS_SERVER_VERSION:=$DEFAULT_NATS_SERVER_VERSION}"
8
+
9
+ # check to see if nats-server folder is empty
10
+ if [ ! "$(ls -A $HOME/nats-server)" ]; then
11
+ (
12
+ mkdir -p $HOME/nats-server
13
+ cd $HOME/nats-server
14
+ wget https://github.com/nats-io/nats-server/releases/download/$NATS_SERVER_VERSION/nats-server-$NATS_SERVER_VERSION-linux-amd64.zip -O nats-server.zip
15
+ unzip nats-server.zip
16
+ cp nats-server-$NATS_SERVER_VERSION-linux-amd64/nats-server $HOME/nats-server/nats-server
17
+ )
18
+ else
19
+ echo 'Using cached directory.';
20
+ fi
metadata CHANGED
@@ -1,44 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protobuf-nats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0.pre1
4
+ version: 0.12.0.pre0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Dewitt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-21 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
+ name: activesupport
14
15
  requirement: !ruby/object:Gem::Requirement
15
16
  requirements:
16
17
  - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: '3.2'
19
- name: activesupport
20
- prerelease: false
21
20
  type: :runtime
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
+ name: connection_pool
28
29
  requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - ">="
31
32
  - !ruby/object:Gem::Version
32
33
  version: '0'
33
- name: connection_pool
34
- prerelease: false
35
34
  type: :runtime
35
+ prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
+ name: protobuf
42
43
  requirement: !ruby/object:Gem::Requirement
43
44
  requirements:
44
45
  - - "~>"
@@ -47,9 +48,8 @@ dependencies:
47
48
  - - ">="
48
49
  - !ruby/object:Gem::Version
49
50
  version: 3.7.2
50
- name: protobuf
51
- prerelease: false
52
51
  type: :runtime
52
+ prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - "~>"
@@ -59,90 +59,84 @@ dependencies:
59
59
  - !ruby/object:Gem::Version
60
60
  version: 3.7.2
61
61
  - !ruby/object:Gem::Dependency
62
+ name: nats-pure
62
63
  requirement: !ruby/object:Gem::Requirement
63
64
  requirements:
64
65
  - - "~>"
65
66
  - !ruby/object:Gem::Version
66
- version: '0.3'
67
- - - "<"
68
- - !ruby/object:Gem::Version
69
- version: '0.4'
70
- name: nats-pure
71
- prerelease: false
67
+ version: '2'
72
68
  type: :runtime
69
+ prerelease: false
73
70
  version_requirements: !ruby/object:Gem::Requirement
74
71
  requirements:
75
72
  - - "~>"
76
73
  - !ruby/object:Gem::Version
77
- version: '0.3'
78
- - - "<"
79
- - !ruby/object:Gem::Version
80
- version: '0.4'
74
+ version: '2'
81
75
  - !ruby/object:Gem::Dependency
76
+ name: bundler
82
77
  requirement: !ruby/object:Gem::Requirement
83
78
  requirements:
84
79
  - - ">="
85
80
  - !ruby/object:Gem::Version
86
81
  version: '0'
87
- name: bundler
88
- prerelease: false
89
82
  type: :development
83
+ prerelease: false
90
84
  version_requirements: !ruby/object:Gem::Requirement
91
85
  requirements:
92
86
  - - ">="
93
87
  - !ruby/object:Gem::Version
94
88
  version: '0'
95
89
  - !ruby/object:Gem::Dependency
90
+ name: rake
96
91
  requirement: !ruby/object:Gem::Requirement
97
92
  requirements:
98
93
  - - "~>"
99
94
  - !ruby/object:Gem::Version
100
95
  version: '10.0'
101
- name: rake
102
- prerelease: false
103
96
  type: :development
97
+ prerelease: false
104
98
  version_requirements: !ruby/object:Gem::Requirement
105
99
  requirements:
106
100
  - - "~>"
107
101
  - !ruby/object:Gem::Version
108
102
  version: '10.0'
109
103
  - !ruby/object:Gem::Dependency
104
+ name: rspec
110
105
  requirement: !ruby/object:Gem::Requirement
111
106
  requirements:
112
107
  - - ">="
113
108
  - !ruby/object:Gem::Version
114
109
  version: '0'
115
- name: rspec
116
- prerelease: false
117
110
  type: :development
111
+ prerelease: false
118
112
  version_requirements: !ruby/object:Gem::Requirement
119
113
  requirements:
120
114
  - - ">="
121
115
  - !ruby/object:Gem::Version
122
116
  version: '0'
123
117
  - !ruby/object:Gem::Dependency
118
+ name: benchmark-ips
124
119
  requirement: !ruby/object:Gem::Requirement
125
120
  requirements:
126
121
  - - ">="
127
122
  - !ruby/object:Gem::Version
128
123
  version: '0'
129
- name: benchmark-ips
130
- prerelease: false
131
124
  type: :development
125
+ prerelease: false
132
126
  version_requirements: !ruby/object:Gem::Requirement
133
127
  requirements:
134
128
  - - ">="
135
129
  - !ruby/object:Gem::Version
136
130
  version: '0'
137
131
  - !ruby/object:Gem::Dependency
132
+ name: pry
138
133
  requirement: !ruby/object:Gem::Requirement
139
134
  requirements:
140
135
  - - ">="
141
136
  - !ruby/object:Gem::Version
142
137
  version: '0'
143
- name: pry
144
- prerelease: false
145
138
  type: :development
139
+ prerelease: false
146
140
  version_requirements: !ruby/object:Gem::Requirement
147
141
  requirements:
148
142
  - - ">="
@@ -171,7 +165,7 @@ files:
171
165
  - examples/warehouse/start_client.sh
172
166
  - examples/warehouse/start_server.sh
173
167
  - ext/jars/gson-2.6.2.jar
174
- - ext/jars/jnats-2.6.0.jar
168
+ - ext/jars/jnats-1.1-SNAPSHOT.jar
175
169
  - ext/jars/slf4j-api-1.7.25.jar
176
170
  - ext/jars/slf4j-simple-1.7.25.jar
177
171
  - lib/protobuf/nats.rb
@@ -179,17 +173,19 @@ files:
179
173
  - lib/protobuf/nats/config.rb
180
174
  - lib/protobuf/nats/errors.rb
181
175
  - lib/protobuf/nats/jnats.rb
176
+ - lib/protobuf/nats/platform.rb
182
177
  - lib/protobuf/nats/runner.rb
183
178
  - lib/protobuf/nats/server.rb
184
179
  - lib/protobuf/nats/thread_pool.rb
185
180
  - lib/protobuf/nats/version.rb
186
181
  - protobuf-nats.gemspec
187
- homepage:
182
+ - scripts/install_gnatsd.sh
183
+ homepage: https://github.com/mxenabled/protobuf-nats
188
184
  licenses:
189
185
  - MIT
190
186
  metadata:
191
187
  allowed_push_host: https://rubygems.org
192
- post_install_message:
188
+ post_install_message:
193
189
  rdoc_options: []
194
190
  require_paths:
195
191
  - lib
@@ -204,9 +200,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
204
200
  - !ruby/object:Gem::Version
205
201
  version: 1.3.1
206
202
  requirements: []
207
- rubyforge_project:
208
- rubygems_version: 2.6.14.1
209
- signing_key:
203
+ rubygems_version: 3.1.6
204
+ signing_key:
210
205
  specification_version: 4
211
206
  summary: ruby-protobuf client/server for nats
212
207
  test_files: []
Binary file