nats-pure 2.2.0 → 2.3.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/LICENSE +201 -0
- data/README.md +251 -0
- data/lib/nats/io/client.rb +226 -151
- data/lib/nats/io/errors.rb +6 -0
- data/lib/nats/io/jetstream/api.rb +305 -0
- data/lib/nats/io/jetstream/errors.rb +104 -0
- data/lib/nats/io/jetstream/js/config.rb +26 -0
- data/lib/nats/io/jetstream/js/header.rb +31 -0
- data/lib/nats/io/jetstream/js/status.rb +27 -0
- data/lib/nats/io/jetstream/js/sub.rb +30 -0
- data/lib/nats/io/jetstream/js.rb +93 -0
- data/lib/nats/io/jetstream/manager.rb +284 -0
- data/lib/nats/io/jetstream/msg/ack.rb +57 -0
- data/lib/nats/io/jetstream/msg/ack_methods.rb +111 -0
- data/lib/nats/io/jetstream/msg/metadata.rb +37 -0
- data/lib/nats/io/jetstream/msg.rb +26 -0
- data/lib/nats/io/jetstream/pull_subscription.rb +260 -0
- data/lib/nats/io/jetstream/push_subscription.rb +42 -0
- data/lib/nats/io/jetstream.rb +269 -0
- data/lib/nats/io/kv/api.rb +39 -0
- data/lib/nats/io/kv/bucket_status.rb +38 -0
- data/lib/nats/io/kv/errors.rb +60 -0
- data/lib/nats/io/kv/manager.rb +89 -0
- data/lib/nats/io/kv.rb +5 -157
- data/lib/nats/io/msg.rb +4 -2
- data/lib/nats/io/rails.rb +29 -0
- data/lib/nats/io/subscription.rb +70 -5
- data/lib/nats/io/version.rb +1 -1
- data/lib/nats/io/websocket.rb +75 -0
- data/sig/nats/io/client.rbs +304 -0
- data/sig/nats/io/errors.rbs +54 -0
- data/sig/nats/io/jetstream/api.rbs +35 -0
- data/sig/nats/io/jetstream/errors.rbs +54 -0
- data/sig/nats/io/jetstream/js/config.rbs +11 -0
- data/sig/nats/io/jetstream/js/header.rbs +17 -0
- data/sig/nats/io/jetstream/js/status.rbs +13 -0
- data/sig/nats/io/jetstream/js/sub.rbs +14 -0
- data/sig/nats/io/jetstream/js.rbs +27 -0
- data/sig/nats/io/jetstream/manager.rbs +33 -0
- data/sig/nats/io/jetstream/msg/ack.rbs +35 -0
- data/sig/nats/io/jetstream/msg/ack_methods.rbs +25 -0
- data/sig/nats/io/jetstream/msg/metadata.rbs +15 -0
- data/sig/nats/io/jetstream/msg.rbs +6 -0
- data/sig/nats/io/jetstream/pull_subscription.rbs +14 -0
- data/sig/nats/io/jetstream/push_subscription.rbs +7 -0
- data/sig/nats/io/jetstream.rbs +15 -0
- data/sig/nats/io/kv/api.rbs +8 -0
- data/sig/nats/io/kv/bucket_status.rbs +17 -0
- data/sig/nats/io/kv/errors.rbs +30 -0
- data/sig/nats/io/kv/manager.rbs +11 -0
- data/sig/nats/io/kv.rbs +39 -0
- data/sig/nats/io/msg.rbs +14 -0
- data/sig/nats/io/parser.rbs +32 -0
- data/sig/nats/io/subscription.rbs +33 -0
- data/sig/nats/io/version.rbs +9 -0
- data/sig/nats/nuid.rbs +32 -0
- metadata +68 -5
- data/lib/nats/io/js.rb +0 -1434
data/lib/nats/io/client.rb
CHANGED
@@ -17,7 +17,8 @@ require_relative 'version'
|
|
17
17
|
require_relative 'errors'
|
18
18
|
require_relative 'msg'
|
19
19
|
require_relative 'subscription'
|
20
|
-
require_relative '
|
20
|
+
require_relative 'jetstream'
|
21
|
+
require_relative "rails" if defined?(::Rails::Engine)
|
21
22
|
|
22
23
|
require 'nats/nuid'
|
23
24
|
require 'thread'
|
@@ -26,6 +27,7 @@ require 'json'
|
|
26
27
|
require 'monitor'
|
27
28
|
require 'uri'
|
28
29
|
require 'securerandom'
|
30
|
+
require 'concurrent'
|
29
31
|
|
30
32
|
begin
|
31
33
|
require "openssl"
|
@@ -81,15 +83,28 @@ module NATS
|
|
81
83
|
DRAINING_PUBS = 6
|
82
84
|
end
|
83
85
|
|
86
|
+
# Fork Detection handling
|
87
|
+
# Based from similar approach as mperham/connection_pool: https://github.com/mperham/connection_pool/pull/166
|
88
|
+
if Process.respond_to?(:fork) && Process.respond_to?(:_fork) # MRI 3.1+
|
89
|
+
module ForkTracker
|
90
|
+
def _fork
|
91
|
+
super.tap do |pid|
|
92
|
+
Client.after_fork if pid.zero? # in the child process
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
Process.singleton_class.prepend(ForkTracker)
|
97
|
+
end
|
98
|
+
|
84
99
|
# Client creates a connection to the NATS Server.
|
85
100
|
class Client
|
86
101
|
include MonitorMixin
|
87
102
|
include Status
|
88
103
|
|
89
|
-
attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri
|
104
|
+
attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri, :subscription_executor, :reloader
|
90
105
|
|
91
|
-
DEFAULT_PORT = 4222
|
92
|
-
DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT}".freeze)
|
106
|
+
DEFAULT_PORT = { nats: 4222, ws: 80, wss: 443 }.freeze
|
107
|
+
DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT[:nats]}".freeze)
|
93
108
|
|
94
109
|
CR_LF = ("\r\n".freeze)
|
95
110
|
CR_LF_SIZE = (CR_LF.bytesize)
|
@@ -106,9 +121,39 @@ module NATS
|
|
106
121
|
SUB_OP = ('SUB'.freeze)
|
107
122
|
EMPTY_MSG = (''.freeze)
|
108
123
|
|
109
|
-
|
110
|
-
|
111
|
-
|
124
|
+
INSTANCES = ObjectSpace::WeakMap.new # tracks all alive client instances
|
125
|
+
private_constant :INSTANCES
|
126
|
+
|
127
|
+
class << self
|
128
|
+
# Reloader should free resources managed by external framework
|
129
|
+
# that were implicitly acquired in subscription callbacks.
|
130
|
+
attr_writer :default_reloader
|
131
|
+
|
132
|
+
def default_reloader
|
133
|
+
@default_reloader ||= proc { |&block| block.call }.tap { |r| Ractor.make_shareable(r) if defined? Ractor }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Re-establish connection in a new process after forking to start new threads.
|
137
|
+
def after_fork
|
138
|
+
INSTANCES.each do |client|
|
139
|
+
if client.options[:reconnect]
|
140
|
+
was_connected = !client.disconnected?
|
141
|
+
client.send(:close_connection, Status::DISCONNECTED, true)
|
142
|
+
client.connect if was_connected
|
143
|
+
else
|
144
|
+
client.send(:err_cb_call, self, NATS::IO::ForkDetectedError, nil)
|
145
|
+
client.close
|
146
|
+
end
|
147
|
+
rescue => e
|
148
|
+
warn "nats: Error during handling after_fork callback: #{e}" # TODO: Report as async error via error callback?
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def initialize(uri = nil, opts = {})
|
154
|
+
super() # required to initialize monitor
|
155
|
+
@initial_uri = uri
|
156
|
+
@initial_options = opts
|
112
157
|
|
113
158
|
# Read/Write IO
|
114
159
|
@io = nil
|
@@ -132,7 +177,7 @@ module NATS
|
|
132
177
|
@uri = nil
|
133
178
|
@server_pool = []
|
134
179
|
|
135
|
-
@status =
|
180
|
+
@status = nil
|
136
181
|
|
137
182
|
# Subscriptions
|
138
183
|
@subs = { }
|
@@ -194,29 +239,63 @@ module NATS
|
|
194
239
|
|
195
240
|
# Draining
|
196
241
|
@drain_t = nil
|
242
|
+
|
243
|
+
# Prepare for calling connect or automatic delayed connection
|
244
|
+
parse_and_validate_options if uri || opts.any?
|
245
|
+
|
246
|
+
# Keep track of all client instances to handle them after process forking in Ruby 3.1+
|
247
|
+
INSTANCES[self] = self if !defined?(Ractor) || Ractor.current == Ractor.main # Ractors doesn't work in forked processes
|
248
|
+
|
249
|
+
@reloader = opts.fetch(:reloader, self.class.default_reloader)
|
197
250
|
end
|
198
251
|
|
199
|
-
#
|
252
|
+
# Prepare connecting to NATS, but postpone real connection until first usage.
|
200
253
|
def connect(uri=nil, opts={})
|
254
|
+
if uri || opts.any?
|
255
|
+
@initial_uri = uri
|
256
|
+
@initial_options = opts
|
257
|
+
end
|
258
|
+
|
201
259
|
synchronize do
|
202
260
|
# In case it has been connected already, then do not need to call this again.
|
203
261
|
return if @connect_called
|
204
262
|
@connect_called = true
|
205
263
|
end
|
206
264
|
|
265
|
+
parse_and_validate_options
|
266
|
+
establish_connection!
|
267
|
+
|
268
|
+
self
|
269
|
+
end
|
270
|
+
|
271
|
+
private def parse_and_validate_options
|
272
|
+
# Reset these in case we have reconnected via fork.
|
273
|
+
@server_pool = []
|
274
|
+
@resp_sub = nil
|
275
|
+
@resp_map = nil
|
276
|
+
@resp_sub_prefix = nil
|
277
|
+
@nuid = NATS::NUID.new
|
278
|
+
@stats = {
|
279
|
+
in_msgs: 0,
|
280
|
+
out_msgs: 0,
|
281
|
+
in_bytes: 0,
|
282
|
+
out_bytes: 0,
|
283
|
+
reconnects: 0
|
284
|
+
}
|
285
|
+
@status = DISCONNECTED
|
286
|
+
|
207
287
|
# Convert URI to string if needed.
|
288
|
+
uri = @initial_uri.dup
|
208
289
|
uri = uri.to_s if uri.is_a?(URI)
|
209
290
|
|
291
|
+
opts = @initial_options.dup
|
292
|
+
|
210
293
|
case uri
|
211
294
|
when String
|
212
295
|
# Initialize TLS defaults in case any url is using it.
|
213
296
|
srvs = opts[:servers] = process_uri(uri)
|
214
|
-
if srvs.any? {|u| u.scheme
|
215
|
-
|
216
|
-
tls_context.set_params
|
217
|
-
opts[:tls] = {
|
218
|
-
context: tls_context
|
219
|
-
}
|
297
|
+
if srvs.any? {|u| %w[tls wss].include? u.scheme } and !opts[:tls]
|
298
|
+
opts[:tls] = { context: tls_context }
|
220
299
|
end
|
221
300
|
@single_url_connect_used = true if srvs.size == 1
|
222
301
|
when Hash
|
@@ -287,11 +366,25 @@ module NATS
|
|
287
366
|
|
288
367
|
validate_settings!
|
289
368
|
|
369
|
+
self
|
370
|
+
end
|
371
|
+
|
372
|
+
private def establish_connection!
|
373
|
+
@ruby_pid = Process.pid # For fork detection
|
374
|
+
|
290
375
|
srv = nil
|
291
376
|
begin
|
292
377
|
srv = select_next_server
|
293
378
|
|
294
|
-
#
|
379
|
+
# Use the hostname from the server for TLS hostname verification.
|
380
|
+
if client_using_secure_connection? and single_url_connect_used?
|
381
|
+
# Always reuse the original hostname used to connect.
|
382
|
+
@hostname ||= srv[:hostname]
|
383
|
+
else
|
384
|
+
@hostname = srv[:hostname]
|
385
|
+
end
|
386
|
+
|
387
|
+
# Create TCP socket connection to NATS.
|
295
388
|
@io = create_socket
|
296
389
|
@io.connect
|
297
390
|
|
@@ -302,14 +395,6 @@ module NATS
|
|
302
395
|
# Connection established and now in process of sending CONNECT to NATS
|
303
396
|
@status = CONNECTING
|
304
397
|
|
305
|
-
# Use the hostname from the server for TLS hostname verification.
|
306
|
-
if client_using_secure_connection? and single_url_connect_used?
|
307
|
-
# Always reuse the original hostname used to connect.
|
308
|
-
@hostname ||= srv[:hostname]
|
309
|
-
else
|
310
|
-
@hostname = srv[:hostname]
|
311
|
-
end
|
312
|
-
|
313
398
|
# Established TCP connection successfully so can start connect
|
314
399
|
process_connect_init
|
315
400
|
|
@@ -341,8 +426,8 @@ module NATS
|
|
341
426
|
# triggering the disconnection/closed callbacks.
|
342
427
|
close_connection(DISCONNECTED, false)
|
343
428
|
|
344
|
-
#
|
345
|
-
# is set for the first time
|
429
|
+
# Always sleep here to safe guard against errors before current[:was_connected]
|
430
|
+
# is set for the first time.
|
346
431
|
sleep @options[:reconnect_time_wait] if @options[:reconnect_time_wait]
|
347
432
|
|
348
433
|
# Continue retrying until there are no options left in the server pool
|
@@ -434,6 +519,7 @@ module NATS
|
|
434
519
|
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
435
520
|
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
436
521
|
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
522
|
+
sub.processing_concurrency = opts[:processing_concurrency] if opts.key?(:processing_concurrency)
|
437
523
|
|
438
524
|
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
|
439
525
|
@flush_queue << :sub
|
@@ -446,41 +532,6 @@ module NATS
|
|
446
532
|
sub.wait_for_msgs_cond = cond
|
447
533
|
end
|
448
534
|
|
449
|
-
# Async subscriptions each own a single thread for the
|
450
|
-
# delivery of messages.
|
451
|
-
# FIXME: Support shared thread pool with configurable limits
|
452
|
-
# to better support case of having a lot of subscriptions.
|
453
|
-
sub.wait_for_msgs_t = Thread.new do
|
454
|
-
loop do
|
455
|
-
msg = sub.pending_queue.pop
|
456
|
-
|
457
|
-
cb = nil
|
458
|
-
sub.synchronize do
|
459
|
-
|
460
|
-
# Decrease pending size since consumed already
|
461
|
-
sub.pending_size -= msg.data.size
|
462
|
-
cb = sub.callback
|
463
|
-
end
|
464
|
-
|
465
|
-
begin
|
466
|
-
# Note: Keep some of the alternative arity versions to slightly
|
467
|
-
# improve backwards compatibility. Eventually fine to deprecate
|
468
|
-
# since recommended version would be arity of 1 to get a NATS::Msg.
|
469
|
-
case cb.arity
|
470
|
-
when 0 then cb.call
|
471
|
-
when 1 then cb.call(msg)
|
472
|
-
when 2 then cb.call(msg.data, msg.reply)
|
473
|
-
when 3 then cb.call(msg.data, msg.reply, msg.subject)
|
474
|
-
else cb.call(msg.data, msg.reply, msg.subject, msg.header)
|
475
|
-
end
|
476
|
-
rescue => e
|
477
|
-
synchronize do
|
478
|
-
err_cb_call(self, e, sub) if @err_cb
|
479
|
-
end
|
480
|
-
end
|
481
|
-
end
|
482
|
-
end if callback
|
483
|
-
|
484
535
|
sub
|
485
536
|
end
|
486
537
|
|
@@ -709,6 +760,10 @@ module NATS
|
|
709
760
|
connected? ? @uri : nil
|
710
761
|
end
|
711
762
|
|
763
|
+
def disconnected?
|
764
|
+
!@status or @status == DISCONNECTED
|
765
|
+
end
|
766
|
+
|
712
767
|
def connected?
|
713
768
|
@status == CONNECTED
|
714
769
|
end
|
@@ -812,7 +867,8 @@ module NATS
|
|
812
867
|
if !@options[:ignore_discovered_urls] && connect_urls
|
813
868
|
srvs = []
|
814
869
|
connect_urls.each do |url|
|
815
|
-
scheme
|
870
|
+
# Use the same scheme as the currently in use URI.
|
871
|
+
scheme = @uri.scheme
|
816
872
|
u = URI.parse("#{scheme}://#{url}")
|
817
873
|
|
818
874
|
# Skip in case it is the current server which we already know
|
@@ -854,14 +910,18 @@ module NATS
|
|
854
910
|
hdr = {}
|
855
911
|
lines = header.lines
|
856
912
|
|
857
|
-
# Check if
|
858
|
-
if lines.count
|
913
|
+
# Check if the first line has an inline status and description.
|
914
|
+
if lines.count > 0
|
859
915
|
status_hdr = lines.first.rstrip
|
860
|
-
|
916
|
+
status = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
917
|
+
|
918
|
+
if status and !status.empty?
|
919
|
+
hdr[STATUS_HDR] = status
|
861
920
|
|
862
|
-
|
863
|
-
|
864
|
-
|
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)
|
923
|
+
hdr[DESC_HDR] = desc unless desc.empty?
|
924
|
+
end
|
865
925
|
end
|
866
926
|
end
|
867
927
|
begin
|
@@ -967,12 +1027,8 @@ module NATS
|
|
967
1027
|
# Only dispatch message when sure that it would not block
|
968
1028
|
# the main read loop from the parser.
|
969
1029
|
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr, nc: self, sub: sub)
|
970
|
-
sub.pending_queue << msg
|
971
|
-
|
972
|
-
# For sync subscribers, signal that there is a new message.
|
973
|
-
sub.wait_for_msgs_cond.signal if sub.wait_for_msgs_cond
|
974
1030
|
|
975
|
-
sub.
|
1031
|
+
sub.dispatch(msg)
|
976
1032
|
end
|
977
1033
|
end
|
978
1034
|
end
|
@@ -1012,11 +1068,32 @@ module NATS
|
|
1012
1068
|
@uri.scheme == "tls" || @tls
|
1013
1069
|
end
|
1014
1070
|
|
1071
|
+
def tls_context
|
1072
|
+
return nil unless @tls
|
1073
|
+
|
1074
|
+
# Allow prepared context and customizations via :tls opts
|
1075
|
+
return @tls[:context] if @tls[:context]
|
1076
|
+
|
1077
|
+
@tls_context ||= OpenSSL::SSL::SSLContext.new.tap do |tls_context|
|
1078
|
+
# Use the default verification options from Ruby:
|
1079
|
+
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1080
|
+
#
|
1081
|
+
# Insecure TLS versions not supported already:
|
1082
|
+
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1083
|
+
#
|
1084
|
+
tls_context.set_params
|
1085
|
+
end
|
1086
|
+
end
|
1087
|
+
|
1015
1088
|
def single_url_connect_used?
|
1016
1089
|
@single_url_connect_used
|
1017
1090
|
end
|
1018
1091
|
|
1019
1092
|
def send_command(command)
|
1093
|
+
raise NATS::IO::ConnectionClosedError if closed?
|
1094
|
+
|
1095
|
+
establish_connection! if !status || (disconnected? && should_reconnect?)
|
1096
|
+
|
1020
1097
|
@pending_size += command.bytesize
|
1021
1098
|
@pending_queue << command
|
1022
1099
|
|
@@ -1043,12 +1120,6 @@ module NATS
|
|
1043
1120
|
synchronize do
|
1044
1121
|
sub.max = opt_max
|
1045
1122
|
@subs.delete(sid) unless (sub.max && (sub.received < sub.max))
|
1046
|
-
|
1047
|
-
# Stop messages delivery thread for async subscribers
|
1048
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1049
|
-
sub.wait_for_msgs_t.exit
|
1050
|
-
sub.pending_queue.clear
|
1051
|
-
end
|
1052
1123
|
end
|
1053
1124
|
|
1054
1125
|
sub.synchronize do
|
@@ -1103,11 +1174,6 @@ module NATS
|
|
1103
1174
|
|
1104
1175
|
to_delete.each do |sub|
|
1105
1176
|
@subs.delete(sub.sid)
|
1106
|
-
# Stop messages delivery thread for async subscribers
|
1107
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1108
|
-
sub.wait_for_msgs_t.exit
|
1109
|
-
sub.pending_queue.clear
|
1110
|
-
end
|
1111
1177
|
end
|
1112
1178
|
to_delete.clear
|
1113
1179
|
|
@@ -1122,6 +1188,9 @@ module NATS
|
|
1122
1188
|
end
|
1123
1189
|
end
|
1124
1190
|
|
1191
|
+
subscription_executor.shutdown
|
1192
|
+
subscription_executor.wait_for_termination(@options[:drain_timeout])
|
1193
|
+
|
1125
1194
|
if MonotonicTime::now > drain_timeout
|
1126
1195
|
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
1127
1196
|
err_cb_call(self, e, nil) if @err_cb
|
@@ -1283,7 +1352,7 @@ module NATS
|
|
1283
1352
|
@flush_queue.pop
|
1284
1353
|
|
1285
1354
|
should_bail = synchronize do
|
1286
|
-
@status != CONNECTED || @status == CONNECTING
|
1355
|
+
(@status != CONNECTED && !draining? ) || @status == CONNECTING
|
1287
1356
|
end
|
1288
1357
|
return if should_bail
|
1289
1358
|
|
@@ -1338,6 +1407,7 @@ module NATS
|
|
1338
1407
|
end
|
1339
1408
|
|
1340
1409
|
def process_connect_init
|
1410
|
+
# FIXME: Can receive PING as well here in recent versions.
|
1341
1411
|
line = @io.read_line(options[:connect_timeout])
|
1342
1412
|
if !line or line.empty?
|
1343
1413
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
@@ -1352,39 +1422,13 @@ module NATS
|
|
1352
1422
|
|
1353
1423
|
case
|
1354
1424
|
when (server_using_secure_connection? and client_using_secure_connection?)
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
# Allow prepared context and customizations via :tls opts
|
1359
|
-
tls_context = @tls[:context] if @tls[:context]
|
1360
|
-
else
|
1361
|
-
# Defaults
|
1362
|
-
tls_context = OpenSSL::SSL::SSLContext.new
|
1363
|
-
|
1364
|
-
# Use the default verification options from Ruby:
|
1365
|
-
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1366
|
-
#
|
1367
|
-
# Insecure TLS versions not supported already:
|
1368
|
-
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1369
|
-
#
|
1370
|
-
tls_context.set_params
|
1371
|
-
end
|
1372
|
-
|
1373
|
-
# Setup TLS connection by rewrapping the socket
|
1374
|
-
tls_socket = OpenSSL::SSL::SSLSocket.new(@io.socket, tls_context)
|
1375
|
-
|
1376
|
-
# Close TCP socket after closing TLS socket as well.
|
1377
|
-
tls_socket.sync_close = true
|
1378
|
-
|
1379
|
-
# Required to enable hostname verification if Ruby runtime supports it (>= 2.4):
|
1380
|
-
# https://github.com/ruby/openssl/commit/028e495734e9e6aa5dba1a2e130b08f66cf31a21
|
1381
|
-
tls_socket.hostname = @hostname
|
1382
|
-
|
1383
|
-
tls_socket.connect
|
1384
|
-
@io.socket = tls_socket
|
1385
|
-
when (server_using_secure_connection? and !client_using_secure_connection?)
|
1425
|
+
@io.setup_tls!
|
1426
|
+
# Server > v2.9.19 returns tls_required regardless of no_tls for WebSocket config being used so need to check URI.
|
1427
|
+
when (server_using_secure_connection? and !client_using_secure_connection? and @uri.scheme != "ws")
|
1386
1428
|
raise NATS::IO::ConnectError.new('TLS/SSL required by server')
|
1387
|
-
|
1429
|
+
# Server < v2.9.19 requiring TLS/SSL over websocket but not requiring it over standard protocol
|
1430
|
+
# doesn't send `tls_required` in its INFO so we need to check the URI scheme for WebSocket.
|
1431
|
+
when (client_using_secure_connection? and !server_using_secure_connection? and @uri.scheme != "wss")
|
1388
1432
|
raise NATS::IO::ConnectError.new('TLS/SSL not supported by server')
|
1389
1433
|
else
|
1390
1434
|
# Otherwise, use a regular connection.
|
@@ -1429,11 +1473,6 @@ module NATS
|
|
1429
1473
|
begin
|
1430
1474
|
srv = select_next_server
|
1431
1475
|
|
1432
|
-
# Establish TCP connection with new server
|
1433
|
-
@io = create_socket
|
1434
|
-
@io.connect
|
1435
|
-
@stats[:reconnects] += 1
|
1436
|
-
|
1437
1476
|
# Set hostname to use for TLS hostname verification
|
1438
1477
|
if client_using_secure_connection? and single_url_connect_used?
|
1439
1478
|
# Reuse original hostname name in case of using TLS.
|
@@ -1442,6 +1481,11 @@ module NATS
|
|
1442
1481
|
@hostname = srv[:hostname]
|
1443
1482
|
end
|
1444
1483
|
|
1484
|
+
# Establish TCP connection with new server
|
1485
|
+
@io = create_socket
|
1486
|
+
@io.connect
|
1487
|
+
@stats[:reconnects] += 1
|
1488
|
+
|
1445
1489
|
# Established TCP connection successfully so can start connect
|
1446
1490
|
process_connect_init
|
1447
1491
|
|
@@ -1501,6 +1545,7 @@ module NATS
|
|
1501
1545
|
|
1502
1546
|
def close_connection(conn_status, do_cbs=true)
|
1503
1547
|
synchronize do
|
1548
|
+
@connect_called = false
|
1504
1549
|
if @status == CLOSED
|
1505
1550
|
@status = conn_status
|
1506
1551
|
return
|
@@ -1549,12 +1594,6 @@ module NATS
|
|
1549
1594
|
end if should_flush
|
1550
1595
|
|
1551
1596
|
# Destroy any remaining subscriptions.
|
1552
|
-
@subs.each do |_, sub|
|
1553
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1554
|
-
sub.wait_for_msgs_t.exit
|
1555
|
-
sub.pending_queue.clear
|
1556
|
-
end
|
1557
|
-
end
|
1558
1597
|
@subs.clear
|
1559
1598
|
|
1560
1599
|
if do_cbs
|
@@ -1576,15 +1615,24 @@ module NATS
|
|
1576
1615
|
def start_threads!
|
1577
1616
|
# Reading loop for gathering data
|
1578
1617
|
@read_loop_thread = Thread.new { read_loop }
|
1618
|
+
@read_loop_thread.name = "nats:read_loop"
|
1579
1619
|
@read_loop_thread.abort_on_exception = true
|
1580
1620
|
|
1581
1621
|
# Flusher loop for sending commands
|
1582
1622
|
@flusher_thread = Thread.new { flusher_loop }
|
1623
|
+
@flusher_thread.name = "nats:flusher_loop"
|
1583
1624
|
@flusher_thread.abort_on_exception = true
|
1584
1625
|
|
1585
1626
|
# Ping interval handling for keeping alive the connection
|
1586
1627
|
@ping_interval_thread = Thread.new { ping_interval_loop }
|
1628
|
+
@ping_interval_thread.name = "nats:ping_loop"
|
1587
1629
|
@ping_interval_thread.abort_on_exception = true
|
1630
|
+
|
1631
|
+
# Subscription handling thread pool
|
1632
|
+
@subscription_executor = Concurrent::ThreadPoolExecutor.new(
|
1633
|
+
name: 'nats:subscription', # threads will be given names like nats:subscription-worker-1
|
1634
|
+
max_threads: NATS::IO::DEFAULT_TOTAL_SUB_CONCURRENCY,
|
1635
|
+
)
|
1588
1636
|
end
|
1589
1637
|
|
1590
1638
|
# Prepares requests subscription that handles the responses
|
@@ -1602,29 +1650,25 @@ module NATS
|
|
1602
1650
|
@resp_sub.pending_msgs_limit = NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1603
1651
|
@resp_sub.pending_bytes_limit = NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
1604
1652
|
@resp_sub.pending_queue = SizedQueue.new(@resp_sub.pending_msgs_limit)
|
1605
|
-
@resp_sub.
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
token = msg
|
1613
|
-
|
1614
|
-
synchronize do
|
1615
|
-
future = @resp_map[token][:future]
|
1616
|
-
@resp_map[token][:response] = msg
|
1617
|
-
end
|
1653
|
+
@resp_sub.callback = proc do |msg|
|
1654
|
+
# Pick the token and signal the request under the mutex
|
1655
|
+
# from the subscription itself.
|
1656
|
+
token = msg.subject.split('.').last
|
1657
|
+
future = nil
|
1658
|
+
synchronize do
|
1659
|
+
future = @resp_map[token][:future]
|
1660
|
+
@resp_map[token][:response] = msg
|
1661
|
+
end
|
1618
1662
|
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
end
|
1663
|
+
# Signal back that the response has arrived
|
1664
|
+
# in case the future has not been yet delete.
|
1665
|
+
@resp_sub.synchronize do
|
1666
|
+
future.signal if future
|
1624
1667
|
end
|
1625
1668
|
end
|
1626
1669
|
|
1627
1670
|
sid = (@ssid += 1)
|
1671
|
+
@resp_sub.sid = sid
|
1628
1672
|
@subs[sid] = @resp_sub
|
1629
1673
|
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1630
1674
|
@flush_queue << :sub
|
@@ -1657,10 +1701,21 @@ module NATS
|
|
1657
1701
|
end
|
1658
1702
|
|
1659
1703
|
def create_socket
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1704
|
+
socket_class = case @uri.scheme
|
1705
|
+
when "nats", "tls"
|
1706
|
+
NATS::IO::Socket
|
1707
|
+
when "ws", "wss"
|
1708
|
+
require_relative 'websocket'
|
1709
|
+
NATS::IO::WebSocket
|
1710
|
+
else
|
1711
|
+
raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling"
|
1712
|
+
end
|
1713
|
+
|
1714
|
+
socket_class.new(
|
1715
|
+
uri: @uri,
|
1716
|
+
tls: { context: tls_context, hostname: @hostname },
|
1717
|
+
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT,
|
1718
|
+
)
|
1664
1719
|
end
|
1665
1720
|
|
1666
1721
|
def setup_nkeys_connect
|
@@ -1770,7 +1825,7 @@ module NATS
|
|
1770
1825
|
|
1771
1826
|
# Host and Port
|
1772
1827
|
uri_object.hostname ||= "localhost"
|
1773
|
-
uri_object.port ||= DEFAULT_PORT
|
1828
|
+
uri_object.port ||= DEFAULT_PORT.fetch(uri.scheme.to_sym, DEFAULT_PORT[:nats])
|
1774
1829
|
|
1775
1830
|
uri_object
|
1776
1831
|
end
|
@@ -1809,6 +1864,9 @@ module NATS
|
|
1809
1864
|
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
1810
1865
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
1811
1866
|
|
1867
|
+
DEFAULT_TOTAL_SUB_CONCURRENCY = 24
|
1868
|
+
DEFAULT_SINGLE_SUB_CONCURRENCY = 1
|
1869
|
+
|
1812
1870
|
# Implementation adapted from https://github.com/redis/redis-rb
|
1813
1871
|
class Socket
|
1814
1872
|
attr_accessor :socket
|
@@ -1819,6 +1877,7 @@ module NATS
|
|
1819
1877
|
@write_timeout = options[:write_timeout]
|
1820
1878
|
@read_timeout = options[:read_timeout]
|
1821
1879
|
@socket = nil
|
1880
|
+
@tls = options[:tls]
|
1822
1881
|
end
|
1823
1882
|
|
1824
1883
|
def connect
|
@@ -1837,6 +1896,22 @@ module NATS
|
|
1837
1896
|
@socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
1838
1897
|
end
|
1839
1898
|
|
1899
|
+
# (Re-)connect using secure connection if server and client agreed on using it.
|
1900
|
+
def setup_tls!
|
1901
|
+
# Setup TLS connection by rewrapping the socket
|
1902
|
+
tls_socket = OpenSSL::SSL::SSLSocket.new(@socket, @tls.fetch(:context))
|
1903
|
+
|
1904
|
+
# Close TCP socket after closing TLS socket as well.
|
1905
|
+
tls_socket.sync_close = true
|
1906
|
+
|
1907
|
+
# Required to enable hostname verification if Ruby runtime supports it (>= 2.4):
|
1908
|
+
# https://github.com/ruby/openssl/commit/028e495734e9e6aa5dba1a2e130b08f66cf31a21
|
1909
|
+
tls_socket.hostname = @tls[:hostname]
|
1910
|
+
|
1911
|
+
tls_socket.connect
|
1912
|
+
@socket = tls_socket
|
1913
|
+
end
|
1914
|
+
|
1840
1915
|
def read_line(deadline=nil)
|
1841
1916
|
# FIXME: Should accumulate and read in a non blocking way instead
|
1842
1917
|
unless ::IO.select([@socket], nil, nil, deadline)
|
data/lib/nats/io/errors.rb
CHANGED
@@ -58,6 +58,12 @@ module NATS
|
|
58
58
|
|
59
59
|
# When drain takes too long to complete.
|
60
60
|
class DrainTimeoutError < Error; end
|
61
|
+
|
62
|
+
# When a fork is detected, but the client is not configured to re-connect automatically.
|
63
|
+
class ForkDetectedError < Error; end
|
64
|
+
|
65
|
+
# When tried to send command after connection has been closed.
|
66
|
+
class ConnectionClosedError < Error; end
|
61
67
|
end
|
62
68
|
|
63
69
|
# Timeout is raised when the client gives up waiting for a response from a service.
|