nats-pure 2.3.0 → 2.5.0
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 +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +10 -3
- data/lib/nats/client.rb +7 -2
- data/lib/nats/io/client.rb +304 -282
- data/lib/nats/io/errors.rb +2 -0
- data/lib/nats/io/jetstream/api.rb +54 -47
- data/lib/nats/io/jetstream/errors.rb +30 -14
- data/lib/nats/io/jetstream/js/config.rb +9 -3
- data/lib/nats/io/jetstream/js/header.rb +15 -9
- data/lib/nats/io/jetstream/js/status.rb +11 -5
- data/lib/nats/io/jetstream/js/sub.rb +4 -2
- data/lib/nats/io/jetstream/js.rb +10 -8
- data/lib/nats/io/jetstream/manager.rb +104 -83
- data/lib/nats/io/jetstream/msg/ack.rb +15 -9
- data/lib/nats/io/jetstream/msg/ack_methods.rb +24 -22
- data/lib/nats/io/jetstream/msg/metadata.rb +9 -7
- data/lib/nats/io/jetstream/msg.rb +11 -4
- data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
- data/lib/nats/io/jetstream/push_subscription.rb +3 -1
- data/lib/nats/io/jetstream.rb +125 -54
- data/lib/nats/io/kv/api.rb +7 -3
- data/lib/nats/io/kv/bucket_status.rb +7 -5
- data/lib/nats/io/kv/errors.rb +25 -2
- data/lib/nats/io/kv/manager.rb +19 -10
- data/lib/nats/io/kv.rb +359 -22
- data/lib/nats/io/msg.rb +19 -19
- data/lib/nats/io/parser.rb +23 -23
- data/lib/nats/io/rails.rb +2 -0
- data/lib/nats/io/subscription.rb +25 -22
- data/lib/nats/io/version.rb +4 -2
- data/lib/nats/io/websocket.rb +10 -8
- data/lib/nats/nuid.rb +33 -22
- data/lib/nats/service/callbacks.rb +22 -0
- data/lib/nats/service/endpoint.rb +155 -0
- data/lib/nats/service/errors.rb +44 -0
- data/lib/nats/service/group.rb +37 -0
- data/lib/nats/service/monitoring.rb +108 -0
- data/lib/nats/service/stats.rb +52 -0
- data/lib/nats/service/status.rb +66 -0
- data/lib/nats/service/validator.rb +31 -0
- data/lib/nats/service.rb +121 -0
- data/lib/nats/utils/list.rb +26 -0
- data/lib/nats-pure.rb +5 -0
- data/lib/nats.rb +10 -6
- metadata +176 -5
data/lib/nats/io/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Copyright 2016-2021 The NATS Authors
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
5
|
# you may not use this file except in compliance with the License.
|
@@ -12,22 +14,20 @@
|
|
12
14
|
# limitations under the License.
|
13
15
|
#
|
14
16
|
|
15
|
-
require_relative
|
16
|
-
require_relative
|
17
|
-
require_relative
|
18
|
-
require_relative
|
19
|
-
require_relative
|
20
|
-
require_relative
|
21
|
-
|
22
|
-
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require 'securerandom'
|
30
|
-
require 'concurrent'
|
17
|
+
require_relative "parser"
|
18
|
+
require_relative "version"
|
19
|
+
require_relative "errors"
|
20
|
+
require_relative "msg"
|
21
|
+
require_relative "subscription"
|
22
|
+
require_relative "jetstream"
|
23
|
+
|
24
|
+
require "nats/nuid"
|
25
|
+
require "socket"
|
26
|
+
require "json"
|
27
|
+
require "monitor"
|
28
|
+
require "uri"
|
29
|
+
require "securerandom"
|
30
|
+
require "concurrent"
|
31
31
|
|
32
32
|
begin
|
33
33
|
require "openssl"
|
@@ -47,7 +47,7 @@ module NATS
|
|
47
47
|
# nc.publish("hello", "world")
|
48
48
|
# nc.close
|
49
49
|
#
|
50
|
-
def connect(uri=nil, opts={})
|
50
|
+
def connect(uri = nil, opts = {})
|
51
51
|
nc = NATS::Client.new
|
52
52
|
nc.connect(uri, opts)
|
53
53
|
|
@@ -101,25 +101,25 @@ module NATS
|
|
101
101
|
include MonitorMixin
|
102
102
|
include Status
|
103
103
|
|
104
|
-
attr_reader :status, :server_info, :server_pool, :options, :
|
104
|
+
attr_reader :status, :server_info, :server_pool, :options, :stats, :uri, :subscription_executor, :reloader
|
105
105
|
|
106
|
-
DEFAULT_PORT = {
|
107
|
-
DEFAULT_URI =
|
106
|
+
DEFAULT_PORT = {nats: 4222, ws: 80, wss: 443}.freeze
|
107
|
+
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT[:nats]}".freeze
|
108
108
|
|
109
|
-
CR_LF =
|
110
|
-
CR_LF_SIZE =
|
109
|
+
CR_LF = "\r\n"
|
110
|
+
CR_LF_SIZE = CR_LF.bytesize
|
111
111
|
|
112
|
-
PING_REQUEST
|
113
|
-
PONG_RESPONSE =
|
112
|
+
PING_REQUEST = "PING#{CR_LF}".freeze
|
113
|
+
PONG_RESPONSE = "PONG#{CR_LF}".freeze
|
114
114
|
|
115
|
-
NATS_HDR_LINE
|
115
|
+
NATS_HDR_LINE = "NATS/1.0#{CR_LF}".freeze
|
116
116
|
STATUS_MSG_LEN = 3
|
117
|
-
STATUS_HDR
|
118
|
-
DESC_HDR
|
119
|
-
NATS_HDR_LINE_SIZE =
|
117
|
+
STATUS_HDR = "Status"
|
118
|
+
DESC_HDR = "Description"
|
119
|
+
NATS_HDR_LINE_SIZE = NATS_HDR_LINE.bytesize
|
120
120
|
|
121
|
-
SUB_OP =
|
122
|
-
EMPTY_MSG =
|
121
|
+
SUB_OP = "SUB"
|
122
|
+
EMPTY_MSG = ""
|
123
123
|
|
124
124
|
INSTANCES = ObjectSpace::WeakMap.new # tracks all alive client instances
|
125
125
|
private_constant :INSTANCES
|
@@ -136,6 +136,8 @@ module NATS
|
|
136
136
|
# Re-establish connection in a new process after forking to start new threads.
|
137
137
|
def after_fork
|
138
138
|
INSTANCES.each do |client|
|
139
|
+
next if client.closed?
|
140
|
+
|
139
141
|
if client.options[:reconnect]
|
140
142
|
was_connected = !client.disconnected?
|
141
143
|
client.send(:close_connection, Status::DISCONNECTED, true)
|
@@ -171,7 +173,7 @@ module NATS
|
|
171
173
|
@ping_interval_thread = nil
|
172
174
|
|
173
175
|
# Info that we get from the server
|
174
|
-
@server_info = {
|
176
|
+
@server_info = {}
|
175
177
|
|
176
178
|
# URI from server to which we are currently connected
|
177
179
|
@uri = nil
|
@@ -180,7 +182,7 @@ module NATS
|
|
180
182
|
@status = nil
|
181
183
|
|
182
184
|
# Subscriptions
|
183
|
-
@subs = {
|
185
|
+
@subs = {}
|
184
186
|
@ssid = 0
|
185
187
|
|
186
188
|
# Ping interval
|
@@ -203,10 +205,10 @@ module NATS
|
|
203
205
|
@last_err = nil
|
204
206
|
|
205
207
|
# Async callbacks, no ops by default.
|
206
|
-
@err_cb = proc {
|
207
|
-
@close_cb = proc {
|
208
|
-
@disconnect_cb = proc {
|
209
|
-
@reconnect_cb = proc {
|
208
|
+
@err_cb = proc {}
|
209
|
+
@close_cb = proc {}
|
210
|
+
@disconnect_cb = proc {}
|
211
|
+
@reconnect_cb = proc {}
|
210
212
|
|
211
213
|
# Secure TLS options
|
212
214
|
@tls = nil
|
@@ -240,6 +242,9 @@ module NATS
|
|
240
242
|
# Draining
|
241
243
|
@drain_t = nil
|
242
244
|
|
245
|
+
# Service API
|
246
|
+
@_services = nil
|
247
|
+
|
243
248
|
# Prepare for calling connect or automatic delayed connection
|
244
249
|
parse_and_validate_options if uri || opts.any?
|
245
250
|
|
@@ -250,7 +255,7 @@ module NATS
|
|
250
255
|
end
|
251
256
|
|
252
257
|
# Prepare connecting to NATS, but postpone real connection until first usage.
|
253
|
-
def connect(uri=nil, opts={})
|
258
|
+
def connect(uri = nil, opts = {})
|
254
259
|
if uri || opts.any?
|
255
260
|
@initial_uri = uri
|
256
261
|
@initial_options = opts
|
@@ -268,6 +273,19 @@ module NATS
|
|
268
273
|
self
|
269
274
|
end
|
270
275
|
|
276
|
+
def force_reconnect
|
277
|
+
synchronize do
|
278
|
+
return true if reconnecting?
|
279
|
+
|
280
|
+
if closed? || draining? || disconnected?
|
281
|
+
raise NATS::IO::ConnectionClosedError
|
282
|
+
end
|
283
|
+
|
284
|
+
initiate_reconnect
|
285
|
+
true
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
271
289
|
private def parse_and_validate_options
|
272
290
|
# Reset these in case we have reconnected via fork.
|
273
291
|
@server_pool = []
|
@@ -294,8 +312,8 @@ module NATS
|
|
294
312
|
when String
|
295
313
|
# Initialize TLS defaults in case any url is using it.
|
296
314
|
srvs = opts[:servers] = process_uri(uri)
|
297
|
-
if srvs.any? {|u| %w[tls wss].include? u.scheme }
|
298
|
-
opts[:tls] = {
|
315
|
+
if srvs.any? { |u| %w[tls wss].include? u.scheme } && !opts[:tls]
|
316
|
+
opts[:tls] = {context: tls_context}
|
299
317
|
end
|
300
318
|
@single_url_connect_used = true if srvs.size == 1
|
301
319
|
when Hash
|
@@ -313,16 +331,17 @@ module NATS
|
|
313
331
|
opts[:max_outstanding_pings] = NATS::IO::DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
|
314
332
|
|
315
333
|
# Override with ENV
|
316
|
-
opts[:verbose] = ENV[
|
317
|
-
opts[:pedantic] = ENV[
|
318
|
-
opts[:reconnect] = ENV[
|
319
|
-
opts[:reconnect_time_wait] = ENV[
|
320
|
-
opts[:ignore_discovered_urls] = ENV[
|
321
|
-
opts[:max_reconnect_attempts] = ENV[
|
322
|
-
opts[:ping_interval] = ENV[
|
323
|
-
opts[:max_outstanding_pings] = ENV[
|
334
|
+
opts[:verbose] = ENV["NATS_VERBOSE"].downcase == "true" unless ENV["NATS_VERBOSE"].nil?
|
335
|
+
opts[:pedantic] = ENV["NATS_PEDANTIC"].downcase == "true" unless ENV["NATS_PEDANTIC"].nil?
|
336
|
+
opts[:reconnect] = ENV["NATS_RECONNECT"].downcase == "true" unless ENV["NATS_RECONNECT"].nil?
|
337
|
+
opts[:reconnect_time_wait] = ENV["NATS_RECONNECT_TIME_WAIT"].to_i unless ENV["NATS_RECONNECT_TIME_WAIT"].nil?
|
338
|
+
opts[:ignore_discovered_urls] = ENV["NATS_IGNORE_DISCOVERED_URLS"].downcase == "true" unless ENV["NATS_IGNORE_DISCOVERED_URLS"].nil?
|
339
|
+
opts[:max_reconnect_attempts] = ENV["NATS_MAX_RECONNECT_ATTEMPTS"].to_i unless ENV["NATS_MAX_RECONNECT_ATTEMPTS"].nil?
|
340
|
+
opts[:ping_interval] = ENV["NATS_PING_INTERVAL"].to_i unless ENV["NATS_PING_INTERVAL"].nil?
|
341
|
+
opts[:max_outstanding_pings] = ENV["NATS_MAX_OUTSTANDING_PINGS"].to_i unless ENV["NATS_MAX_OUTSTANDING_PINGS"].nil?
|
324
342
|
opts[:connect_timeout] ||= NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
325
343
|
opts[:drain_timeout] ||= NATS::IO::DEFAULT_DRAIN_TIMEOUT
|
344
|
+
opts[:close_timeout] ||= NATS::IO::DEFAULT_CLOSE_TIMEOUT
|
326
345
|
@options = opts
|
327
346
|
|
328
347
|
# Process servers in the NATS cluster and pick one to connect
|
@@ -330,14 +349,14 @@ module NATS
|
|
330
349
|
uris.shuffle! unless @options[:dont_randomize_servers]
|
331
350
|
uris.each do |u|
|
332
351
|
nats_uri = case u
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
352
|
+
when URI
|
353
|
+
u.dup
|
354
|
+
else
|
355
|
+
URI.parse(u)
|
356
|
+
end
|
338
357
|
@server_pool << {
|
339
|
-
:
|
340
|
-
:
|
358
|
+
uri: nats_uri,
|
359
|
+
hostname: nats_uri.hostname
|
341
360
|
}
|
342
361
|
end
|
343
362
|
|
@@ -354,7 +373,7 @@ module NATS
|
|
354
373
|
@user_credentials ||= opts[:user_credentials]
|
355
374
|
@nkeys_seed ||= opts[:nkeys_seed]
|
356
375
|
|
357
|
-
setup_nkeys_connect if @user_credentials
|
376
|
+
setup_nkeys_connect if @user_credentials || @nkeys_seed
|
358
377
|
|
359
378
|
# Tokens, if set will take preference over the user@server uri token
|
360
379
|
@auth_token ||= opts[:auth_token]
|
@@ -377,7 +396,7 @@ module NATS
|
|
377
396
|
srv = select_next_server
|
378
397
|
|
379
398
|
# Use the hostname from the server for TLS hostname verification.
|
380
|
-
if client_using_secure_connection?
|
399
|
+
if client_using_secure_connection? && single_url_connect_used?
|
381
400
|
# Always reuse the original hostname used to connect.
|
382
401
|
@hostname ||= srv[:hostname]
|
383
402
|
else
|
@@ -450,8 +469,8 @@ module NATS
|
|
450
469
|
self
|
451
470
|
end
|
452
471
|
|
453
|
-
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, **options, &blk)
|
454
|
-
raise NATS::IO::BadSubject if !subject
|
472
|
+
def publish(subject, msg = EMPTY_MSG, opt_reply = nil, **options, &blk)
|
473
|
+
raise NATS::IO::BadSubject if !subject || subject.empty?
|
455
474
|
if options[:header]
|
456
475
|
return publish_msg(NATS::Msg.new(subject: subject, data: msg, reply: opt_reply, header: options[:header]))
|
457
476
|
end
|
@@ -468,10 +487,10 @@ module NATS
|
|
468
487
|
# Publishes a NATS::Msg that may include headers.
|
469
488
|
def publish_msg(msg)
|
470
489
|
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
471
|
-
raise NATS::IO::BadSubject if !msg.subject
|
490
|
+
raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty?
|
472
491
|
|
473
|
-
msg.reply ||=
|
474
|
-
msg.data ||=
|
492
|
+
msg.reply ||= "".dup
|
493
|
+
msg.data ||= "".dup
|
475
494
|
msg_size = msg.data.bytesize
|
476
495
|
|
477
496
|
# Accounting
|
@@ -479,7 +498,7 @@ module NATS
|
|
479
498
|
@stats[:out_bytes] += msg_size
|
480
499
|
|
481
500
|
if msg.header
|
482
|
-
hdr =
|
501
|
+
hdr = "".dup
|
483
502
|
hdr << NATS_HDR_LINE
|
484
503
|
msg.header.each do |k, v|
|
485
504
|
hdr << "#{k}: #{v}#{CR_LF}"
|
@@ -497,7 +516,7 @@ module NATS
|
|
497
516
|
|
498
517
|
# Create subscription which is dispatched asynchronously
|
499
518
|
# messages to a callback.
|
500
|
-
def subscribe(subject, opts={}, &callback)
|
519
|
+
def subscribe(subject, opts = {}, &callback)
|
501
520
|
raise NATS::IO::ConnectionDrainingError.new("nats: connection draining") if draining?
|
502
521
|
|
503
522
|
sid = nil
|
@@ -508,7 +527,7 @@ module NATS
|
|
508
527
|
sub.nc = self
|
509
528
|
sub.sid = sid
|
510
529
|
end
|
511
|
-
opts[:pending_msgs_limit]
|
530
|
+
opts[:pending_msgs_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
512
531
|
opts[:pending_bytes_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
513
532
|
|
514
533
|
sub.subject = subject
|
@@ -516,7 +535,7 @@ module NATS
|
|
516
535
|
sub.received = 0
|
517
536
|
sub.queue = opts[:queue] if opts[:queue]
|
518
537
|
sub.max = opts[:max] if opts[:max]
|
519
|
-
sub.pending_msgs_limit
|
538
|
+
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
520
539
|
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
521
540
|
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
522
541
|
sub.processing_concurrency = opts[:processing_concurrency] if opts.key?(:processing_concurrency)
|
@@ -540,8 +559,8 @@ module NATS
|
|
540
559
|
# It times out in case the request is not retrieved within the
|
541
560
|
# specified deadline.
|
542
561
|
# If given a callback, then the request happens asynchronously.
|
543
|
-
def request(subject, payload="", **opts, &blk)
|
544
|
-
raise NATS::IO::BadSubject if !subject
|
562
|
+
def request(subject, payload = "", **opts, &blk)
|
563
|
+
raise NATS::IO::BadSubject if !subject || subject.empty?
|
545
564
|
|
546
565
|
# If a block was given then fallback to method using auto unsubscribe.
|
547
566
|
return old_request(subject, payload, opts, &blk) if blk
|
@@ -572,7 +591,7 @@ module NATS
|
|
572
591
|
# Publish request and wait for reply.
|
573
592
|
publish(subject, payload, inbox)
|
574
593
|
begin
|
575
|
-
MonotonicTime
|
594
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
576
595
|
@resp_sub.synchronize do
|
577
596
|
future.wait(timeout)
|
578
597
|
end
|
@@ -589,7 +608,7 @@ module NATS
|
|
589
608
|
@resp_map.delete(token)
|
590
609
|
end
|
591
610
|
|
592
|
-
if response
|
611
|
+
if response&.header
|
593
612
|
status = response.header[STATUS_HDR]
|
594
613
|
raise NATS::IO::NoRespondersError if status == "503"
|
595
614
|
end
|
@@ -600,7 +619,7 @@ module NATS
|
|
600
619
|
# request_msg makes a NATS request using a NATS::Msg that may include headers.
|
601
620
|
def request_msg(msg, **opts)
|
602
621
|
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
603
|
-
raise NATS::IO::BadSubject if !msg.subject
|
622
|
+
raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty?
|
604
623
|
|
605
624
|
token = nil
|
606
625
|
inbox = nil
|
@@ -620,13 +639,13 @@ module NATS
|
|
620
639
|
@resp_map[token][:future] = future
|
621
640
|
end
|
622
641
|
msg.reply = inbox
|
623
|
-
msg.data ||=
|
624
|
-
|
642
|
+
msg.data ||= ""
|
643
|
+
msg.data.bytesize
|
625
644
|
|
626
645
|
# Publish request and wait for reply.
|
627
646
|
publish_msg(msg)
|
628
647
|
begin
|
629
|
-
MonotonicTime
|
648
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
630
649
|
@resp_sub.synchronize do
|
631
650
|
future.wait(timeout)
|
632
651
|
end
|
@@ -643,7 +662,7 @@ module NATS
|
|
643
662
|
@resp_map.delete(token)
|
644
663
|
end
|
645
664
|
|
646
|
-
if response
|
665
|
+
if response&.header
|
647
666
|
status = response.header[STATUS_HDR]
|
648
667
|
raise NATS::IO::NoRespondersError if status == "503"
|
649
668
|
end
|
@@ -655,7 +674,7 @@ module NATS
|
|
655
674
|
# expecting a single response or raising a timeout in case the request
|
656
675
|
# is not retrieved within the specified deadline.
|
657
676
|
# If given a callback, then the request happens asynchronously.
|
658
|
-
def old_request(subject, payload, opts={}, &blk)
|
677
|
+
def old_request(subject, payload, opts = {}, &blk)
|
659
678
|
return unless subject
|
660
679
|
inbox = new_inbox
|
661
680
|
|
@@ -704,13 +723,13 @@ module NATS
|
|
704
723
|
# Publish the request and then wait for the response...
|
705
724
|
publish(subject, payload, inbox)
|
706
725
|
|
707
|
-
MonotonicTime
|
726
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
708
727
|
future.wait(timeout)
|
709
728
|
end
|
710
729
|
end
|
711
730
|
response = sub.response
|
712
731
|
|
713
|
-
if response
|
732
|
+
if response&.header
|
714
733
|
status = response.header[STATUS_HDR]
|
715
734
|
raise NATS::IO::NoRespondersError if status == "503"
|
716
735
|
end
|
@@ -719,7 +738,7 @@ module NATS
|
|
719
738
|
end
|
720
739
|
|
721
740
|
# Send a ping and wait for a pong back within a timeout.
|
722
|
-
def flush(timeout=10)
|
741
|
+
def flush(timeout = 10)
|
723
742
|
# Schedule sending a PING, and block until we receive PONG back,
|
724
743
|
# or raise a timeout in case the response is past the deadline.
|
725
744
|
pong = @pongs.new_cond
|
@@ -729,18 +748,18 @@ module NATS
|
|
729
748
|
# Flush once pong future has been prepared
|
730
749
|
@pending_queue << PING_REQUEST
|
731
750
|
@flush_queue << :ping
|
732
|
-
MonotonicTime
|
751
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
733
752
|
pong.wait(timeout)
|
734
753
|
end
|
735
754
|
end
|
736
755
|
end
|
737
756
|
|
738
|
-
|
757
|
+
alias_method :servers, :server_pool
|
739
758
|
|
740
759
|
# discovered_servers returns the NATS Servers that have been discovered
|
741
760
|
# via INFO protocol updates.
|
742
761
|
def discovered_servers
|
743
|
-
servers.select {|s| s[:discovered] }
|
762
|
+
servers.select { |s| s[:discovered] }
|
744
763
|
end
|
745
764
|
|
746
765
|
# Close connection to NATS, flushing in case connection is alive
|
@@ -781,7 +800,7 @@ module NATS
|
|
781
800
|
end
|
782
801
|
|
783
802
|
def draining?
|
784
|
-
if @status == DRAINING_PUBS
|
803
|
+
if (@status == DRAINING_PUBS) || (@status == DRAINING_SUBS)
|
785
804
|
return true
|
786
805
|
end
|
787
806
|
|
@@ -834,12 +853,16 @@ module NATS
|
|
834
853
|
# @option params [String] :domain JetStream Domain to use for the requests.
|
835
854
|
# @option params [Float] :timeout Default timeout to use for JS requests.
|
836
855
|
# @return [NATS::JetStream]
|
837
|
-
def jetstream(opts={})
|
856
|
+
def jetstream(opts = {})
|
838
857
|
::NATS::JetStream.new(self, opts)
|
839
858
|
end
|
840
859
|
alias_method :JetStream, :jetstream
|
841
860
|
alias_method :jsm, :jetstream
|
842
861
|
|
862
|
+
def services
|
863
|
+
synchronize { @_services ||= Services.new(self) }
|
864
|
+
end
|
865
|
+
|
843
866
|
private
|
844
867
|
|
845
868
|
def validate_settings!
|
@@ -856,10 +879,8 @@ module NATS
|
|
856
879
|
# so has to be done under the lock.
|
857
880
|
synchronize do
|
858
881
|
# Symbolize keys from parsed info line
|
859
|
-
@server_info = parsed_info.
|
882
|
+
@server_info = parsed_info.each_with_object({}) do |(k, v), info|
|
860
883
|
info[k.to_sym] = v
|
861
|
-
|
862
|
-
info
|
863
884
|
end
|
864
885
|
|
865
886
|
# Detect any announced server that we might not be aware of...
|
@@ -878,7 +899,7 @@ module NATS
|
|
878
899
|
srv[:uri].hostname == u.hostname && srv[:uri].port == u.port
|
879
900
|
end
|
880
901
|
|
881
|
-
if
|
902
|
+
if !present
|
882
903
|
# Let explicit user and pass options set the credentials.
|
883
904
|
u.user = options[:user] if options[:user]
|
884
905
|
u.password = options[:pass] if options[:pass]
|
@@ -890,7 +911,7 @@ module NATS
|
|
890
911
|
end
|
891
912
|
|
892
913
|
# NOTE: Auto discovery won't work here when TLS host verification is enabled.
|
893
|
-
srv = {
|
914
|
+
srv = {uri: u, reconnect_attempts: 0, discovered: true, hostname: u.hostname}
|
894
915
|
srvs << srv
|
895
916
|
end
|
896
917
|
end
|
@@ -913,13 +934,13 @@ module NATS
|
|
913
934
|
# Check if the first line has an inline status and description.
|
914
935
|
if lines.count > 0
|
915
936
|
status_hdr = lines.first.rstrip
|
916
|
-
status = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
937
|
+
status = status_hdr.slice(NATS_HDR_LINE_SIZE - 1, STATUS_MSG_LEN)
|
917
938
|
|
918
|
-
if status
|
939
|
+
if status && !status.empty?
|
919
940
|
hdr[STATUS_HDR] = status
|
920
941
|
|
921
|
-
if NATS_HDR_LINE_SIZE+2 < status_hdr.bytesize
|
922
|
-
desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize)
|
942
|
+
if NATS_HDR_LINE_SIZE + 2 < status_hdr.bytesize
|
943
|
+
desc = status_hdr.slice(NATS_HDR_LINE_SIZE + STATUS_MSG_LEN, status_hdr.bytesize)
|
923
944
|
hdr[DESC_HDR] = desc unless desc.empty?
|
924
945
|
end
|
925
946
|
end
|
@@ -932,7 +953,7 @@ module NATS
|
|
932
953
|
hdr[key] = value
|
933
954
|
end
|
934
955
|
rescue => e
|
935
|
-
|
956
|
+
e
|
936
957
|
end
|
937
958
|
end
|
938
959
|
|
@@ -945,7 +966,7 @@ module NATS
|
|
945
966
|
# Take first pong wait and signal any flush in case there was one
|
946
967
|
@pongs.synchronize do
|
947
968
|
pong = @pongs.pop
|
948
|
-
pong
|
969
|
+
pong&.signal
|
949
970
|
end
|
950
971
|
@pings_outstanding -= 1
|
951
972
|
@pongs_received += 1
|
@@ -965,10 +986,9 @@ module NATS
|
|
965
986
|
# while holding the lock.
|
966
987
|
e = synchronize do
|
967
988
|
current = server_pool.first
|
968
|
-
|
969
|
-
when err =~ /'Stale Connection'/
|
989
|
+
if err =~ /'Stale Connection'/
|
970
990
|
@last_err = NATS::IO::StaleConnectionError.new(err)
|
971
|
-
|
991
|
+
elsif current && current[:auth_required]
|
972
992
|
# We cannot recover from auth errors so mark it to avoid
|
973
993
|
# retrying to unecessarily next time.
|
974
994
|
current[:error_received] = true
|
@@ -1018,8 +1038,8 @@ module NATS
|
|
1018
1038
|
elsif sub.pending_queue
|
1019
1039
|
# Async subscribers use a sized queue for processing
|
1020
1040
|
# and should be able to consume messages in parallel.
|
1021
|
-
if sub.pending_queue.size >= sub.pending_msgs_limit \
|
1022
|
-
|
1041
|
+
if (sub.pending_queue.size >= sub.pending_msgs_limit) \
|
1042
|
+
|| (sub.pending_size >= sub.pending_bytes_limit)
|
1023
1043
|
err = NATS::IO::SlowConsumer.new("nats: slow consumer, messages dropped")
|
1024
1044
|
else
|
1025
1045
|
hdr = process_hdr(header)
|
@@ -1033,10 +1053,12 @@ module NATS
|
|
1033
1053
|
end
|
1034
1054
|
end
|
1035
1055
|
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1056
|
+
if err
|
1057
|
+
synchronize do
|
1058
|
+
@last_err = err
|
1059
|
+
err_cb_call(self, err, sub) if @err_cb
|
1060
|
+
end
|
1061
|
+
end
|
1040
1062
|
end
|
1041
1063
|
|
1042
1064
|
def select_next_server
|
@@ -1102,7 +1124,7 @@ module NATS
|
|
1102
1124
|
|
1103
1125
|
# Auto unsubscribes the server by sending UNSUB command and throws away
|
1104
1126
|
# subscription in case already present and has received enough messages.
|
1105
|
-
def unsubscribe(sub, opt_max=nil)
|
1127
|
+
def unsubscribe(sub, opt_max = nil)
|
1106
1128
|
sid = nil
|
1107
1129
|
closed = nil
|
1108
1130
|
sub.synchronize do
|
@@ -1119,7 +1141,7 @@ module NATS
|
|
1119
1141
|
return unless sub
|
1120
1142
|
synchronize do
|
1121
1143
|
sub.max = opt_max
|
1122
|
-
@subs.delete(sid) unless
|
1144
|
+
@subs.delete(sid) unless sub.max && (sub.received < sub.max)
|
1123
1145
|
end
|
1124
1146
|
|
1125
1147
|
sub.synchronize do
|
@@ -1139,8 +1161,9 @@ module NATS
|
|
1139
1161
|
send_command("UNSUB #{sid}#{CR_LF}")
|
1140
1162
|
@flush_queue << :drain
|
1141
1163
|
|
1164
|
+
sub.synchronize { sub.drained = true }
|
1142
1165
|
synchronize { sub = @subs[sid] }
|
1143
|
-
|
1166
|
+
nil unless sub
|
1144
1167
|
end
|
1145
1168
|
|
1146
1169
|
def do_drain
|
@@ -1157,16 +1180,16 @@ module NATS
|
|
1157
1180
|
force_flush!
|
1158
1181
|
|
1159
1182
|
# Wait until all subs have no pending messages.
|
1160
|
-
drain_timeout = MonotonicTime
|
1183
|
+
drain_timeout = MonotonicTime.now + @options[:drain_timeout]
|
1161
1184
|
to_delete = []
|
1162
1185
|
|
1163
1186
|
loop do
|
1164
|
-
break if MonotonicTime
|
1187
|
+
break if MonotonicTime.now > drain_timeout
|
1165
1188
|
sleep 0.1
|
1166
1189
|
|
1167
1190
|
# Wait until all subs are done.
|
1168
1191
|
@subs.each do |_, sub|
|
1169
|
-
if sub != @resp_sub
|
1192
|
+
if (sub != @resp_sub) && (sub.pending_queue.size == 0)
|
1170
1193
|
to_delete << sub
|
1171
1194
|
end
|
1172
1195
|
end
|
@@ -1179,7 +1202,7 @@ module NATS
|
|
1179
1202
|
|
1180
1203
|
# Wait until only the resp mux is remaining or there are no subscriptions.
|
1181
1204
|
if @subs.count == 1
|
1182
|
-
|
1205
|
+
_, sub = @subs.first
|
1183
1206
|
if sub == @resp_sub
|
1184
1207
|
break
|
1185
1208
|
end
|
@@ -1191,7 +1214,7 @@ module NATS
|
|
1191
1214
|
subscription_executor.shutdown
|
1192
1215
|
subscription_executor.wait_for_termination(@options[:drain_timeout])
|
1193
1216
|
|
1194
|
-
if MonotonicTime
|
1217
|
+
if MonotonicTime.now > drain_timeout
|
1195
1218
|
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
1196
1219
|
err_cb_call(self, e, nil) if @err_cb
|
1197
1220
|
end
|
@@ -1228,27 +1251,26 @@ module NATS
|
|
1228
1251
|
|
1229
1252
|
def connect_command
|
1230
1253
|
cs = {
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1254
|
+
verbose: @options[:verbose],
|
1255
|
+
pedantic: @options[:pedantic],
|
1256
|
+
lang: NATS::IO::LANG,
|
1257
|
+
version: NATS::IO::VERSION,
|
1258
|
+
protocol: NATS::IO::PROTOCOL
|
1236
1259
|
}
|
1237
1260
|
cs[:name] = @options[:name] if @options[:name]
|
1238
1261
|
|
1239
|
-
|
1240
|
-
when auth_connection?
|
1262
|
+
if auth_connection?
|
1241
1263
|
if @uri.password
|
1242
1264
|
cs[:user] = @uri.user
|
1243
1265
|
cs[:pass] = @uri.password
|
1244
1266
|
else
|
1245
1267
|
cs[:auth_token] = @uri.user
|
1246
1268
|
end
|
1247
|
-
|
1269
|
+
elsif @user_jwt_cb && @signature_cb
|
1248
1270
|
nonce = @server_info[:nonce]
|
1249
1271
|
cs[:jwt] = @user_jwt_cb.call
|
1250
1272
|
cs[:sig] = @signature_cb.call(nonce)
|
1251
|
-
|
1273
|
+
elsif @user_nkey_cb && @signature_cb
|
1252
1274
|
nonce = @server_info[:nonce]
|
1253
1275
|
cs[:nkey] = @user_nkey_cb.call
|
1254
1276
|
cs[:sig] = @signature_cb.call(nonce)
|
@@ -1259,10 +1281,10 @@ module NATS
|
|
1259
1281
|
if @server_info[:headers]
|
1260
1282
|
cs[:headers] = @server_info[:headers]
|
1261
1283
|
cs[:no_responders] = if @options[:no_responders] == false
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1284
|
+
@options[:no_responders]
|
1285
|
+
else
|
1286
|
+
@server_info[:headers]
|
1287
|
+
end
|
1266
1288
|
end
|
1267
1289
|
|
1268
1290
|
"CONNECT #{cs.to_json}#{CR_LF}"
|
@@ -1282,30 +1304,8 @@ module NATS
|
|
1282
1304
|
|
1283
1305
|
# If we were connected and configured to reconnect,
|
1284
1306
|
# then trigger disconnect and start reconnection logic
|
1285
|
-
if connected?
|
1286
|
-
|
1287
|
-
@io.close if @io
|
1288
|
-
@io = nil
|
1289
|
-
|
1290
|
-
# TODO: Reconnecting pending buffer?
|
1291
|
-
|
1292
|
-
# Do reconnect under a different thread than the one
|
1293
|
-
# in which we got the error.
|
1294
|
-
Thread.new do
|
1295
|
-
begin
|
1296
|
-
# Abort currently running reads in case they're around
|
1297
|
-
# FIXME: There might be more graceful way here...
|
1298
|
-
@read_loop_thread.exit if @read_loop_thread.alive?
|
1299
|
-
@flusher_thread.exit if @flusher_thread.alive?
|
1300
|
-
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
1301
|
-
|
1302
|
-
attempt_reconnect
|
1303
|
-
rescue NATS::IO::NoServersError => e
|
1304
|
-
@last_err = e
|
1305
|
-
close
|
1306
|
-
end
|
1307
|
-
end
|
1308
|
-
|
1307
|
+
if connected? && should_reconnect?
|
1308
|
+
initiate_reconnect
|
1309
1309
|
Thread.exit
|
1310
1310
|
return
|
1311
1311
|
end
|
@@ -1318,29 +1318,52 @@ module NATS
|
|
1318
1318
|
close
|
1319
1319
|
end
|
1320
1320
|
|
1321
|
+
def initiate_reconnect
|
1322
|
+
@status = RECONNECTING
|
1323
|
+
@io&.close
|
1324
|
+
@io = nil
|
1325
|
+
|
1326
|
+
# TODO: Reconnecting pending buffer?
|
1327
|
+
|
1328
|
+
# Do reconnect under a different thread than the one
|
1329
|
+
# in which we got the error.
|
1330
|
+
Thread.new do
|
1331
|
+
# Abort currently running reads in case they're around
|
1332
|
+
# FIXME: There might be more graceful way here...
|
1333
|
+
@read_loop_thread.exit if @read_loop_thread.alive?
|
1334
|
+
@flusher_thread.exit if @flusher_thread.alive?
|
1335
|
+
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
1336
|
+
|
1337
|
+
@subscription_executor.shutdown
|
1338
|
+
|
1339
|
+
attempt_reconnect
|
1340
|
+
rescue NATS::IO::NoServersError => e
|
1341
|
+
@last_err = e
|
1342
|
+
close
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1321
1346
|
# Gathers data from the socket and sends it to the parser.
|
1322
1347
|
def read_loop
|
1323
1348
|
loop do
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
return
|
1331
|
-
end
|
1332
|
-
|
1333
|
-
# TODO: Remove timeout and just wait to be ready
|
1334
|
-
data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES)
|
1335
|
-
@parser.parse(data) if data
|
1336
|
-
rescue Errno::ETIMEDOUT
|
1337
|
-
# FIXME: We do not really need a timeout here...
|
1338
|
-
retry
|
1339
|
-
rescue => e
|
1340
|
-
# In case of reading/parser errors, trigger
|
1341
|
-
# reconnection logic in case desired.
|
1342
|
-
process_op_error(e)
|
1349
|
+
should_bail = synchronize do
|
1350
|
+
# FIXME: In case of reconnect as well?
|
1351
|
+
@status == CLOSED or @status == RECONNECTING
|
1352
|
+
end
|
1353
|
+
if !@io || @io.closed? || should_bail
|
1354
|
+
return
|
1343
1355
|
end
|
1356
|
+
|
1357
|
+
# TODO: Remove timeout and just wait to be ready
|
1358
|
+
data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES)
|
1359
|
+
@parser.parse(data) if data
|
1360
|
+
rescue Errno::ETIMEDOUT
|
1361
|
+
# FIXME: We do not really need a timeout here...
|
1362
|
+
retry
|
1363
|
+
rescue => e
|
1364
|
+
# In case of reading/parser errors, trigger
|
1365
|
+
# reconnection logic in case desired.
|
1366
|
+
process_op_error(e)
|
1344
1367
|
end
|
1345
1368
|
end
|
1346
1369
|
|
@@ -1352,7 +1375,7 @@ module NATS
|
|
1352
1375
|
@flush_queue.pop
|
1353
1376
|
|
1354
1377
|
should_bail = synchronize do
|
1355
|
-
(@status != CONNECTED && !draining?
|
1378
|
+
(@status != CONNECTED && !draining?) || @status == CONNECTING
|
1356
1379
|
end
|
1357
1380
|
return if should_bail
|
1358
1381
|
|
@@ -1373,17 +1396,19 @@ module NATS
|
|
1373
1396
|
# until reaching the max pending queue size.
|
1374
1397
|
cmds = []
|
1375
1398
|
cmds << @pending_queue.pop until @pending_queue.empty?
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1399
|
+
if @io
|
1400
|
+
begin
|
1401
|
+
@io.write(cmds.join) unless cmds.empty?
|
1402
|
+
rescue => e
|
1403
|
+
synchronize do
|
1404
|
+
@last_err = e
|
1405
|
+
err_cb_call(self, e, nil) if @err_cb
|
1406
|
+
end
|
1383
1407
|
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1408
|
+
process_op_error(e)
|
1409
|
+
nil
|
1410
|
+
end
|
1411
|
+
end
|
1387
1412
|
end
|
1388
1413
|
|
1389
1414
|
def ping_interval_loop
|
@@ -1409,27 +1434,26 @@ module NATS
|
|
1409
1434
|
def process_connect_init
|
1410
1435
|
# FIXME: Can receive PING as well here in recent versions.
|
1411
1436
|
line = @io.read_line(options[:connect_timeout])
|
1412
|
-
if !line
|
1437
|
+
if !line || line.empty?
|
1413
1438
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
1414
1439
|
end
|
1415
1440
|
|
1416
|
-
if match = line.match(NATS::Protocol::INFO)
|
1441
|
+
if (match = line.match(NATS::Protocol::INFO))
|
1417
1442
|
info_json = match.captures.first
|
1418
1443
|
process_info(info_json)
|
1419
1444
|
else
|
1420
1445
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not valid")
|
1421
1446
|
end
|
1422
1447
|
|
1423
|
-
|
1424
|
-
when (server_using_secure_connection? and client_using_secure_connection?)
|
1448
|
+
if server_using_secure_connection? && client_using_secure_connection?
|
1425
1449
|
@io.setup_tls!
|
1426
1450
|
# Server > v2.9.19 returns tls_required regardless of no_tls for WebSocket config being used so need to check URI.
|
1427
|
-
|
1428
|
-
raise NATS::IO::ConnectError.new(
|
1451
|
+
elsif server_using_secure_connection? && !client_using_secure_connection? && (@uri.scheme != "ws")
|
1452
|
+
raise NATS::IO::ConnectError.new("TLS/SSL required by server")
|
1429
1453
|
# Server < v2.9.19 requiring TLS/SSL over websocket but not requiring it over standard protocol
|
1430
1454
|
# doesn't send `tls_required` in its INFO so we need to check the URI scheme for WebSocket.
|
1431
|
-
|
1432
|
-
raise NATS::IO::ConnectError.new(
|
1455
|
+
elsif client_using_secure_connection? && !server_using_secure_connection? && (@uri.scheme != "wss")
|
1456
|
+
raise NATS::IO::ConnectError.new("TLS/SSL not supported by server")
|
1433
1457
|
else
|
1434
1458
|
# Otherwise, use a regular connection.
|
1435
1459
|
end
|
@@ -1450,6 +1474,7 @@ module NATS
|
|
1450
1474
|
|
1451
1475
|
case next_op
|
1452
1476
|
when NATS::Protocol::PONG
|
1477
|
+
# do nothing
|
1453
1478
|
when NATS::Protocol::ERR
|
1454
1479
|
if @server_info[:auth_required]
|
1455
1480
|
raise NATS::IO::AuthError.new($1)
|
@@ -1463,7 +1488,7 @@ module NATS
|
|
1463
1488
|
|
1464
1489
|
# Reconnect logic, this is done while holding the lock.
|
1465
1490
|
def attempt_reconnect
|
1466
|
-
@disconnect_cb
|
1491
|
+
@disconnect_cb&.call(@last_err)
|
1467
1492
|
|
1468
1493
|
# Clear sticky error
|
1469
1494
|
@last_err = nil
|
@@ -1474,7 +1499,7 @@ module NATS
|
|
1474
1499
|
srv = select_next_server
|
1475
1500
|
|
1476
1501
|
# Set hostname to use for TLS hostname verification
|
1477
|
-
if client_using_secure_connection?
|
1502
|
+
if client_using_secure_connection? && single_url_connect_used?
|
1478
1503
|
# Reuse original hostname name in case of using TLS.
|
1479
1504
|
@hostname ||= srv[:hostname]
|
1480
1505
|
else
|
@@ -1540,10 +1565,10 @@ module NATS
|
|
1540
1565
|
|
1541
1566
|
# Dispatch the reconnected callback while holding lock
|
1542
1567
|
# which we should have already
|
1543
|
-
@reconnect_cb
|
1568
|
+
@reconnect_cb&.call
|
1544
1569
|
end
|
1545
1570
|
|
1546
|
-
def close_connection(conn_status, do_cbs=true)
|
1571
|
+
def close_connection(conn_status, do_cbs = true)
|
1547
1572
|
synchronize do
|
1548
1573
|
@connect_called = false
|
1549
1574
|
if @status == CLOSED
|
@@ -1558,18 +1583,21 @@ module NATS
|
|
1558
1583
|
|
1559
1584
|
# FIXME: More graceful way of handling the following?
|
1560
1585
|
# Ensure ping interval and flusher are not running anymore
|
1561
|
-
if @ping_interval_thread
|
1586
|
+
if @ping_interval_thread&.alive?
|
1562
1587
|
@ping_interval_thread.exit
|
1563
1588
|
end
|
1564
1589
|
|
1565
|
-
if @flusher_thread
|
1590
|
+
if @flusher_thread&.alive?
|
1566
1591
|
@flusher_thread.exit
|
1567
1592
|
end
|
1568
1593
|
|
1569
|
-
if @read_loop_thread
|
1594
|
+
if @read_loop_thread&.alive?
|
1570
1595
|
@read_loop_thread.exit
|
1571
1596
|
end
|
1572
1597
|
|
1598
|
+
@subscription_executor&.shutdown
|
1599
|
+
@subscription_executor&.wait_for_termination(options[:close_timeout])
|
1600
|
+
|
1573
1601
|
# TODO: Delete any other state which we are not using here too.
|
1574
1602
|
synchronize do
|
1575
1603
|
@pongs.synchronize do
|
@@ -1581,24 +1609,26 @@ module NATS
|
|
1581
1609
|
|
1582
1610
|
# Try to write any pending flushes in case
|
1583
1611
|
# we have a connection then close it.
|
1584
|
-
should_flush =
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1612
|
+
should_flush = @pending_queue && @io && @io.socket && !@io.closed?
|
1613
|
+
if should_flush
|
1614
|
+
begin
|
1615
|
+
cmds = []
|
1616
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
1617
|
+
|
1618
|
+
# FIXME: Fails when empty on TLS connection?
|
1619
|
+
@io.write(cmds.join) unless cmds.empty?
|
1620
|
+
rescue => e
|
1621
|
+
@last_err = e
|
1622
|
+
err_cb_call(self, e, nil) if @err_cb
|
1623
|
+
end
|
1624
|
+
end
|
1595
1625
|
|
1596
1626
|
# Destroy any remaining subscriptions.
|
1597
1627
|
@subs.clear
|
1598
1628
|
|
1599
1629
|
if do_cbs
|
1600
|
-
@disconnect_cb
|
1601
|
-
@close_cb
|
1630
|
+
@disconnect_cb&.call(@last_err)
|
1631
|
+
@close_cb&.call
|
1602
1632
|
end
|
1603
1633
|
|
1604
1634
|
@status = conn_status
|
@@ -1630,8 +1660,11 @@ module NATS
|
|
1630
1660
|
|
1631
1661
|
# Subscription handling thread pool
|
1632
1662
|
@subscription_executor = Concurrent::ThreadPoolExecutor.new(
|
1633
|
-
name:
|
1663
|
+
name: "nats:subscription", # threads will be given names like nats:subscription-worker-1
|
1634
1664
|
max_threads: NATS::IO::DEFAULT_TOTAL_SUB_CONCURRENCY,
|
1665
|
+
# JRuby has a bug on certain Java version of not creating new threads:
|
1666
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/864
|
1667
|
+
min_threads: defined?(JRUBY_VERSION) ? 2 : 0
|
1635
1668
|
)
|
1636
1669
|
end
|
1637
1670
|
|
@@ -1639,7 +1672,7 @@ module NATS
|
|
1639
1672
|
# for the new style request response.
|
1640
1673
|
def start_resp_mux_sub!
|
1641
1674
|
@resp_sub_prefix = new_inbox
|
1642
|
-
@resp_map = Hash.new { |h,k| h[k] = { }
|
1675
|
+
@resp_map = Hash.new { |h, k| h[k] = {} }
|
1643
1676
|
|
1644
1677
|
@resp_sub = Subscription.new
|
1645
1678
|
@resp_sub.subject = "#{@resp_sub_prefix}.*"
|
@@ -1653,7 +1686,7 @@ module NATS
|
|
1653
1686
|
@resp_sub.callback = proc do |msg|
|
1654
1687
|
# Pick the token and signal the request under the mutex
|
1655
1688
|
# from the subscription itself.
|
1656
|
-
token = msg.subject.split(
|
1689
|
+
token = msg.subject.split(".").last
|
1657
1690
|
future = nil
|
1658
1691
|
synchronize do
|
1659
1692
|
future = @resp_map[token][:future]
|
@@ -1685,7 +1718,7 @@ module NATS
|
|
1685
1718
|
return false if server[:error_received]
|
1686
1719
|
|
1687
1720
|
# We will retry a number of times to reconnect to a server.
|
1688
|
-
|
1721
|
+
server[:reconnect_attempts] <= @options[:max_reconnect_attempts]
|
1689
1722
|
end
|
1690
1723
|
|
1691
1724
|
def should_delay_connect?(server)
|
@@ -1702,35 +1735,34 @@ module NATS
|
|
1702
1735
|
|
1703
1736
|
def create_socket
|
1704
1737
|
socket_class = case @uri.scheme
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1738
|
+
when "nats", "tls"
|
1739
|
+
NATS::IO::Socket
|
1740
|
+
when "ws", "wss"
|
1741
|
+
require_relative "websocket"
|
1742
|
+
NATS::IO::WebSocket
|
1743
|
+
else
|
1744
|
+
raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling"
|
1745
|
+
end
|
1713
1746
|
|
1714
1747
|
socket_class.new(
|
1715
1748
|
uri: @uri,
|
1716
|
-
tls: {
|
1717
|
-
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
1749
|
+
tls: {context: tls_context, hostname: @hostname},
|
1750
|
+
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
1718
1751
|
)
|
1719
1752
|
end
|
1720
1753
|
|
1721
1754
|
def setup_nkeys_connect
|
1722
1755
|
begin
|
1723
|
-
require
|
1724
|
-
require
|
1756
|
+
require "nkeys"
|
1757
|
+
require "base64"
|
1725
1758
|
rescue LoadError
|
1726
1759
|
raise(Error, "nkeys is not installed")
|
1727
1760
|
end
|
1728
1761
|
|
1729
|
-
|
1730
|
-
when @nkeys_seed
|
1762
|
+
if @nkeys_seed
|
1731
1763
|
@user_nkey_cb = nkey_cb_for_nkey_file(@nkeys_seed)
|
1732
1764
|
@signature_cb = signature_cb_for_nkey_file(@nkeys_seed)
|
1733
|
-
|
1765
|
+
elsif @user_credentials
|
1734
1766
|
# When the credentials are within a single decorated file.
|
1735
1767
|
@user_jwt_cb = jwt_cb_for_creds_file(@user_credentials)
|
1736
1768
|
@signature_cb = signature_cb_for_creds_file(@user_credentials)
|
@@ -1740,18 +1772,18 @@ module NATS
|
|
1740
1772
|
def signature_cb_for_nkey_file(nkey)
|
1741
1773
|
proc { |nonce|
|
1742
1774
|
seed = File.read(nkey).chomp
|
1743
|
-
kp = NKEYS
|
1775
|
+
kp = NKEYS.from_seed(seed)
|
1744
1776
|
raw_signed = kp.sign(nonce)
|
1745
1777
|
kp.wipe!
|
1746
1778
|
encoded = Base64.urlsafe_encode64(raw_signed)
|
1747
|
-
encoded.gsub(
|
1779
|
+
encoded.gsub("=", "")
|
1748
1780
|
}
|
1749
1781
|
end
|
1750
1782
|
|
1751
1783
|
def nkey_cb_for_nkey_file(nkey)
|
1752
1784
|
proc {
|
1753
1785
|
seed = File.read(nkey).chomp
|
1754
|
-
kp = NKEYS
|
1786
|
+
kp = NKEYS.from_seed(seed)
|
1755
1787
|
|
1756
1788
|
# Take a copy since original will be gone with the wipe.
|
1757
1789
|
pub_key = kp.public_key.dup
|
@@ -1763,21 +1795,20 @@ module NATS
|
|
1763
1795
|
|
1764
1796
|
def jwt_cb_for_creds_file(creds)
|
1765
1797
|
proc {
|
1766
|
-
jwt_start = "BEGIN NATS USER JWT"
|
1798
|
+
jwt_start = "BEGIN NATS USER JWT"
|
1767
1799
|
found = false
|
1768
1800
|
jwt = nil
|
1769
1801
|
|
1770
1802
|
File.readlines(creds).each do |line|
|
1771
|
-
|
1772
|
-
when found
|
1803
|
+
if found
|
1773
1804
|
jwt = line.chomp
|
1774
1805
|
break
|
1775
|
-
|
1806
|
+
elsif line.include?(jwt_start)
|
1776
1807
|
found = true
|
1777
1808
|
end
|
1778
1809
|
end
|
1779
1810
|
|
1780
|
-
raise(Error, "No JWT found in #{creds}") if
|
1811
|
+
raise(Error, "No JWT found in #{creds}") if !found
|
1781
1812
|
|
1782
1813
|
jwt
|
1783
1814
|
}
|
@@ -1785,23 +1816,22 @@ module NATS
|
|
1785
1816
|
|
1786
1817
|
def signature_cb_for_creds_file(creds)
|
1787
1818
|
proc { |nonce|
|
1788
|
-
seed_start = "BEGIN USER NKEY SEED"
|
1819
|
+
seed_start = "BEGIN USER NKEY SEED"
|
1789
1820
|
found = false
|
1790
1821
|
seed = nil
|
1791
1822
|
|
1792
1823
|
File.readlines(creds).each do |line|
|
1793
|
-
|
1794
|
-
when found
|
1824
|
+
if found
|
1795
1825
|
seed = line.chomp
|
1796
1826
|
break
|
1797
|
-
|
1827
|
+
elsif line.include?(seed_start)
|
1798
1828
|
found = true
|
1799
1829
|
end
|
1800
1830
|
end
|
1801
1831
|
|
1802
|
-
raise(Error, "No nkey user seed found in #{creds}") if
|
1832
|
+
raise(Error, "No nkey user seed found in #{creds}") if !found
|
1803
1833
|
|
1804
|
-
kp = NKEYS
|
1834
|
+
kp = NKEYS.from_seed(seed)
|
1805
1835
|
raw_signed = kp.sign(nonce)
|
1806
1836
|
|
1807
1837
|
# seed is a reference so also cleared when doing wipe,
|
@@ -1810,14 +1840,12 @@ module NATS
|
|
1810
1840
|
encoded = Base64.urlsafe_encode64(raw_signed)
|
1811
1841
|
|
1812
1842
|
# Remove padding
|
1813
|
-
encoded.gsub(
|
1843
|
+
encoded.gsub("=", "")
|
1814
1844
|
}
|
1815
1845
|
end
|
1816
1846
|
|
1817
1847
|
def process_uri(uris)
|
1818
|
-
uris.split(
|
1819
|
-
opts = {}
|
1820
|
-
|
1848
|
+
uris.gsub(/\s+/, "").split(",").map do |uri|
|
1821
1849
|
# Scheme
|
1822
1850
|
uri = "nats://#{uri}" if !uri.include?("://")
|
1823
1851
|
|
@@ -1825,7 +1853,7 @@ module NATS
|
|
1825
1853
|
|
1826
1854
|
# Host and Port
|
1827
1855
|
uri_object.hostname ||= "localhost"
|
1828
|
-
uri_object.port ||= DEFAULT_PORT.fetch(
|
1856
|
+
uri_object.port ||= DEFAULT_PORT.fetch(uri_object.scheme.to_sym, DEFAULT_PORT[:nats])
|
1829
1857
|
|
1830
1858
|
uri_object
|
1831
1859
|
end
|
@@ -1859,9 +1887,10 @@ module NATS
|
|
1859
1887
|
DEFAULT_CONNECT_TIMEOUT = 2
|
1860
1888
|
DEFAULT_READ_WRITE_TIMEOUT = 2
|
1861
1889
|
DEFAULT_DRAIN_TIMEOUT = 30
|
1890
|
+
DEFAULT_CLOSE_TIMEOUT = 30
|
1862
1891
|
|
1863
1892
|
# Default Pending Limits
|
1864
|
-
DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1893
|
+
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
1865
1894
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
1866
1895
|
|
1867
1896
|
DEFAULT_TOTAL_SUB_CONCURRENCY = 24
|
@@ -1871,7 +1900,7 @@ module NATS
|
|
1871
1900
|
class Socket
|
1872
1901
|
attr_accessor :socket
|
1873
1902
|
|
1874
|
-
def initialize(options={})
|
1903
|
+
def initialize(options = {})
|
1875
1904
|
@uri = options[:uri]
|
1876
1905
|
@connect_timeout = options[:connect_timeout]
|
1877
1906
|
@write_timeout = options[:write_timeout]
|
@@ -1883,13 +1912,11 @@ module NATS
|
|
1883
1912
|
def connect
|
1884
1913
|
addrinfo = ::Socket.getaddrinfo(@uri.hostname, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM)
|
1885
1914
|
addrinfo.each_with_index do |ai, i|
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
raise e if addrinfo.length == i+1
|
1892
|
-
end
|
1915
|
+
@socket = connect_addrinfo(ai, @uri.port, @connect_timeout)
|
1916
|
+
break
|
1917
|
+
rescue SystemCallError => e
|
1918
|
+
# Give up if no more available
|
1919
|
+
raise e if addrinfo.length == i + 1
|
1893
1920
|
end
|
1894
1921
|
|
1895
1922
|
# Set TCP no delay by default
|
@@ -1912,7 +1939,7 @@ module NATS
|
|
1912
1939
|
@socket = tls_socket
|
1913
1940
|
end
|
1914
1941
|
|
1915
|
-
def read_line(deadline=nil)
|
1942
|
+
def read_line(deadline = nil)
|
1916
1943
|
# FIXME: Should accumulate and read in a non blocking way instead
|
1917
1944
|
unless ::IO.select([@socket], nil, nil, deadline)
|
1918
1945
|
raise NATS::IO::SocketTimeoutError
|
@@ -1920,10 +1947,9 @@ module NATS
|
|
1920
1947
|
@socket.gets
|
1921
1948
|
end
|
1922
1949
|
|
1923
|
-
def read(max_bytes, deadline=nil)
|
1924
|
-
|
1950
|
+
def read(max_bytes, deadline = nil)
|
1925
1951
|
begin
|
1926
|
-
|
1952
|
+
@socket.read_nonblock(max_bytes)
|
1927
1953
|
rescue ::IO::WaitReadable
|
1928
1954
|
if ::IO.select([@socket], nil, nil, deadline)
|
1929
1955
|
retry
|
@@ -1938,7 +1964,7 @@ module NATS
|
|
1938
1964
|
end
|
1939
1965
|
end
|
1940
1966
|
rescue EOFError => e
|
1941
|
-
if RUBY_ENGINE ==
|
1967
|
+
if (RUBY_ENGINE == "jruby") && (e.message == "No message available")
|
1942
1968
|
# FIXME: <EOFError: No message available> can happen in jruby
|
1943
1969
|
# even though seems it is temporary and eventually possible
|
1944
1970
|
# to read from socket.
|
@@ -1947,32 +1973,29 @@ module NATS
|
|
1947
1973
|
raise Errno::ECONNRESET
|
1948
1974
|
end
|
1949
1975
|
|
1950
|
-
def write(data, deadline=nil)
|
1976
|
+
def write(data, deadline = nil)
|
1951
1977
|
length = data.bytesize
|
1952
1978
|
total_written = 0
|
1953
1979
|
|
1954
1980
|
loop do
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
raise NATS::IO::SocketTimeoutError
|
1972
|
-
end
|
1981
|
+
written = @socket.write_nonblock(data)
|
1982
|
+
|
1983
|
+
total_written += written
|
1984
|
+
break total_written if total_written >= length
|
1985
|
+
data = data.byteslice(written..-1)
|
1986
|
+
rescue ::IO::WaitWritable
|
1987
|
+
if ::IO.select(nil, [@socket], nil, deadline)
|
1988
|
+
retry
|
1989
|
+
else
|
1990
|
+
raise NATS::IO::SocketTimeoutError
|
1991
|
+
end
|
1992
|
+
rescue ::IO::WaitReadable
|
1993
|
+
if ::IO.select([@socket], nil, nil, deadline)
|
1994
|
+
retry
|
1995
|
+
else
|
1996
|
+
raise NATS::IO::SocketTimeoutError
|
1973
1997
|
end
|
1974
1998
|
end
|
1975
|
-
|
1976
1999
|
rescue EOFError
|
1977
2000
|
raise Errno::ECONNRESET
|
1978
2001
|
end
|
@@ -2017,14 +2040,13 @@ module NATS
|
|
2017
2040
|
# Implementation of MonotonicTime adapted from
|
2018
2041
|
# https://github.com/ruby-concurrency/concurrent-ruby/
|
2019
2042
|
class << self
|
2020
|
-
|
2021
|
-
when defined?(Process::CLOCK_MONOTONIC)
|
2043
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
2022
2044
|
def now
|
2023
2045
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
2024
2046
|
end
|
2025
|
-
|
2047
|
+
elsif RUBY_ENGINE == "jruby"
|
2026
2048
|
def now
|
2027
|
-
java.lang.System.nanoTime
|
2049
|
+
java.lang.System.nanoTime / 1_000_000_000.0
|
2028
2050
|
end
|
2029
2051
|
else
|
2030
2052
|
def now
|