protobuf-nats 0.11.0.pre1 → 0.12.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
- 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