nats-pure 2.2.1 → 2.3.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/io/client.rb +215 -144
- data/lib/nats/io/errors.rb +6 -0
- data/lib/nats/io/jetstream/msg/ack_methods.rb +8 -4
- 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
@@ -18,6 +18,7 @@ require_relative 'errors'
|
|
18
18
|
require_relative 'msg'
|
19
19
|
require_relative 'subscription'
|
20
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
|
@@ -971,12 +1027,8 @@ module NATS
|
|
971
1027
|
# Only dispatch message when sure that it would not block
|
972
1028
|
# the main read loop from the parser.
|
973
1029
|
msg = Msg.new(subject: subject, reply: reply, data: data, header: hdr, nc: self, sub: sub)
|
974
|
-
sub.pending_queue << msg
|
975
1030
|
|
976
|
-
|
977
|
-
sub.wait_for_msgs_cond.signal if sub.wait_for_msgs_cond
|
978
|
-
|
979
|
-
sub.pending_size += data.size
|
1031
|
+
sub.dispatch(msg)
|
980
1032
|
end
|
981
1033
|
end
|
982
1034
|
end
|
@@ -1016,11 +1068,32 @@ module NATS
|
|
1016
1068
|
@uri.scheme == "tls" || @tls
|
1017
1069
|
end
|
1018
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
|
+
|
1019
1088
|
def single_url_connect_used?
|
1020
1089
|
@single_url_connect_used
|
1021
1090
|
end
|
1022
1091
|
|
1023
1092
|
def send_command(command)
|
1093
|
+
raise NATS::IO::ConnectionClosedError if closed?
|
1094
|
+
|
1095
|
+
establish_connection! if !status || (disconnected? && should_reconnect?)
|
1096
|
+
|
1024
1097
|
@pending_size += command.bytesize
|
1025
1098
|
@pending_queue << command
|
1026
1099
|
|
@@ -1047,12 +1120,6 @@ module NATS
|
|
1047
1120
|
synchronize do
|
1048
1121
|
sub.max = opt_max
|
1049
1122
|
@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
1123
|
end
|
1057
1124
|
|
1058
1125
|
sub.synchronize do
|
@@ -1107,11 +1174,6 @@ module NATS
|
|
1107
1174
|
|
1108
1175
|
to_delete.each do |sub|
|
1109
1176
|
@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
1177
|
end
|
1116
1178
|
to_delete.clear
|
1117
1179
|
|
@@ -1126,6 +1188,9 @@ module NATS
|
|
1126
1188
|
end
|
1127
1189
|
end
|
1128
1190
|
|
1191
|
+
subscription_executor.shutdown
|
1192
|
+
subscription_executor.wait_for_termination(@options[:drain_timeout])
|
1193
|
+
|
1129
1194
|
if MonotonicTime::now > drain_timeout
|
1130
1195
|
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
1131
1196
|
err_cb_call(self, e, nil) if @err_cb
|
@@ -1287,7 +1352,7 @@ module NATS
|
|
1287
1352
|
@flush_queue.pop
|
1288
1353
|
|
1289
1354
|
should_bail = synchronize do
|
1290
|
-
@status != CONNECTED || @status == CONNECTING
|
1355
|
+
(@status != CONNECTED && !draining? ) || @status == CONNECTING
|
1291
1356
|
end
|
1292
1357
|
return if should_bail
|
1293
1358
|
|
@@ -1342,6 +1407,7 @@ module NATS
|
|
1342
1407
|
end
|
1343
1408
|
|
1344
1409
|
def process_connect_init
|
1410
|
+
# FIXME: Can receive PING as well here in recent versions.
|
1345
1411
|
line = @io.read_line(options[:connect_timeout])
|
1346
1412
|
if !line or line.empty?
|
1347
1413
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
@@ -1356,39 +1422,13 @@ module NATS
|
|
1356
1422
|
|
1357
1423
|
case
|
1358
1424
|
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?)
|
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")
|
1390
1428
|
raise NATS::IO::ConnectError.new('TLS/SSL required by server')
|
1391
|
-
|
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")
|
1392
1432
|
raise NATS::IO::ConnectError.new('TLS/SSL not supported by server')
|
1393
1433
|
else
|
1394
1434
|
# Otherwise, use a regular connection.
|
@@ -1433,11 +1473,6 @@ module NATS
|
|
1433
1473
|
begin
|
1434
1474
|
srv = select_next_server
|
1435
1475
|
|
1436
|
-
# Establish TCP connection with new server
|
1437
|
-
@io = create_socket
|
1438
|
-
@io.connect
|
1439
|
-
@stats[:reconnects] += 1
|
1440
|
-
|
1441
1476
|
# Set hostname to use for TLS hostname verification
|
1442
1477
|
if client_using_secure_connection? and single_url_connect_used?
|
1443
1478
|
# Reuse original hostname name in case of using TLS.
|
@@ -1446,6 +1481,11 @@ module NATS
|
|
1446
1481
|
@hostname = srv[:hostname]
|
1447
1482
|
end
|
1448
1483
|
|
1484
|
+
# Establish TCP connection with new server
|
1485
|
+
@io = create_socket
|
1486
|
+
@io.connect
|
1487
|
+
@stats[:reconnects] += 1
|
1488
|
+
|
1449
1489
|
# Established TCP connection successfully so can start connect
|
1450
1490
|
process_connect_init
|
1451
1491
|
|
@@ -1505,6 +1545,7 @@ module NATS
|
|
1505
1545
|
|
1506
1546
|
def close_connection(conn_status, do_cbs=true)
|
1507
1547
|
synchronize do
|
1548
|
+
@connect_called = false
|
1508
1549
|
if @status == CLOSED
|
1509
1550
|
@status = conn_status
|
1510
1551
|
return
|
@@ -1553,12 +1594,6 @@ module NATS
|
|
1553
1594
|
end if should_flush
|
1554
1595
|
|
1555
1596
|
# 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
1597
|
@subs.clear
|
1563
1598
|
|
1564
1599
|
if do_cbs
|
@@ -1580,15 +1615,24 @@ module NATS
|
|
1580
1615
|
def start_threads!
|
1581
1616
|
# Reading loop for gathering data
|
1582
1617
|
@read_loop_thread = Thread.new { read_loop }
|
1618
|
+
@read_loop_thread.name = "nats:read_loop"
|
1583
1619
|
@read_loop_thread.abort_on_exception = true
|
1584
1620
|
|
1585
1621
|
# Flusher loop for sending commands
|
1586
1622
|
@flusher_thread = Thread.new { flusher_loop }
|
1623
|
+
@flusher_thread.name = "nats:flusher_loop"
|
1587
1624
|
@flusher_thread.abort_on_exception = true
|
1588
1625
|
|
1589
1626
|
# Ping interval handling for keeping alive the connection
|
1590
1627
|
@ping_interval_thread = Thread.new { ping_interval_loop }
|
1628
|
+
@ping_interval_thread.name = "nats:ping_loop"
|
1591
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
|
+
)
|
1592
1636
|
end
|
1593
1637
|
|
1594
1638
|
# Prepares requests subscription that handles the responses
|
@@ -1606,29 +1650,25 @@ module NATS
|
|
1606
1650
|
@resp_sub.pending_msgs_limit = NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
1607
1651
|
@resp_sub.pending_bytes_limit = NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
1608
1652
|
@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
|
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
|
1622
1662
|
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
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
|
1628
1667
|
end
|
1629
1668
|
end
|
1630
1669
|
|
1631
1670
|
sid = (@ssid += 1)
|
1671
|
+
@resp_sub.sid = sid
|
1632
1672
|
@subs[sid] = @resp_sub
|
1633
1673
|
send_command("SUB #{@resp_sub.subject} #{sid}#{CR_LF}")
|
1634
1674
|
@flush_queue << :sub
|
@@ -1661,10 +1701,21 @@ module NATS
|
|
1661
1701
|
end
|
1662
1702
|
|
1663
1703
|
def create_socket
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
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
|
+
)
|
1668
1719
|
end
|
1669
1720
|
|
1670
1721
|
def setup_nkeys_connect
|
@@ -1774,7 +1825,7 @@ module NATS
|
|
1774
1825
|
|
1775
1826
|
# Host and Port
|
1776
1827
|
uri_object.hostname ||= "localhost"
|
1777
|
-
uri_object.port ||= DEFAULT_PORT
|
1828
|
+
uri_object.port ||= DEFAULT_PORT.fetch(uri.scheme.to_sym, DEFAULT_PORT[:nats])
|
1778
1829
|
|
1779
1830
|
uri_object
|
1780
1831
|
end
|
@@ -1813,6 +1864,9 @@ module NATS
|
|
1813
1864
|
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
1814
1865
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
1815
1866
|
|
1867
|
+
DEFAULT_TOTAL_SUB_CONCURRENCY = 24
|
1868
|
+
DEFAULT_SINGLE_SUB_CONCURRENCY = 1
|
1869
|
+
|
1816
1870
|
# Implementation adapted from https://github.com/redis/redis-rb
|
1817
1871
|
class Socket
|
1818
1872
|
attr_accessor :socket
|
@@ -1823,6 +1877,7 @@ module NATS
|
|
1823
1877
|
@write_timeout = options[:write_timeout]
|
1824
1878
|
@read_timeout = options[:read_timeout]
|
1825
1879
|
@socket = nil
|
1880
|
+
@tls = options[:tls]
|
1826
1881
|
end
|
1827
1882
|
|
1828
1883
|
def connect
|
@@ -1841,6 +1896,22 @@ module NATS
|
|
1841
1896
|
@socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
1842
1897
|
end
|
1843
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
|
+
|
1844
1915
|
def read_line(deadline=nil)
|
1845
1916
|
# FIXME: Should accumulate and read in a non blocking way instead
|
1846
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.
|
@@ -41,11 +41,15 @@ module NATS
|
|
41
41
|
|
42
42
|
def nak(**params)
|
43
43
|
ensure_is_acked_once!
|
44
|
-
|
44
|
+
payload = if params[:delay]
|
45
|
+
payload = "#{Ack::Nak} #{{ delay: params[:delay] }.to_json}"
|
46
|
+
else
|
47
|
+
Ack::Nak
|
48
|
+
end
|
45
49
|
resp = if params[:timeout]
|
46
|
-
@nc.request(@reply,
|
50
|
+
@nc.request(@reply, payload, **params)
|
47
51
|
else
|
48
|
-
@nc.publish(@reply,
|
52
|
+
@nc.publish(@reply, payload)
|
49
53
|
end
|
50
54
|
@sub.synchronize { @ackd = true }
|
51
55
|
|
@@ -104,4 +108,4 @@ module NATS
|
|
104
108
|
end
|
105
109
|
end
|
106
110
|
end
|
107
|
-
end
|
111
|
+
end
|
data/lib/nats/io/msg.rb
CHANGED
@@ -50,7 +50,9 @@ module NATS
|
|
50
50
|
|
51
51
|
def inspect
|
52
52
|
hdr = ", header=#{@header}" if @header
|
53
|
-
|
53
|
+
dot = '...' if @data.length > 10
|
54
|
+
dat = "#{data.slice(0, 10)}#{dot}"
|
55
|
+
"#<NATS::Msg(subject: \"#{@subject}\", reply: \"#{@reply}\", data: #{dat.inspect}#{hdr})>"
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rails"
|
2
|
+
|
3
|
+
module NATS
|
4
|
+
class Rails < ::Rails::Engine
|
5
|
+
# This class is used to free resources managed by Rails (e.g. database connections)
|
6
|
+
# that were implicitly acquired in subscription callbacks
|
7
|
+
# Implementation is based on https://github.com/sidekiq/sidekiq/blob/5e1a77a6d03193dd977fbfe8961ab78df91bb392/lib/sidekiq/rails.rb
|
8
|
+
class Reloader
|
9
|
+
def initialize(app = ::Rails.application)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "gem.nats"} : {}
|
15
|
+
@app.reloader.wrap(**params) do
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def inspect
|
21
|
+
"#<NATS::Rails::Reloader @app=#{@app.class.name}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
config.after_initialize do
|
26
|
+
NATS::Client.default_reloader = NATS::Rails::Reloader.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|