nats-pure 2.2.1 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|