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