nats-pure 2.2.1 → 2.4.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/client.rb +1 -0
- data/lib/nats/io/client.rb +214 -144
- data/lib/nats/io/errors.rb +6 -0
- data/lib/nats/io/jetstream/api.rb +4 -0
- data/lib/nats/io/jetstream/manager.rb +21 -2
- data/lib/nats/io/jetstream/msg/ack_methods.rb +8 -4
- data/lib/nats/io/jetstream.rb +82 -7
- data/lib/nats/io/msg.rb +3 -1
- 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 +49 -4
data/lib/nats/io/client.rb
CHANGED
@@ -26,6 +26,7 @@ require 'json'
|
|
26
26
|
require 'monitor'
|
27
27
|
require 'uri'
|
28
28
|
require 'securerandom'
|
29
|
+
require 'concurrent'
|
29
30
|
|
30
31
|
begin
|
31
32
|
require "openssl"
|
@@ -81,15 +82,28 @@ module NATS
|
|
81
82
|
DRAINING_PUBS = 6
|
82
83
|
end
|
83
84
|
|
85
|
+
# Fork Detection handling
|
86
|
+
# Based from similar approach as mperham/connection_pool: https://github.com/mperham/connection_pool/pull/166
|
87
|
+
if Process.respond_to?(:fork) && Process.respond_to?(:_fork) # MRI 3.1+
|
88
|
+
module ForkTracker
|
89
|
+
def _fork
|
90
|
+
super.tap do |pid|
|
91
|
+
Client.after_fork if pid.zero? # in the child process
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
Process.singleton_class.prepend(ForkTracker)
|
96
|
+
end
|
97
|
+
|
84
98
|
# Client creates a connection to the NATS Server.
|
85
99
|
class Client
|
86
100
|
include MonitorMixin
|
87
101
|
include Status
|
88
102
|
|
89
|
-
attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri
|
103
|
+
attr_reader :status, :server_info, :server_pool, :options, :connected_server, :stats, :uri, :subscription_executor, :reloader
|
90
104
|
|
91
|
-
DEFAULT_PORT = 4222
|
92
|
-
DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT}".freeze)
|
105
|
+
DEFAULT_PORT = { nats: 4222, ws: 80, wss: 443 }.freeze
|
106
|
+
DEFAULT_URI = ("nats://localhost:#{DEFAULT_PORT[:nats]}".freeze)
|
93
107
|
|
94
108
|
CR_LF = ("\r\n".freeze)
|
95
109
|
CR_LF_SIZE = (CR_LF.bytesize)
|
@@ -106,9 +120,39 @@ module NATS
|
|
106
120
|
SUB_OP = ('SUB'.freeze)
|
107
121
|
EMPTY_MSG = (''.freeze)
|
108
122
|
|
109
|
-
|
110
|
-
|
111
|
-
|
123
|
+
INSTANCES = ObjectSpace::WeakMap.new # tracks all alive client instances
|
124
|
+
private_constant :INSTANCES
|
125
|
+
|
126
|
+
class << self
|
127
|
+
# Reloader should free resources managed by external framework
|
128
|
+
# that were implicitly acquired in subscription callbacks.
|
129
|
+
attr_writer :default_reloader
|
130
|
+
|
131
|
+
def default_reloader
|
132
|
+
@default_reloader ||= proc { |&block| block.call }.tap { |r| Ractor.make_shareable(r) if defined? Ractor }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Re-establish connection in a new process after forking to start new threads.
|
136
|
+
def after_fork
|
137
|
+
INSTANCES.each do |client|
|
138
|
+
if client.options[:reconnect]
|
139
|
+
was_connected = !client.disconnected?
|
140
|
+
client.send(:close_connection, Status::DISCONNECTED, true)
|
141
|
+
client.connect if was_connected
|
142
|
+
else
|
143
|
+
client.send(:err_cb_call, self, NATS::IO::ForkDetectedError, nil)
|
144
|
+
client.close
|
145
|
+
end
|
146
|
+
rescue => e
|
147
|
+
warn "nats: Error during handling after_fork callback: #{e}" # TODO: Report as async error via error callback?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize(uri = nil, opts = {})
|
153
|
+
super() # required to initialize monitor
|
154
|
+
@initial_uri = uri
|
155
|
+
@initial_options = opts
|
112
156
|
|
113
157
|
# Read/Write IO
|
114
158
|
@io = nil
|
@@ -132,7 +176,7 @@ module NATS
|
|
132
176
|
@uri = nil
|
133
177
|
@server_pool = []
|
134
178
|
|
135
|
-
@status =
|
179
|
+
@status = nil
|
136
180
|
|
137
181
|
# Subscriptions
|
138
182
|
@subs = { }
|
@@ -194,29 +238,63 @@ module NATS
|
|
194
238
|
|
195
239
|
# Draining
|
196
240
|
@drain_t = nil
|
241
|
+
|
242
|
+
# Prepare for calling connect or automatic delayed connection
|
243
|
+
parse_and_validate_options if uri || opts.any?
|
244
|
+
|
245
|
+
# Keep track of all client instances to handle them after process forking in Ruby 3.1+
|
246
|
+
INSTANCES[self] = self if !defined?(Ractor) || Ractor.current == Ractor.main # Ractors doesn't work in forked processes
|
247
|
+
|
248
|
+
@reloader = opts.fetch(:reloader, self.class.default_reloader)
|
197
249
|
end
|
198
250
|
|
199
|
-
#
|
251
|
+
# Prepare connecting to NATS, but postpone real connection until first usage.
|
200
252
|
def connect(uri=nil, opts={})
|
253
|
+
if uri || opts.any?
|
254
|
+
@initial_uri = uri
|
255
|
+
@initial_options = opts
|
256
|
+
end
|
257
|
+
|
201
258
|
synchronize do
|
202
259
|
# In case it has been connected already, then do not need to call this again.
|
203
260
|
return if @connect_called
|
204
261
|
@connect_called = true
|
205
262
|
end
|
206
263
|
|
264
|
+
parse_and_validate_options
|
265
|
+
establish_connection!
|
266
|
+
|
267
|
+
self
|
268
|
+
end
|
269
|
+
|
270
|
+
private def parse_and_validate_options
|
271
|
+
# Reset these in case we have reconnected via fork.
|
272
|
+
@server_pool = []
|
273
|
+
@resp_sub = nil
|
274
|
+
@resp_map = nil
|
275
|
+
@resp_sub_prefix = nil
|
276
|
+
@nuid = NATS::NUID.new
|
277
|
+
@stats = {
|
278
|
+
in_msgs: 0,
|
279
|
+
out_msgs: 0,
|
280
|
+
in_bytes: 0,
|
281
|
+
out_bytes: 0,
|
282
|
+
reconnects: 0
|
283
|
+
}
|
284
|
+
@status = DISCONNECTED
|
285
|
+
|
207
286
|
# Convert URI to string if needed.
|
287
|
+
uri = @initial_uri.dup
|
208
288
|
uri = uri.to_s if uri.is_a?(URI)
|
209
289
|
|
290
|
+
opts = @initial_options.dup
|
291
|
+
|
210
292
|
case uri
|
211
293
|
when String
|
212
294
|
# Initialize TLS defaults in case any url is using it.
|
213
295
|
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
|
-
}
|
296
|
+
if srvs.any? {|u| %w[tls wss].include? u.scheme } and !opts[:tls]
|
297
|
+
opts[:tls] = { context: tls_context }
|
220
298
|
end
|
221
299
|
@single_url_connect_used = true if srvs.size == 1
|
222
300
|
when Hash
|
@@ -287,11 +365,25 @@ module NATS
|
|
287
365
|
|
288
366
|
validate_settings!
|
289
367
|
|
368
|
+
self
|
369
|
+
end
|
370
|
+
|
371
|
+
private def establish_connection!
|
372
|
+
@ruby_pid = Process.pid # For fork detection
|
373
|
+
|
290
374
|
srv = nil
|
291
375
|
begin
|
292
376
|
srv = select_next_server
|
293
377
|
|
294
|
-
#
|
378
|
+
# Use the hostname from the server for TLS hostname verification.
|
379
|
+
if client_using_secure_connection? and single_url_connect_used?
|
380
|
+
# Always reuse the original hostname used to connect.
|
381
|
+
@hostname ||= srv[:hostname]
|
382
|
+
else
|
383
|
+
@hostname = srv[:hostname]
|
384
|
+
end
|
385
|
+
|
386
|
+
# Create TCP socket connection to NATS.
|
295
387
|
@io = create_socket
|
296
388
|
@io.connect
|
297
389
|
|
@@ -302,14 +394,6 @@ module NATS
|
|
302
394
|
# Connection established and now in process of sending CONNECT to NATS
|
303
395
|
@status = CONNECTING
|
304
396
|
|
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
397
|
# Established TCP connection successfully so can start connect
|
314
398
|
process_connect_init
|
315
399
|
|
@@ -341,8 +425,8 @@ module NATS
|
|
341
425
|
# triggering the disconnection/closed callbacks.
|
342
426
|
close_connection(DISCONNECTED, false)
|
343
427
|
|
344
|
-
#
|
345
|
-
# is set for the first time
|
428
|
+
# Always sleep here to safe guard against errors before current[:was_connected]
|
429
|
+
# is set for the first time.
|
346
430
|
sleep @options[:reconnect_time_wait] if @options[:reconnect_time_wait]
|
347
431
|
|
348
432
|
# Continue retrying until there are no options left in the server pool
|
@@ -434,6 +518,7 @@ module NATS
|
|
434
518
|
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
435
519
|
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
436
520
|
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
521
|
+
sub.processing_concurrency = opts[:processing_concurrency] if opts.key?(:processing_concurrency)
|
437
522
|
|
438
523
|
send_command("SUB #{subject} #{opts[:queue]} #{sid}#{CR_LF}")
|
439
524
|
@flush_queue << :sub
|
@@ -446,41 +531,6 @@ module NATS
|
|
446
531
|
sub.wait_for_msgs_cond = cond
|
447
532
|
end
|
448
533
|
|
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
534
|
sub
|
485
535
|
end
|
486
536
|
|
@@ -709,6 +759,10 @@ module NATS
|
|
709
759
|
connected? ? @uri : nil
|
710
760
|
end
|
711
761
|
|
762
|
+
def disconnected?
|
763
|
+
!@status or @status == DISCONNECTED
|
764
|
+
end
|
765
|
+
|
712
766
|
def connected?
|
713
767
|
@status == CONNECTED
|
714
768
|
end
|
@@ -812,7 +866,8 @@ module NATS
|
|
812
866
|
if !@options[:ignore_discovered_urls] && connect_urls
|
813
867
|
srvs = []
|
814
868
|
connect_urls.each do |url|
|
815
|
-
scheme
|
869
|
+
# Use the same scheme as the currently in use URI.
|
870
|
+
scheme = @uri.scheme
|
816
871
|
u = URI.parse("#{scheme}://#{url}")
|
817
872
|
|
818
873
|
# Skip in case it is the current server which we already know
|
@@ -971,12 +1026,8 @@ module NATS
|
|
971
1026
|
# Only dispatch message when sure that it would not block
|
972
1027
|
# the main read loop from the parser.
|
973
1028
|
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr, nc: self, sub: sub)
|
974
|
-
sub.pending_queue << msg
|
975
1029
|
|
976
|
-
|
977
|
-
sub.wait_for_msgs_cond.signal if sub.wait_for_msgs_cond
|
978
|
-
|
979
|
-
sub.pending_size += data.size
|
1030
|
+
sub.dispatch(msg)
|
980
1031
|
end
|
981
1032
|
end
|
982
1033
|
end
|
@@ -1016,11 +1067,32 @@ module NATS
|
|
1016
1067
|
@uri.scheme == "tls" || @tls
|
1017
1068
|
end
|
1018
1069
|
|
1070
|
+
def tls_context
|
1071
|
+
return nil unless @tls
|
1072
|
+
|
1073
|
+
# Allow prepared context and customizations via :tls opts
|
1074
|
+
return @tls[:context] if @tls[:context]
|
1075
|
+
|
1076
|
+
@tls_context ||= OpenSSL::SSL::SSLContext.new.tap do |tls_context|
|
1077
|
+
# Use the default verification options from Ruby:
|
1078
|
+
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1079
|
+
#
|
1080
|
+
# Insecure TLS versions not supported already:
|
1081
|
+
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1082
|
+
#
|
1083
|
+
tls_context.set_params
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
1019
1087
|
def single_url_connect_used?
|
1020
1088
|
@single_url_connect_used
|
1021
1089
|
end
|
1022
1090
|
|
1023
1091
|
def send_command(command)
|
1092
|
+
raise NATS::IO::ConnectionClosedError if closed?
|
1093
|
+
|
1094
|
+
establish_connection! if !status || (disconnected? && should_reconnect?)
|
1095
|
+
|
1024
1096
|
@pending_size += command.bytesize
|
1025
1097
|
@pending_queue << command
|
1026
1098
|
|
@@ -1047,12 +1119,6 @@ module NATS
|
|
1047
1119
|
synchronize do
|
1048
1120
|
sub.max = opt_max
|
1049
1121
|
@subs.delete(sid) unless (sub.max && (sub.received < sub.max))
|
1050
|
-
|
1051
|
-
# Stop messages delivery thread for async subscribers
|
1052
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1053
|
-
sub.wait_for_msgs_t.exit
|
1054
|
-
sub.pending_queue.clear
|
1055
|
-
end
|
1056
1122
|
end
|
1057
1123
|
|
1058
1124
|
sub.synchronize do
|
@@ -1107,11 +1173,6 @@ module NATS
|
|
1107
1173
|
|
1108
1174
|
to_delete.each do |sub|
|
1109
1175
|
@subs.delete(sub.sid)
|
1110
|
-
# Stop messages delivery thread for async subscribers
|
1111
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1112
|
-
sub.wait_for_msgs_t.exit
|
1113
|
-
sub.pending_queue.clear
|
1114
|
-
end
|
1115
1176
|
end
|
1116
1177
|
to_delete.clear
|
1117
1178
|
|
@@ -1126,6 +1187,9 @@ module NATS
|
|
1126
1187
|
end
|
1127
1188
|
end
|
1128
1189
|
|
1190
|
+
subscription_executor.shutdown
|
1191
|
+
subscription_executor.wait_for_termination(@options[:drain_timeout])
|
1192
|
+
|
1129
1193
|
if MonotonicTime::now > drain_timeout
|
1130
1194
|
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
1131
1195
|
err_cb_call(self, e, nil) if @err_cb
|
@@ -1287,7 +1351,7 @@ module NATS
|
|
1287
1351
|
@flush_queue.pop
|
1288
1352
|
|
1289
1353
|
should_bail = synchronize do
|
1290
|
-
@status != CONNECTED || @status == CONNECTING
|
1354
|
+
(@status != CONNECTED && !draining? ) || @status == CONNECTING
|
1291
1355
|
end
|
1292
1356
|
return if should_bail
|
1293
1357
|
|
@@ -1342,6 +1406,7 @@ module NATS
|
|
1342
1406
|
end
|
1343
1407
|
|
1344
1408
|
def process_connect_init
|
1409
|
+
# FIXME: Can receive PING as well here in recent versions.
|
1345
1410
|
line = @io.read_line(options[:connect_timeout])
|
1346
1411
|
if !line or line.empty?
|
1347
1412
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
@@ -1356,39 +1421,13 @@ module NATS
|
|
1356
1421
|
|
1357
1422
|
case
|
1358
1423
|
when (server_using_secure_connection? and client_using_secure_connection?)
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
# Allow prepared context and customizations via :tls opts
|
1363
|
-
tls_context = @tls[:context] if @tls[:context]
|
1364
|
-
else
|
1365
|
-
# Defaults
|
1366
|
-
tls_context = OpenSSL::SSL::SSLContext.new
|
1367
|
-
|
1368
|
-
# Use the default verification options from Ruby:
|
1369
|
-
# https://github.com/ruby/ruby/blob/96db72ce38b27799dd8e80ca00696e41234db6ba/ext/openssl/lib/openssl/ssl.rb#L19-L29
|
1370
|
-
#
|
1371
|
-
# Insecure TLS versions not supported already:
|
1372
|
-
# https://github.com/ruby/openssl/commit/3e5a009966bd7f806f7180d82cf830a04be28986
|
1373
|
-
#
|
1374
|
-
tls_context.set_params
|
1375
|
-
end
|
1376
|
-
|
1377
|
-
# Setup TLS connection by rewrapping the socket
|
1378
|
-
tls_socket = OpenSSL::SSL::SSLSocket.new(@io.socket, tls_context)
|
1379
|
-
|
1380
|
-
# Close TCP socket after closing TLS socket as well.
|
1381
|
-
tls_socket.sync_close = true
|
1382
|
-
|
1383
|
-
# Required to enable hostname verification if Ruby runtime supports it (>= 2.4):
|
1384
|
-
# https://github.com/ruby/openssl/commit/028e495734e9e6aa5dba1a2e130b08f66cf31a21
|
1385
|
-
tls_socket.hostname = @hostname
|
1386
|
-
|
1387
|
-
tls_socket.connect
|
1388
|
-
@io.socket = tls_socket
|
1389
|
-
when (server_using_secure_connection? and !client_using_secure_connection?)
|
1424
|
+
@io.setup_tls!
|
1425
|
+
# Server > v2.9.19 returns tls_required regardless of no_tls for WebSocket config being used so need to check URI.
|
1426
|
+
when (server_using_secure_connection? and !client_using_secure_connection? and @uri.scheme != "ws")
|
1390
1427
|
raise NATS::IO::ConnectError.new('TLS/SSL required by server')
|
1391
|
-
|
1428
|
+
# Server < v2.9.19 requiring TLS/SSL over websocket but not requiring it over standard protocol
|
1429
|
+
# doesn't send `tls_required` in its INFO so we need to check the URI scheme for WebSocket.
|
1430
|
+
when (client_using_secure_connection? and !server_using_secure_connection? and @uri.scheme != "wss")
|
1392
1431
|
raise NATS::IO::ConnectError.new('TLS/SSL not supported by server')
|
1393
1432
|
else
|
1394
1433
|
# Otherwise, use a regular connection.
|
@@ -1433,11 +1472,6 @@ module NATS
|
|
1433
1472
|
begin
|
1434
1473
|
srv = select_next_server
|
1435
1474
|
|
1436
|
-
# Establish TCP connection with new server
|
1437
|
-
@io = create_socket
|
1438
|
-
@io.connect
|
1439
|
-
@stats[:reconnects] += 1
|
1440
|
-
|
1441
1475
|
# Set hostname to use for TLS hostname verification
|
1442
1476
|
if client_using_secure_connection? and single_url_connect_used?
|
1443
1477
|
# Reuse original hostname name in case of using TLS.
|
@@ -1446,6 +1480,11 @@ module NATS
|
|
1446
1480
|
@hostname = srv[:hostname]
|
1447
1481
|
end
|
1448
1482
|
|
1483
|
+
# Establish TCP connection with new server
|
1484
|
+
@io = create_socket
|
1485
|
+
@io.connect
|
1486
|
+
@stats[:reconnects] += 1
|
1487
|
+
|
1449
1488
|
# Established TCP connection successfully so can start connect
|
1450
1489
|
process_connect_init
|
1451
1490
|
|
@@ -1505,6 +1544,7 @@ module NATS
|
|
1505
1544
|
|
1506
1545
|
def close_connection(conn_status, do_cbs=true)
|
1507
1546
|
synchronize do
|
1547
|
+
@connect_called = false
|
1508
1548
|
if @status == CLOSED
|
1509
1549
|
@status = conn_status
|
1510
1550
|
return
|
@@ -1553,12 +1593,6 @@ module NATS
|
|
1553
1593
|
end if should_flush
|
1554
1594
|
|
1555
1595
|
# Destroy any remaining subscriptions.
|
1556
|
-
@subs.each do |_, sub|
|
1557
|
-
if sub.wait_for_msgs_t && sub.wait_for_msgs_t.alive?
|
1558
|
-
sub.wait_for_msgs_t.exit
|
1559
|
-
sub.pending_queue.clear
|
1560
|
-
end
|
1561
|
-
end
|
1562
1596
|
@subs.clear
|
1563
1597
|
|
1564
1598
|
if do_cbs
|
@@ -1580,15 +1614,24 @@ module NATS
|
|
1580
1614
|
def start_threads!
|
1581
1615
|
# Reading loop for gathering data
|
1582
1616
|
@read_loop_thread = Thread.new { read_loop }
|
1617
|
+
@read_loop_thread.name = "nats:read_loop"
|
1583
1618
|
@read_loop_thread.abort_on_exception = true
|
1584
1619
|
|
1585
1620
|
# Flusher loop for sending commands
|
1586
1621
|
@flusher_thread = Thread.new { flusher_loop }
|
1622
|
+
@flusher_thread.name = "nats:flusher_loop"
|
1587
1623
|
@flusher_thread.abort_on_exception = true
|
1588
1624
|
|
1589
1625
|
# Ping interval handling for keeping alive the connection
|
1590
1626
|
@ping_interval_thread = Thread.new { ping_interval_loop }
|
1627
|
+
@ping_interval_thread.name = "nats:ping_loop"
|
1591
1628
|
@ping_interval_thread.abort_on_exception = true
|
1629
|
+
|
1630
|
+
# Subscription handling thread pool
|
1631
|
+
@subscription_executor = Concurrent::ThreadPoolExecutor.new(
|
1632
|
+
name: 'nats:subscription', # threads will be given names like nats:subscription-worker-1
|
1633
|
+
max_threads: NATS::IO::DEFAULT_TOTAL_SUB_CONCURRENCY,
|
1634
|
+
)
|
1592
1635
|
end
|
1593
1636
|
|
1594
1637
|
# Prepares requests subscription that handles the responses
|
@@ -1606,29 +1649,25 @@ module NATS
|
|
1606
1649
|
@resp_sub.pending_msgs_limit = NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1607
1650
|
@resp_sub.pending_bytes_limit = NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
1608
1651
|
@resp_sub.pending_queue = SizedQueue.new(@resp_sub.pending_msgs_limit)
|
1609
|
-
@resp_sub.
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
token = msg
|
1617
|
-
|
1618
|
-
synchronize do
|
1619
|
-
future = @resp_map[token][:future]
|
1620
|
-
@resp_map[token][:response] = msg
|
1621
|
-
end
|
1652
|
+
@resp_sub.callback = proc do |msg|
|
1653
|
+
# Pick the token and signal the request under the mutex
|
1654
|
+
# from the subscription itself.
|
1655
|
+
token = msg.subject.split('.').last
|
1656
|
+
future = nil
|
1657
|
+
synchronize do
|
1658
|
+
future = @resp_map[token][:future]
|
1659
|
+
@resp_map[token][:response] = msg
|
1660
|
+
end
|
1622
1661
|
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
end
|
1662
|
+
# Signal back that the response has arrived
|
1663
|
+
# in case the future has not been yet delete.
|
1664
|
+
@resp_sub.synchronize do
|
1665
|
+
future.signal if future
|
1628
1666
|
end
|
1629
1667
|
end
|
1630
1668
|
|
1631
1669
|
sid = (@ssid += 1)
|
1670
|
+
@resp_sub.sid = sid
|
1632
1671
|
@subs[sid] = @resp_sub
|
1633
1672
|
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1634
1673
|
@flush_queue << :sub
|
@@ -1661,10 +1700,21 @@ module NATS
|
|
1661
1700
|
end
|
1662
1701
|
|
1663
1702
|
def create_socket
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1703
|
+
socket_class = case @uri.scheme
|
1704
|
+
when "nats", "tls"
|
1705
|
+
NATS::IO::Socket
|
1706
|
+
when "ws", "wss"
|
1707
|
+
require_relative 'websocket'
|
1708
|
+
NATS::IO::WebSocket
|
1709
|
+
else
|
1710
|
+
raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling"
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
socket_class.new(
|
1714
|
+
uri: @uri,
|
1715
|
+
tls: { context: tls_context, hostname: @hostname },
|
1716
|
+
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT,
|
1717
|
+
)
|
1668
1718
|
end
|
1669
1719
|
|
1670
1720
|
def setup_nkeys_connect
|
@@ -1774,7 +1824,7 @@ module NATS
|
|
1774
1824
|
|
1775
1825
|
# Host and Port
|
1776
1826
|
uri_object.hostname ||= "localhost"
|
1777
|
-
uri_object.port ||= DEFAULT_PORT
|
1827
|
+
uri_object.port ||= DEFAULT_PORT.fetch(uri_object.scheme.to_sym, DEFAULT_PORT[:nats])
|
1778
1828
|
|
1779
1829
|
uri_object
|
1780
1830
|
end
|
@@ -1813,6 +1863,9 @@ module NATS
|
|
1813
1863
|
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
1814
1864
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
1815
1865
|
|
1866
|
+
DEFAULT_TOTAL_SUB_CONCURRENCY = 24
|
1867
|
+
DEFAULT_SINGLE_SUB_CONCURRENCY = 1
|
1868
|
+
|
1816
1869
|
# Implementation adapted from https://github.com/redis/redis-rb
|
1817
1870
|
class Socket
|
1818
1871
|
attr_accessor :socket
|
@@ -1823,6 +1876,7 @@ module NATS
|
|
1823
1876
|
@write_timeout = options[:write_timeout]
|
1824
1877
|
@read_timeout = options[:read_timeout]
|
1825
1878
|
@socket = nil
|
1879
|
+
@tls = options[:tls]
|
1826
1880
|
end
|
1827
1881
|
|
1828
1882
|
def connect
|
@@ -1841,6 +1895,22 @@ module NATS
|
|
1841
1895
|
@socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
1842
1896
|
end
|
1843
1897
|
|
1898
|
+
# (Re-)connect using secure connection if server and client agreed on using it.
|
1899
|
+
def setup_tls!
|
1900
|
+
# Setup TLS connection by rewrapping the socket
|
1901
|
+
tls_socket = OpenSSL::SSL::SSLSocket.new(@socket, @tls.fetch(:context))
|
1902
|
+
|
1903
|
+
# Close TCP socket after closing TLS socket as well.
|
1904
|
+
tls_socket.sync_close = true
|
1905
|
+
|
1906
|
+
# Required to enable hostname verification if Ruby runtime supports it (>= 2.4):
|
1907
|
+
# https://github.com/ruby/openssl/commit/028e495734e9e6aa5dba1a2e130b08f66cf31a21
|
1908
|
+
tls_socket.hostname = @tls[:hostname]
|
1909
|
+
|
1910
|
+
tls_socket.connect
|
1911
|
+
@socket = tls_socket
|
1912
|
+
end
|
1913
|
+
|
1844
1914
|
def read_line(deadline=nil)
|
1845
1915
|
# FIXME: Should accumulate and read in a non blocking way instead
|
1846
1916
|
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.
|
@@ -119,6 +119,9 @@ module NATS
|
|
119
119
|
:num_replicas,
|
120
120
|
# Force memory storage
|
121
121
|
:mem_storage,
|
122
|
+
|
123
|
+
# NATS v2.10 features
|
124
|
+
:metadata, :filter_subjects, :max_bytes,
|
122
125
|
keyword_init: true) do
|
123
126
|
def initialize(opts={})
|
124
127
|
# Filter unrecognized fields just in case.
|
@@ -192,6 +195,7 @@ module NATS
|
|
192
195
|
:republish,
|
193
196
|
:allow_direct,
|
194
197
|
:mirror_direct,
|
198
|
+
:metadata,
|
195
199
|
keyword_init: true) do
|
196
200
|
def initialize(opts={})
|
197
201
|
# Filter unrecognized fields just in case.
|
@@ -106,18 +106,37 @@ module NATS
|
|
106
106
|
else
|
107
107
|
config
|
108
108
|
end
|
109
|
-
|
109
|
+
config[:name] ||= config[:durable_name]
|
110
110
|
req_subject = case
|
111
111
|
when config[:name]
|
112
|
-
|
112
|
+
###############################################################################
|
113
|
+
# #
|
114
|
+
# Using names is the supported way of creating consumers (NATS +v2.9.0. #
|
115
|
+
# #
|
116
|
+
###############################################################################
|
113
117
|
if config[:filter_subject] && config[:filter_subject] != ">"
|
114
118
|
"#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}.#{config[:filter_subject]}"
|
115
119
|
else
|
120
|
+
##############################################################################
|
121
|
+
# #
|
122
|
+
# Endpoint to support creating ANY consumer with multi-filters (NATS +v2.10) #
|
123
|
+
# #
|
124
|
+
##############################################################################
|
116
125
|
"#{@prefix}.CONSUMER.CREATE.#{stream}.#{config[:name]}"
|
117
126
|
end
|
118
127
|
when config[:durable_name]
|
128
|
+
###############################################################################
|
129
|
+
# #
|
130
|
+
# Endpoint to support creating DURABLES before NATS v2.9.0. #
|
131
|
+
# #
|
132
|
+
###############################################################################
|
119
133
|
"#{@prefix}.CONSUMER.DURABLE.CREATE.#{stream}.#{config[:durable_name]}"
|
120
134
|
else
|
135
|
+
###############################################################################
|
136
|
+
# #
|
137
|
+
# Endpoint to support creating EPHEMERALS before NATS v2.9.0. #
|
138
|
+
# #
|
139
|
+
###############################################################################
|
121
140
|
"#{@prefix}.CONSUMER.CREATE.#{stream}"
|
122
141
|
end
|
123
142
|
|