nats-pure 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +10 -3
- data/lib/nats/client.rb +7 -3
- data/lib/nats/io/client.rb +464 -328
- data/lib/nats/io/errors.rb +2 -0
- data/lib/nats/io/jetstream/api.rb +53 -50
- data/lib/nats/io/jetstream/errors.rb +30 -14
- data/lib/nats/io/jetstream/js/config.rb +9 -3
- data/lib/nats/io/jetstream/js/header.rb +15 -9
- data/lib/nats/io/jetstream/js/status.rb +11 -5
- data/lib/nats/io/jetstream/js/sub.rb +4 -2
- data/lib/nats/io/jetstream/js.rb +10 -8
- data/lib/nats/io/jetstream/manager.rb +103 -101
- data/lib/nats/io/jetstream/msg/ack.rb +22 -12
- data/lib/nats/io/jetstream/msg/ack_methods.rb +30 -24
- data/lib/nats/io/jetstream/msg/metadata.rb +10 -7
- data/lib/nats/io/jetstream/msg.rb +11 -4
- data/lib/nats/io/jetstream/pull_subscription.rb +21 -10
- data/lib/nats/io/jetstream/push_subscription.rb +3 -1
- data/lib/nats/io/jetstream.rb +102 -106
- data/lib/nats/io/kv/api.rb +7 -3
- data/lib/nats/io/kv/bucket_status.rb +7 -5
- data/lib/nats/io/kv/errors.rb +25 -2
- data/lib/nats/io/kv/manager.rb +19 -10
- data/lib/nats/io/kv.rb +354 -22
- data/lib/nats/io/msg.rb +19 -19
- data/lib/nats/io/parser.rb +23 -23
- data/lib/nats/io/rails.rb +2 -0
- data/lib/nats/io/subscription.rb +33 -23
- data/lib/nats/io/version.rb +4 -2
- data/lib/nats/io/websocket.rb +40 -18
- data/lib/nats/nuid.rb +33 -22
- data/lib/nats/service/callbacks.rb +22 -0
- data/lib/nats/service/endpoint.rb +155 -0
- data/lib/nats/service/errors.rb +44 -0
- data/lib/nats/service/group.rb +37 -0
- data/lib/nats/service/monitoring.rb +108 -0
- data/lib/nats/service/stats.rb +52 -0
- data/lib/nats/service/status.rb +66 -0
- data/lib/nats/service/validator.rb +31 -0
- data/lib/nats/service.rb +121 -0
- data/lib/nats/utils/list.rb +26 -0
- data/lib/nats-pure.rb +5 -0
- data/lib/nats.rb +10 -6
- metadata +176 -8
data/lib/nats/io/client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Copyright 2016-2021 The NATS Authors
|
|
2
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
5
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,21 +14,20 @@
|
|
|
12
14
|
# limitations under the License.
|
|
13
15
|
#
|
|
14
16
|
|
|
15
|
-
require_relative
|
|
16
|
-
require_relative
|
|
17
|
-
require_relative
|
|
18
|
-
require_relative
|
|
19
|
-
require_relative
|
|
20
|
-
require_relative
|
|
21
|
-
|
|
22
|
-
require
|
|
23
|
-
require
|
|
24
|
-
require
|
|
25
|
-
require
|
|
26
|
-
require
|
|
27
|
-
require
|
|
28
|
-
require
|
|
29
|
-
require 'concurrent'
|
|
17
|
+
require_relative "parser"
|
|
18
|
+
require_relative "version"
|
|
19
|
+
require_relative "errors"
|
|
20
|
+
require_relative "msg"
|
|
21
|
+
require_relative "subscription"
|
|
22
|
+
require_relative "jetstream"
|
|
23
|
+
|
|
24
|
+
require "nats/nuid"
|
|
25
|
+
require "socket"
|
|
26
|
+
require "json"
|
|
27
|
+
require "monitor"
|
|
28
|
+
require "uri"
|
|
29
|
+
require "securerandom"
|
|
30
|
+
require "concurrent"
|
|
30
31
|
|
|
31
32
|
begin
|
|
32
33
|
require "openssl"
|
|
@@ -46,7 +47,7 @@ module NATS
|
|
|
46
47
|
# nc.publish("hello", "world")
|
|
47
48
|
# nc.close
|
|
48
49
|
#
|
|
49
|
-
def connect(uri=nil, opts={})
|
|
50
|
+
def connect(uri = nil, opts = {})
|
|
50
51
|
nc = NATS::Client.new
|
|
51
52
|
nc.connect(uri, opts)
|
|
52
53
|
|
|
@@ -100,25 +101,25 @@ module NATS
|
|
|
100
101
|
include MonitorMixin
|
|
101
102
|
include Status
|
|
102
103
|
|
|
103
|
-
attr_reader :status, :server_info, :server_pool, :options, :
|
|
104
|
+
attr_reader :status, :server_info, :server_pool, :options, :stats, :uri, :subscription_executor, :reloader
|
|
104
105
|
|
|
105
|
-
DEFAULT_PORT = {
|
|
106
|
-
DEFAULT_URI =
|
|
106
|
+
DEFAULT_PORT = {nats: 4222, ws: 80, wss: 443}.freeze
|
|
107
|
+
DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT[:nats]}".freeze
|
|
107
108
|
|
|
108
|
-
CR_LF =
|
|
109
|
-
CR_LF_SIZE =
|
|
109
|
+
CR_LF = "\r\n"
|
|
110
|
+
CR_LF_SIZE = CR_LF.bytesize
|
|
110
111
|
|
|
111
|
-
PING_REQUEST
|
|
112
|
-
PONG_RESPONSE =
|
|
112
|
+
PING_REQUEST = "PING#{CR_LF}".freeze
|
|
113
|
+
PONG_RESPONSE = "PONG#{CR_LF}".freeze
|
|
113
114
|
|
|
114
|
-
NATS_HDR_LINE
|
|
115
|
+
NATS_HDR_LINE = "NATS/1.0#{CR_LF}".freeze
|
|
115
116
|
STATUS_MSG_LEN = 3
|
|
116
|
-
STATUS_HDR
|
|
117
|
-
DESC_HDR
|
|
118
|
-
NATS_HDR_LINE_SIZE =
|
|
117
|
+
STATUS_HDR = "Status"
|
|
118
|
+
DESC_HDR = "Description"
|
|
119
|
+
NATS_HDR_LINE_SIZE = NATS_HDR_LINE.bytesize
|
|
119
120
|
|
|
120
|
-
SUB_OP =
|
|
121
|
-
EMPTY_MSG =
|
|
121
|
+
SUB_OP = "SUB"
|
|
122
|
+
EMPTY_MSG = ""
|
|
122
123
|
|
|
123
124
|
INSTANCES = ObjectSpace::WeakMap.new # tracks all alive client instances
|
|
124
125
|
private_constant :INSTANCES
|
|
@@ -135,6 +136,8 @@ module NATS
|
|
|
135
136
|
# Re-establish connection in a new process after forking to start new threads.
|
|
136
137
|
def after_fork
|
|
137
138
|
INSTANCES.each do |client|
|
|
139
|
+
next if client.closed?
|
|
140
|
+
|
|
138
141
|
if client.options[:reconnect]
|
|
139
142
|
was_connected = !client.disconnected?
|
|
140
143
|
client.send(:close_connection, Status::DISCONNECTED, true)
|
|
@@ -170,7 +173,7 @@ module NATS
|
|
|
170
173
|
@ping_interval_thread = nil
|
|
171
174
|
|
|
172
175
|
# Info that we get from the server
|
|
173
|
-
@server_info = {
|
|
176
|
+
@server_info = {}
|
|
174
177
|
|
|
175
178
|
# URI from server to which we are currently connected
|
|
176
179
|
@uri = nil
|
|
@@ -179,7 +182,7 @@ module NATS
|
|
|
179
182
|
@status = nil
|
|
180
183
|
|
|
181
184
|
# Subscriptions
|
|
182
|
-
@subs = {
|
|
185
|
+
@subs = {}
|
|
183
186
|
@ssid = 0
|
|
184
187
|
|
|
185
188
|
# Ping interval
|
|
@@ -202,10 +205,10 @@ module NATS
|
|
|
202
205
|
@last_err = nil
|
|
203
206
|
|
|
204
207
|
# Async callbacks, no ops by default.
|
|
205
|
-
@err_cb = proc {
|
|
206
|
-
@close_cb = proc {
|
|
207
|
-
@disconnect_cb = proc {
|
|
208
|
-
@reconnect_cb = proc {
|
|
208
|
+
@err_cb = proc {}
|
|
209
|
+
@close_cb = proc {}
|
|
210
|
+
@disconnect_cb = proc {}
|
|
211
|
+
@reconnect_cb = proc {}
|
|
209
212
|
|
|
210
213
|
# Secure TLS options
|
|
211
214
|
@tls = nil
|
|
@@ -218,6 +221,10 @@ module NATS
|
|
|
218
221
|
# Track whether connect has been already been called.
|
|
219
222
|
@connect_called = false
|
|
220
223
|
|
|
224
|
+
# Bumped by every close, so that a reconnect started earlier can tell
|
|
225
|
+
# that the connection was closed meanwhile.
|
|
226
|
+
@close_generation = 0
|
|
227
|
+
|
|
221
228
|
# New style request/response implementation.
|
|
222
229
|
@resp_sub = nil
|
|
223
230
|
@resp_map = nil
|
|
@@ -239,6 +246,9 @@ module NATS
|
|
|
239
246
|
# Draining
|
|
240
247
|
@drain_t = nil
|
|
241
248
|
|
|
249
|
+
# Service API
|
|
250
|
+
@_services = nil
|
|
251
|
+
|
|
242
252
|
# Prepare for calling connect or automatic delayed connection
|
|
243
253
|
parse_and_validate_options if uri || opts.any?
|
|
244
254
|
|
|
@@ -249,7 +259,7 @@ module NATS
|
|
|
249
259
|
end
|
|
250
260
|
|
|
251
261
|
# Prepare connecting to NATS, but postpone real connection until first usage.
|
|
252
|
-
def connect(uri=nil, opts={})
|
|
262
|
+
def connect(uri = nil, opts = {})
|
|
253
263
|
if uri || opts.any?
|
|
254
264
|
@initial_uri = uri
|
|
255
265
|
@initial_options = opts
|
|
@@ -267,6 +277,19 @@ module NATS
|
|
|
267
277
|
self
|
|
268
278
|
end
|
|
269
279
|
|
|
280
|
+
def force_reconnect
|
|
281
|
+
synchronize do
|
|
282
|
+
return true if reconnecting?
|
|
283
|
+
|
|
284
|
+
if closed? || draining? || disconnected?
|
|
285
|
+
raise NATS::IO::ConnectionClosedError
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
initiate_reconnect
|
|
289
|
+
true
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
270
293
|
private def parse_and_validate_options
|
|
271
294
|
# Reset these in case we have reconnected via fork.
|
|
272
295
|
@server_pool = []
|
|
@@ -293,8 +316,8 @@ module NATS
|
|
|
293
316
|
when String
|
|
294
317
|
# Initialize TLS defaults in case any url is using it.
|
|
295
318
|
srvs = opts[:servers] = process_uri(uri)
|
|
296
|
-
if srvs.any? {|u| %w[tls wss].include? u.scheme }
|
|
297
|
-
opts[:tls] = {
|
|
319
|
+
if srvs.any? { |u| %w[tls wss].include? u.scheme } && !opts[:tls]
|
|
320
|
+
opts[:tls] = {context: tls_context}
|
|
298
321
|
end
|
|
299
322
|
@single_url_connect_used = true if srvs.size == 1
|
|
300
323
|
when Hash
|
|
@@ -312,16 +335,17 @@ module NATS
|
|
|
312
335
|
opts[:max_outstanding_pings] = NATS::IO::DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
|
|
313
336
|
|
|
314
337
|
# Override with ENV
|
|
315
|
-
opts[:verbose] = ENV[
|
|
316
|
-
opts[:pedantic] = ENV[
|
|
317
|
-
opts[:reconnect] = ENV[
|
|
318
|
-
opts[:reconnect_time_wait] = ENV[
|
|
319
|
-
opts[:ignore_discovered_urls] = ENV[
|
|
320
|
-
opts[:max_reconnect_attempts] = ENV[
|
|
321
|
-
opts[:ping_interval] = ENV[
|
|
322
|
-
opts[:max_outstanding_pings] = ENV[
|
|
338
|
+
opts[:verbose] = ENV["NATS_VERBOSE"].downcase == "true" unless ENV["NATS_VERBOSE"].nil?
|
|
339
|
+
opts[:pedantic] = ENV["NATS_PEDANTIC"].downcase == "true" unless ENV["NATS_PEDANTIC"].nil?
|
|
340
|
+
opts[:reconnect] = ENV["NATS_RECONNECT"].downcase == "true" unless ENV["NATS_RECONNECT"].nil?
|
|
341
|
+
opts[:reconnect_time_wait] = ENV["NATS_RECONNECT_TIME_WAIT"].to_i unless ENV["NATS_RECONNECT_TIME_WAIT"].nil?
|
|
342
|
+
opts[:ignore_discovered_urls] = ENV["NATS_IGNORE_DISCOVERED_URLS"].downcase == "true" unless ENV["NATS_IGNORE_DISCOVERED_URLS"].nil?
|
|
343
|
+
opts[:max_reconnect_attempts] = ENV["NATS_MAX_RECONNECT_ATTEMPTS"].to_i unless ENV["NATS_MAX_RECONNECT_ATTEMPTS"].nil?
|
|
344
|
+
opts[:ping_interval] = ENV["NATS_PING_INTERVAL"].to_i unless ENV["NATS_PING_INTERVAL"].nil?
|
|
345
|
+
opts[:max_outstanding_pings] = ENV["NATS_MAX_OUTSTANDING_PINGS"].to_i unless ENV["NATS_MAX_OUTSTANDING_PINGS"].nil?
|
|
323
346
|
opts[:connect_timeout] ||= NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
|
324
347
|
opts[:drain_timeout] ||= NATS::IO::DEFAULT_DRAIN_TIMEOUT
|
|
348
|
+
opts[:close_timeout] ||= NATS::IO::DEFAULT_CLOSE_TIMEOUT
|
|
325
349
|
@options = opts
|
|
326
350
|
|
|
327
351
|
# Process servers in the NATS cluster and pick one to connect
|
|
@@ -329,14 +353,14 @@ module NATS
|
|
|
329
353
|
uris.shuffle! unless @options[:dont_randomize_servers]
|
|
330
354
|
uris.each do |u|
|
|
331
355
|
nats_uri = case u
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
when URI
|
|
357
|
+
u.dup
|
|
358
|
+
else
|
|
359
|
+
URI.parse(u)
|
|
360
|
+
end
|
|
337
361
|
@server_pool << {
|
|
338
|
-
:
|
|
339
|
-
:
|
|
362
|
+
uri: nats_uri,
|
|
363
|
+
hostname: nats_uri.hostname
|
|
340
364
|
}
|
|
341
365
|
end
|
|
342
366
|
|
|
@@ -353,7 +377,7 @@ module NATS
|
|
|
353
377
|
@user_credentials ||= opts[:user_credentials]
|
|
354
378
|
@nkeys_seed ||= opts[:nkeys_seed]
|
|
355
379
|
|
|
356
|
-
setup_nkeys_connect if @user_credentials
|
|
380
|
+
setup_nkeys_connect if @user_credentials || @nkeys_seed
|
|
357
381
|
|
|
358
382
|
# Tokens, if set will take preference over the user@server uri token
|
|
359
383
|
@auth_token ||= opts[:auth_token]
|
|
@@ -376,7 +400,7 @@ module NATS
|
|
|
376
400
|
srv = select_next_server
|
|
377
401
|
|
|
378
402
|
# Use the hostname from the server for TLS hostname verification.
|
|
379
|
-
if client_using_secure_connection?
|
|
403
|
+
if client_using_secure_connection? && single_url_connect_used?
|
|
380
404
|
# Always reuse the original hostname used to connect.
|
|
381
405
|
@hostname ||= srv[:hostname]
|
|
382
406
|
else
|
|
@@ -449,8 +473,8 @@ module NATS
|
|
|
449
473
|
self
|
|
450
474
|
end
|
|
451
475
|
|
|
452
|
-
def publish(subject, msg=EMPTY_MSG, opt_reply=nil, **options, &blk)
|
|
453
|
-
raise NATS::IO::BadSubject if !subject
|
|
476
|
+
def publish(subject, msg = EMPTY_MSG, opt_reply = nil, **options, &blk)
|
|
477
|
+
raise NATS::IO::BadSubject if !subject || subject.empty?
|
|
454
478
|
if options[:header]
|
|
455
479
|
return publish_msg(NATS::Msg.new(subject: subject, data: msg, reply: opt_reply, header: options[:header]))
|
|
456
480
|
end
|
|
@@ -467,10 +491,10 @@ module NATS
|
|
|
467
491
|
# Publishes a NATS::Msg that may include headers.
|
|
468
492
|
def publish_msg(msg)
|
|
469
493
|
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
|
470
|
-
raise NATS::IO::BadSubject if !msg.subject
|
|
494
|
+
raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty?
|
|
471
495
|
|
|
472
|
-
msg.reply ||=
|
|
473
|
-
msg.data ||=
|
|
496
|
+
msg.reply ||= "".dup
|
|
497
|
+
msg.data ||= "".dup
|
|
474
498
|
msg_size = msg.data.bytesize
|
|
475
499
|
|
|
476
500
|
# Accounting
|
|
@@ -478,7 +502,7 @@ module NATS
|
|
|
478
502
|
@stats[:out_bytes] += msg_size
|
|
479
503
|
|
|
480
504
|
if msg.header
|
|
481
|
-
hdr =
|
|
505
|
+
hdr = "".dup
|
|
482
506
|
hdr << NATS_HDR_LINE
|
|
483
507
|
msg.header.each do |k, v|
|
|
484
508
|
hdr << "#{k}: #{v}#{CR_LF}"
|
|
@@ -496,7 +520,7 @@ module NATS
|
|
|
496
520
|
|
|
497
521
|
# Create subscription which is dispatched asynchronously
|
|
498
522
|
# messages to a callback.
|
|
499
|
-
def subscribe(subject, opts={}, &callback)
|
|
523
|
+
def subscribe(subject, opts = {}, &callback)
|
|
500
524
|
raise NATS::IO::ConnectionDrainingError.new("nats: connection draining") if draining?
|
|
501
525
|
|
|
502
526
|
sid = nil
|
|
@@ -507,7 +531,7 @@ module NATS
|
|
|
507
531
|
sub.nc = self
|
|
508
532
|
sub.sid = sid
|
|
509
533
|
end
|
|
510
|
-
opts[:pending_msgs_limit]
|
|
534
|
+
opts[:pending_msgs_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_MSGS_LIMIT
|
|
511
535
|
opts[:pending_bytes_limit] ||= NATS::IO::DEFAULT_SUB_PENDING_BYTES_LIMIT
|
|
512
536
|
|
|
513
537
|
sub.subject = subject
|
|
@@ -515,7 +539,7 @@ module NATS
|
|
|
515
539
|
sub.received = 0
|
|
516
540
|
sub.queue = opts[:queue] if opts[:queue]
|
|
517
541
|
sub.max = opts[:max] if opts[:max]
|
|
518
|
-
sub.pending_msgs_limit
|
|
542
|
+
sub.pending_msgs_limit = opts[:pending_msgs_limit]
|
|
519
543
|
sub.pending_bytes_limit = opts[:pending_bytes_limit]
|
|
520
544
|
sub.pending_queue = SizedQueue.new(sub.pending_msgs_limit)
|
|
521
545
|
sub.processing_concurrency = opts[:processing_concurrency] if opts.key?(:processing_concurrency)
|
|
@@ -539,8 +563,8 @@ module NATS
|
|
|
539
563
|
# It times out in case the request is not retrieved within the
|
|
540
564
|
# specified deadline.
|
|
541
565
|
# If given a callback, then the request happens asynchronously.
|
|
542
|
-
def request(subject, payload="", **opts, &blk)
|
|
543
|
-
raise NATS::IO::BadSubject if !subject
|
|
566
|
+
def request(subject, payload = "", **opts, &blk)
|
|
567
|
+
raise NATS::IO::BadSubject if !subject || subject.empty?
|
|
544
568
|
|
|
545
569
|
# If a block was given then fallback to method using auto unsubscribe.
|
|
546
570
|
return old_request(subject, payload, opts, &blk) if blk
|
|
@@ -571,7 +595,7 @@ module NATS
|
|
|
571
595
|
# Publish request and wait for reply.
|
|
572
596
|
publish(subject, payload, inbox)
|
|
573
597
|
begin
|
|
574
|
-
MonotonicTime
|
|
598
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
575
599
|
@resp_sub.synchronize do
|
|
576
600
|
future.wait(timeout)
|
|
577
601
|
end
|
|
@@ -588,7 +612,7 @@ module NATS
|
|
|
588
612
|
@resp_map.delete(token)
|
|
589
613
|
end
|
|
590
614
|
|
|
591
|
-
if response
|
|
615
|
+
if response&.header
|
|
592
616
|
status = response.header[STATUS_HDR]
|
|
593
617
|
raise NATS::IO::NoRespondersError if status == "503"
|
|
594
618
|
end
|
|
@@ -599,7 +623,7 @@ module NATS
|
|
|
599
623
|
# request_msg makes a NATS request using a NATS::Msg that may include headers.
|
|
600
624
|
def request_msg(msg, **opts)
|
|
601
625
|
raise TypeError, "nats: expected NATS::Msg, got #{msg.class.name}" unless msg.is_a?(Msg)
|
|
602
|
-
raise NATS::IO::BadSubject if !msg.subject
|
|
626
|
+
raise NATS::IO::BadSubject if !msg.subject || msg.subject.empty?
|
|
603
627
|
|
|
604
628
|
token = nil
|
|
605
629
|
inbox = nil
|
|
@@ -619,13 +643,13 @@ module NATS
|
|
|
619
643
|
@resp_map[token][:future] = future
|
|
620
644
|
end
|
|
621
645
|
msg.reply = inbox
|
|
622
|
-
msg.data ||=
|
|
623
|
-
|
|
646
|
+
msg.data ||= ""
|
|
647
|
+
msg.data.bytesize
|
|
624
648
|
|
|
625
649
|
# Publish request and wait for reply.
|
|
626
650
|
publish_msg(msg)
|
|
627
651
|
begin
|
|
628
|
-
MonotonicTime
|
|
652
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
629
653
|
@resp_sub.synchronize do
|
|
630
654
|
future.wait(timeout)
|
|
631
655
|
end
|
|
@@ -642,7 +666,7 @@ module NATS
|
|
|
642
666
|
@resp_map.delete(token)
|
|
643
667
|
end
|
|
644
668
|
|
|
645
|
-
if response
|
|
669
|
+
if response&.header
|
|
646
670
|
status = response.header[STATUS_HDR]
|
|
647
671
|
raise NATS::IO::NoRespondersError if status == "503"
|
|
648
672
|
end
|
|
@@ -654,7 +678,7 @@ module NATS
|
|
|
654
678
|
# expecting a single response or raising a timeout in case the request
|
|
655
679
|
# is not retrieved within the specified deadline.
|
|
656
680
|
# If given a callback, then the request happens asynchronously.
|
|
657
|
-
def old_request(subject, payload, opts={}, &blk)
|
|
681
|
+
def old_request(subject, payload, opts = {}, &blk)
|
|
658
682
|
return unless subject
|
|
659
683
|
inbox = new_inbox
|
|
660
684
|
|
|
@@ -703,13 +727,13 @@ module NATS
|
|
|
703
727
|
# Publish the request and then wait for the response...
|
|
704
728
|
publish(subject, payload, inbox)
|
|
705
729
|
|
|
706
|
-
MonotonicTime
|
|
730
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
707
731
|
future.wait(timeout)
|
|
708
732
|
end
|
|
709
733
|
end
|
|
710
734
|
response = sub.response
|
|
711
735
|
|
|
712
|
-
if response
|
|
736
|
+
if response&.header
|
|
713
737
|
status = response.header[STATUS_HDR]
|
|
714
738
|
raise NATS::IO::NoRespondersError if status == "503"
|
|
715
739
|
end
|
|
@@ -718,7 +742,7 @@ module NATS
|
|
|
718
742
|
end
|
|
719
743
|
|
|
720
744
|
# Send a ping and wait for a pong back within a timeout.
|
|
721
|
-
def flush(timeout=10)
|
|
745
|
+
def flush(timeout = 10)
|
|
722
746
|
# Schedule sending a PING, and block until we receive PONG back,
|
|
723
747
|
# or raise a timeout in case the response is past the deadline.
|
|
724
748
|
pong = @pongs.new_cond
|
|
@@ -728,18 +752,18 @@ module NATS
|
|
|
728
752
|
# Flush once pong future has been prepared
|
|
729
753
|
@pending_queue << PING_REQUEST
|
|
730
754
|
@flush_queue << :ping
|
|
731
|
-
MonotonicTime
|
|
755
|
+
MonotonicTime.with_nats_timeout(timeout) do
|
|
732
756
|
pong.wait(timeout)
|
|
733
757
|
end
|
|
734
758
|
end
|
|
735
759
|
end
|
|
736
760
|
|
|
737
|
-
|
|
761
|
+
alias_method :servers, :server_pool
|
|
738
762
|
|
|
739
763
|
# discovered_servers returns the NATS Servers that have been discovered
|
|
740
764
|
# via INFO protocol updates.
|
|
741
765
|
def discovered_servers
|
|
742
|
-
servers.select {|s| s[:discovered] }
|
|
766
|
+
servers.select { |s| s[:discovered] }
|
|
743
767
|
end
|
|
744
768
|
|
|
745
769
|
# Close connection to NATS, flushing in case connection is alive
|
|
@@ -780,7 +804,7 @@ module NATS
|
|
|
780
804
|
end
|
|
781
805
|
|
|
782
806
|
def draining?
|
|
783
|
-
if @status == DRAINING_PUBS
|
|
807
|
+
if (@status == DRAINING_PUBS) || (@status == DRAINING_SUBS)
|
|
784
808
|
return true
|
|
785
809
|
end
|
|
786
810
|
|
|
@@ -833,12 +857,16 @@ module NATS
|
|
|
833
857
|
# @option params [String] :domain JetStream Domain to use for the requests.
|
|
834
858
|
# @option params [Float] :timeout Default timeout to use for JS requests.
|
|
835
859
|
# @return [NATS::JetStream]
|
|
836
|
-
def jetstream(opts={})
|
|
860
|
+
def jetstream(opts = {})
|
|
837
861
|
::NATS::JetStream.new(self, opts)
|
|
838
862
|
end
|
|
839
863
|
alias_method :JetStream, :jetstream
|
|
840
864
|
alias_method :jsm, :jetstream
|
|
841
865
|
|
|
866
|
+
def services
|
|
867
|
+
synchronize { @_services ||= Services.new(self) }
|
|
868
|
+
end
|
|
869
|
+
|
|
842
870
|
private
|
|
843
871
|
|
|
844
872
|
def validate_settings!
|
|
@@ -855,10 +883,8 @@ module NATS
|
|
|
855
883
|
# so has to be done under the lock.
|
|
856
884
|
synchronize do
|
|
857
885
|
# Symbolize keys from parsed info line
|
|
858
|
-
@server_info = parsed_info.
|
|
886
|
+
@server_info = parsed_info.each_with_object({}) do |(k, v), info|
|
|
859
887
|
info[k.to_sym] = v
|
|
860
|
-
|
|
861
|
-
info
|
|
862
888
|
end
|
|
863
889
|
|
|
864
890
|
# Detect any announced server that we might not be aware of...
|
|
@@ -877,7 +903,7 @@ module NATS
|
|
|
877
903
|
srv[:uri].hostname == u.hostname && srv[:uri].port == u.port
|
|
878
904
|
end
|
|
879
905
|
|
|
880
|
-
if
|
|
906
|
+
if !present
|
|
881
907
|
# Let explicit user and pass options set the credentials.
|
|
882
908
|
u.user = options[:user] if options[:user]
|
|
883
909
|
u.password = options[:pass] if options[:pass]
|
|
@@ -889,7 +915,7 @@ module NATS
|
|
|
889
915
|
end
|
|
890
916
|
|
|
891
917
|
# NOTE: Auto discovery won't work here when TLS host verification is enabled.
|
|
892
|
-
srv = {
|
|
918
|
+
srv = {uri: u, reconnect_attempts: 0, discovered: true, hostname: u.hostname}
|
|
893
919
|
srvs << srv
|
|
894
920
|
end
|
|
895
921
|
end
|
|
@@ -912,13 +938,13 @@ module NATS
|
|
|
912
938
|
# Check if the first line has an inline status and description.
|
|
913
939
|
if lines.count > 0
|
|
914
940
|
status_hdr = lines.first.rstrip
|
|
915
|
-
status = status_hdr.slice(NATS_HDR_LINE_SIZE-1, STATUS_MSG_LEN)
|
|
941
|
+
status = status_hdr.slice(NATS_HDR_LINE_SIZE - 1, STATUS_MSG_LEN)
|
|
916
942
|
|
|
917
|
-
if status
|
|
943
|
+
if status && !status.empty?
|
|
918
944
|
hdr[STATUS_HDR] = status
|
|
919
945
|
|
|
920
|
-
if NATS_HDR_LINE_SIZE+2 < status_hdr.bytesize
|
|
921
|
-
desc = status_hdr.slice(NATS_HDR_LINE_SIZE+STATUS_MSG_LEN, status_hdr.bytesize)
|
|
946
|
+
if NATS_HDR_LINE_SIZE + 2 < status_hdr.bytesize
|
|
947
|
+
desc = status_hdr.slice(NATS_HDR_LINE_SIZE + STATUS_MSG_LEN, status_hdr.bytesize)
|
|
922
948
|
hdr[DESC_HDR] = desc unless desc.empty?
|
|
923
949
|
end
|
|
924
950
|
end
|
|
@@ -931,7 +957,7 @@ module NATS
|
|
|
931
957
|
hdr[key] = value
|
|
932
958
|
end
|
|
933
959
|
rescue => e
|
|
934
|
-
|
|
960
|
+
e
|
|
935
961
|
end
|
|
936
962
|
end
|
|
937
963
|
|
|
@@ -944,7 +970,7 @@ module NATS
|
|
|
944
970
|
# Take first pong wait and signal any flush in case there was one
|
|
945
971
|
@pongs.synchronize do
|
|
946
972
|
pong = @pongs.pop
|
|
947
|
-
pong
|
|
973
|
+
pong&.signal
|
|
948
974
|
end
|
|
949
975
|
@pings_outstanding -= 1
|
|
950
976
|
@pongs_received += 1
|
|
@@ -964,10 +990,9 @@ module NATS
|
|
|
964
990
|
# while holding the lock.
|
|
965
991
|
e = synchronize do
|
|
966
992
|
current = server_pool.first
|
|
967
|
-
|
|
968
|
-
when err =~ /'Stale Connection'/
|
|
993
|
+
if err =~ /'Stale Connection'/
|
|
969
994
|
@last_err = NATS::IO::StaleConnectionError.new(err)
|
|
970
|
-
|
|
995
|
+
elsif current && current[:auth_required]
|
|
971
996
|
# We cannot recover from auth errors so mark it to avoid
|
|
972
997
|
# retrying to unecessarily next time.
|
|
973
998
|
current[:error_received] = true
|
|
@@ -1017,8 +1042,8 @@ module NATS
|
|
|
1017
1042
|
elsif sub.pending_queue
|
|
1018
1043
|
# Async subscribers use a sized queue for processing
|
|
1019
1044
|
# and should be able to consume messages in parallel.
|
|
1020
|
-
if sub.pending_queue.size >= sub.pending_msgs_limit \
|
|
1021
|
-
|
|
1045
|
+
if (sub.pending_queue.size >= sub.pending_msgs_limit) \
|
|
1046
|
+
|| (sub.pending_size >= sub.pending_bytes_limit)
|
|
1022
1047
|
err = NATS::IO::SlowConsumer.new("nats: slow consumer, messages dropped")
|
|
1023
1048
|
else
|
|
1024
1049
|
hdr = process_hdr(header)
|
|
@@ -1032,10 +1057,12 @@ module NATS
|
|
|
1032
1057
|
end
|
|
1033
1058
|
end
|
|
1034
1059
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1060
|
+
if err
|
|
1061
|
+
synchronize do
|
|
1062
|
+
@last_err = err
|
|
1063
|
+
err_cb_call(self, err, sub) if @err_cb
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1039
1066
|
end
|
|
1040
1067
|
|
|
1041
1068
|
def select_next_server
|
|
@@ -1101,7 +1128,7 @@ module NATS
|
|
|
1101
1128
|
|
|
1102
1129
|
# Auto unsubscribes the server by sending UNSUB command and throws away
|
|
1103
1130
|
# subscription in case already present and has received enough messages.
|
|
1104
|
-
def unsubscribe(sub, opt_max=nil)
|
|
1131
|
+
def unsubscribe(sub, opt_max = nil)
|
|
1105
1132
|
sid = nil
|
|
1106
1133
|
closed = nil
|
|
1107
1134
|
sub.synchronize do
|
|
@@ -1118,7 +1145,7 @@ module NATS
|
|
|
1118
1145
|
return unless sub
|
|
1119
1146
|
synchronize do
|
|
1120
1147
|
sub.max = opt_max
|
|
1121
|
-
@subs.delete(sid) unless
|
|
1148
|
+
@subs.delete(sid) unless sub.max && (sub.received < sub.max)
|
|
1122
1149
|
end
|
|
1123
1150
|
|
|
1124
1151
|
sub.synchronize do
|
|
@@ -1138,8 +1165,9 @@ module NATS
|
|
|
1138
1165
|
send_command("UNSUB #{sid}#{CR_LF}")
|
|
1139
1166
|
@flush_queue << :drain
|
|
1140
1167
|
|
|
1168
|
+
sub.synchronize { sub.drained = true }
|
|
1141
1169
|
synchronize { sub = @subs[sid] }
|
|
1142
|
-
|
|
1170
|
+
nil unless sub
|
|
1143
1171
|
end
|
|
1144
1172
|
|
|
1145
1173
|
def do_drain
|
|
@@ -1156,16 +1184,16 @@ module NATS
|
|
|
1156
1184
|
force_flush!
|
|
1157
1185
|
|
|
1158
1186
|
# Wait until all subs have no pending messages.
|
|
1159
|
-
drain_timeout = MonotonicTime
|
|
1187
|
+
drain_timeout = MonotonicTime.now + @options[:drain_timeout]
|
|
1160
1188
|
to_delete = []
|
|
1161
1189
|
|
|
1162
1190
|
loop do
|
|
1163
|
-
break if MonotonicTime
|
|
1191
|
+
break if MonotonicTime.now > drain_timeout
|
|
1164
1192
|
sleep 0.1
|
|
1165
1193
|
|
|
1166
1194
|
# Wait until all subs are done.
|
|
1167
1195
|
@subs.each do |_, sub|
|
|
1168
|
-
if sub != @resp_sub
|
|
1196
|
+
if (sub != @resp_sub) && (sub.pending_queue.size == 0)
|
|
1169
1197
|
to_delete << sub
|
|
1170
1198
|
end
|
|
1171
1199
|
end
|
|
@@ -1178,7 +1206,7 @@ module NATS
|
|
|
1178
1206
|
|
|
1179
1207
|
# Wait until only the resp mux is remaining or there are no subscriptions.
|
|
1180
1208
|
if @subs.count == 1
|
|
1181
|
-
|
|
1209
|
+
_, sub = @subs.first
|
|
1182
1210
|
if sub == @resp_sub
|
|
1183
1211
|
break
|
|
1184
1212
|
end
|
|
@@ -1190,7 +1218,7 @@ module NATS
|
|
|
1190
1218
|
subscription_executor.shutdown
|
|
1191
1219
|
subscription_executor.wait_for_termination(@options[:drain_timeout])
|
|
1192
1220
|
|
|
1193
|
-
if MonotonicTime
|
|
1221
|
+
if MonotonicTime.now > drain_timeout
|
|
1194
1222
|
e = NATS::IO::DrainTimeoutError.new("nats: draining connection timed out")
|
|
1195
1223
|
err_cb_call(self, e, nil) if @err_cb
|
|
1196
1224
|
end
|
|
@@ -1227,27 +1255,26 @@ module NATS
|
|
|
1227
1255
|
|
|
1228
1256
|
def connect_command
|
|
1229
1257
|
cs = {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1258
|
+
verbose: @options[:verbose],
|
|
1259
|
+
pedantic: @options[:pedantic],
|
|
1260
|
+
lang: NATS::IO::LANG,
|
|
1261
|
+
version: NATS::IO::VERSION,
|
|
1262
|
+
protocol: NATS::IO::PROTOCOL
|
|
1235
1263
|
}
|
|
1236
1264
|
cs[:name] = @options[:name] if @options[:name]
|
|
1237
1265
|
|
|
1238
|
-
|
|
1239
|
-
when auth_connection?
|
|
1266
|
+
if auth_connection?
|
|
1240
1267
|
if @uri.password
|
|
1241
1268
|
cs[:user] = @uri.user
|
|
1242
1269
|
cs[:pass] = @uri.password
|
|
1243
1270
|
else
|
|
1244
1271
|
cs[:auth_token] = @uri.user
|
|
1245
1272
|
end
|
|
1246
|
-
|
|
1273
|
+
elsif @user_jwt_cb && @signature_cb
|
|
1247
1274
|
nonce = @server_info[:nonce]
|
|
1248
1275
|
cs[:jwt] = @user_jwt_cb.call
|
|
1249
1276
|
cs[:sig] = @signature_cb.call(nonce)
|
|
1250
|
-
|
|
1277
|
+
elsif @user_nkey_cb && @signature_cb
|
|
1251
1278
|
nonce = @server_info[:nonce]
|
|
1252
1279
|
cs[:nkey] = @user_nkey_cb.call
|
|
1253
1280
|
cs[:sig] = @signature_cb.call(nonce)
|
|
@@ -1258,10 +1285,10 @@ module NATS
|
|
|
1258
1285
|
if @server_info[:headers]
|
|
1259
1286
|
cs[:headers] = @server_info[:headers]
|
|
1260
1287
|
cs[:no_responders] = if @options[:no_responders] == false
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1288
|
+
@options[:no_responders]
|
|
1289
|
+
else
|
|
1290
|
+
@server_info[:headers]
|
|
1291
|
+
end
|
|
1265
1292
|
end
|
|
1266
1293
|
|
|
1267
1294
|
"CONNECT #{cs.to_json}#{CR_LF}"
|
|
@@ -1270,6 +1297,11 @@ module NATS
|
|
|
1270
1297
|
# Handles errors from reading, parsing the protocol or stale connection.
|
|
1271
1298
|
# the lock should not be held entering this function.
|
|
1272
1299
|
def process_op_error(e)
|
|
1300
|
+
# A background thread being stopped by close or a reconnect sees
|
|
1301
|
+
# errors from that; it must not start another close or reconnect,
|
|
1302
|
+
# which would wait for the thread that is stopping it.
|
|
1303
|
+
return if stopping?
|
|
1304
|
+
|
|
1273
1305
|
should_bail = synchronize do
|
|
1274
1306
|
connecting? || closed? || reconnecting?
|
|
1275
1307
|
end
|
|
@@ -1281,30 +1313,8 @@ module NATS
|
|
|
1281
1313
|
|
|
1282
1314
|
# If we were connected and configured to reconnect,
|
|
1283
1315
|
# then trigger disconnect and start reconnection logic
|
|
1284
|
-
if connected?
|
|
1285
|
-
|
|
1286
|
-
@io.close if @io
|
|
1287
|
-
@io = nil
|
|
1288
|
-
|
|
1289
|
-
# TODO: Reconnecting pending buffer?
|
|
1290
|
-
|
|
1291
|
-
# Do reconnect under a different thread than the one
|
|
1292
|
-
# in which we got the error.
|
|
1293
|
-
Thread.new do
|
|
1294
|
-
begin
|
|
1295
|
-
# Abort currently running reads in case they're around
|
|
1296
|
-
# FIXME: There might be more graceful way here...
|
|
1297
|
-
@read_loop_thread.exit if @read_loop_thread.alive?
|
|
1298
|
-
@flusher_thread.exit if @flusher_thread.alive?
|
|
1299
|
-
@ping_interval_thread.exit if @ping_interval_thread.alive?
|
|
1300
|
-
|
|
1301
|
-
attempt_reconnect
|
|
1302
|
-
rescue NATS::IO::NoServersError => e
|
|
1303
|
-
@last_err = e
|
|
1304
|
-
close
|
|
1305
|
-
end
|
|
1306
|
-
end
|
|
1307
|
-
|
|
1316
|
+
if connected? && should_reconnect?
|
|
1317
|
+
initiate_reconnect
|
|
1308
1318
|
Thread.exit
|
|
1309
1319
|
return
|
|
1310
1320
|
end
|
|
@@ -1317,29 +1327,63 @@ module NATS
|
|
|
1317
1327
|
close
|
|
1318
1328
|
end
|
|
1319
1329
|
|
|
1330
|
+
def initiate_reconnect
|
|
1331
|
+
@status = RECONNECTING
|
|
1332
|
+
old_io = @io
|
|
1333
|
+
@io = nil
|
|
1334
|
+
|
|
1335
|
+
# Wake a read loop blocked on the socket by shutting it down, and
|
|
1336
|
+
# close the socket only once the loop has stopped: on JRuby a close
|
|
1337
|
+
# right after the shutdown can leave the read blocked for good.
|
|
1338
|
+
if @read_loop_thread&.alive? && @read_loop_thread != Thread.current
|
|
1339
|
+
old_io&.shutdown_read
|
|
1340
|
+
end
|
|
1341
|
+
|
|
1342
|
+
# TODO: Reconnecting pending buffer?
|
|
1343
|
+
|
|
1344
|
+
generation = @close_generation
|
|
1345
|
+
|
|
1346
|
+
# Do reconnect under a different thread than the one
|
|
1347
|
+
# in which we got the error.
|
|
1348
|
+
Thread.new do
|
|
1349
|
+
stop_threads!
|
|
1350
|
+
old_io&.close
|
|
1351
|
+
|
|
1352
|
+
@subscription_executor.shutdown
|
|
1353
|
+
|
|
1354
|
+
attempt_reconnect(generation)
|
|
1355
|
+
rescue NATS::IO::NoServersError => e
|
|
1356
|
+
@last_err = e
|
|
1357
|
+
close
|
|
1358
|
+
end
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1320
1361
|
# Gathers data from the socket and sends it to the parser.
|
|
1321
1362
|
def read_loop
|
|
1322
1363
|
loop do
|
|
1323
|
-
|
|
1324
|
-
should_bail = synchronize do
|
|
1325
|
-
# FIXME: In case of reconnect as well?
|
|
1326
|
-
@status == CLOSED or @status == RECONNECTING
|
|
1327
|
-
end
|
|
1328
|
-
if !@io or @io.closed? or should_bail
|
|
1329
|
-
return
|
|
1330
|
-
end
|
|
1364
|
+
return if stopping?
|
|
1331
1365
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
@
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
rescue => e
|
|
1339
|
-
# In case of reading/parser errors, trigger
|
|
1340
|
-
# reconnection logic in case desired.
|
|
1341
|
-
process_op_error(e)
|
|
1366
|
+
should_bail = synchronize do
|
|
1367
|
+
# FIXME: In case of reconnect as well?
|
|
1368
|
+
@status == CLOSED or @status == RECONNECTING
|
|
1369
|
+
end
|
|
1370
|
+
if !@io || @io.closed? || should_bail
|
|
1371
|
+
return
|
|
1342
1372
|
end
|
|
1373
|
+
|
|
1374
|
+
# TODO: Remove timeout and just wait to be ready
|
|
1375
|
+
data = @io.read(NATS::IO::MAX_SOCKET_READ_BYTES)
|
|
1376
|
+
@parser.parse(data) if data
|
|
1377
|
+
rescue Errno::ETIMEDOUT
|
|
1378
|
+
# FIXME: We do not really need a timeout here...
|
|
1379
|
+
retry
|
|
1380
|
+
rescue => e
|
|
1381
|
+
# Asked to stop: the error comes from the socket being shut down.
|
|
1382
|
+
return if stopping?
|
|
1383
|
+
|
|
1384
|
+
# In case of reading/parser errors, trigger
|
|
1385
|
+
# reconnection logic in case desired.
|
|
1386
|
+
process_op_error(e)
|
|
1343
1387
|
end
|
|
1344
1388
|
end
|
|
1345
1389
|
|
|
@@ -1349,9 +1393,10 @@ module NATS
|
|
|
1349
1393
|
loop do
|
|
1350
1394
|
# Blocks waiting for the flusher to be kicked...
|
|
1351
1395
|
@flush_queue.pop
|
|
1396
|
+
return if stopping?
|
|
1352
1397
|
|
|
1353
1398
|
should_bail = synchronize do
|
|
1354
|
-
(@status != CONNECTED && !draining?
|
|
1399
|
+
(@status != CONNECTED && !draining?) || @status == CONNECTING
|
|
1355
1400
|
end
|
|
1356
1401
|
return if should_bail
|
|
1357
1402
|
|
|
@@ -1372,22 +1417,24 @@ module NATS
|
|
|
1372
1417
|
# until reaching the max pending queue size.
|
|
1373
1418
|
cmds = []
|
|
1374
1419
|
cmds << @pending_queue.pop until @pending_queue.empty?
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1420
|
+
if @io
|
|
1421
|
+
begin
|
|
1422
|
+
@io.write(cmds.join) unless cmds.empty?
|
|
1423
|
+
rescue => e
|
|
1424
|
+
synchronize do
|
|
1425
|
+
@last_err = e
|
|
1426
|
+
err_cb_call(self, e, nil) if @err_cb
|
|
1427
|
+
end
|
|
1382
1428
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1429
|
+
process_op_error(e)
|
|
1430
|
+
nil
|
|
1431
|
+
end
|
|
1432
|
+
end
|
|
1386
1433
|
end
|
|
1387
1434
|
|
|
1388
|
-
def ping_interval_loop
|
|
1435
|
+
def ping_interval_loop(stop)
|
|
1389
1436
|
loop do
|
|
1390
|
-
|
|
1437
|
+
return if stop.wait(@options[:ping_interval])
|
|
1391
1438
|
|
|
1392
1439
|
# Skip ping interval until connected
|
|
1393
1440
|
next if !connected?
|
|
@@ -1408,27 +1455,26 @@ module NATS
|
|
|
1408
1455
|
def process_connect_init
|
|
1409
1456
|
# FIXME: Can receive PING as well here in recent versions.
|
|
1410
1457
|
line = @io.read_line(options[:connect_timeout])
|
|
1411
|
-
if !line
|
|
1458
|
+
if !line || line.empty?
|
|
1412
1459
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not received")
|
|
1413
1460
|
end
|
|
1414
1461
|
|
|
1415
|
-
if match = line.match(NATS::Protocol::INFO)
|
|
1462
|
+
if (match = line.match(NATS::Protocol::INFO))
|
|
1416
1463
|
info_json = match.captures.first
|
|
1417
1464
|
process_info(info_json)
|
|
1418
1465
|
else
|
|
1419
1466
|
raise NATS::IO::ConnectError.new("nats: protocol exception, INFO not valid")
|
|
1420
1467
|
end
|
|
1421
1468
|
|
|
1422
|
-
|
|
1423
|
-
when (server_using_secure_connection? and client_using_secure_connection?)
|
|
1469
|
+
if server_using_secure_connection? && client_using_secure_connection?
|
|
1424
1470
|
@io.setup_tls!
|
|
1425
1471
|
# Server > v2.9.19 returns tls_required regardless of no_tls for WebSocket config being used so need to check URI.
|
|
1426
|
-
|
|
1427
|
-
raise NATS::IO::ConnectError.new(
|
|
1472
|
+
elsif server_using_secure_connection? && !client_using_secure_connection? && (@uri.scheme != "ws")
|
|
1473
|
+
raise NATS::IO::ConnectError.new("TLS/SSL required by server")
|
|
1428
1474
|
# Server < v2.9.19 requiring TLS/SSL over websocket but not requiring it over standard protocol
|
|
1429
1475
|
# doesn't send `tls_required` in its INFO so we need to check the URI scheme for WebSocket.
|
|
1430
|
-
|
|
1431
|
-
raise NATS::IO::ConnectError.new(
|
|
1476
|
+
elsif client_using_secure_connection? && !server_using_secure_connection? && (@uri.scheme != "wss")
|
|
1477
|
+
raise NATS::IO::ConnectError.new("TLS/SSL not supported by server")
|
|
1432
1478
|
else
|
|
1433
1479
|
# Otherwise, use a regular connection.
|
|
1434
1480
|
end
|
|
@@ -1449,6 +1495,7 @@ module NATS
|
|
|
1449
1495
|
|
|
1450
1496
|
case next_op
|
|
1451
1497
|
when NATS::Protocol::PONG
|
|
1498
|
+
# do nothing
|
|
1452
1499
|
when NATS::Protocol::ERR
|
|
1453
1500
|
if @server_info[:auth_required]
|
|
1454
1501
|
raise NATS::IO::AuthError.new($1)
|
|
@@ -1460,9 +1507,12 @@ module NATS
|
|
|
1460
1507
|
end
|
|
1461
1508
|
end
|
|
1462
1509
|
|
|
1463
|
-
# Reconnect logic
|
|
1464
|
-
|
|
1465
|
-
|
|
1510
|
+
# Reconnect logic. generation is @close_generation from when the
|
|
1511
|
+
# reconnect started; a close since then cancels the reconnect.
|
|
1512
|
+
def attempt_reconnect(generation)
|
|
1513
|
+
return if closed_since?(generation)
|
|
1514
|
+
|
|
1515
|
+
@disconnect_cb&.call(@last_err)
|
|
1466
1516
|
|
|
1467
1517
|
# Clear sticky error
|
|
1468
1518
|
@last_err = nil
|
|
@@ -1470,10 +1520,12 @@ module NATS
|
|
|
1470
1520
|
# Do reconnect
|
|
1471
1521
|
srv = nil
|
|
1472
1522
|
begin
|
|
1523
|
+
return if closed_since?(generation)
|
|
1524
|
+
|
|
1473
1525
|
srv = select_next_server
|
|
1474
1526
|
|
|
1475
1527
|
# Set hostname to use for TLS hostname verification
|
|
1476
|
-
if client_using_secure_connection?
|
|
1528
|
+
if client_using_secure_connection? && single_url_connect_used?
|
|
1477
1529
|
# Reuse original hostname name in case of using TLS.
|
|
1478
1530
|
@hostname ||= srv[:hostname]
|
|
1479
1531
|
else
|
|
@@ -1511,63 +1563,63 @@ module NATS
|
|
|
1511
1563
|
retry
|
|
1512
1564
|
end
|
|
1513
1565
|
|
|
1514
|
-
#
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1566
|
+
# Under the lock, so that a concurrent close either cancels this or
|
|
1567
|
+
# finds the new connection in place and closes it.
|
|
1568
|
+
synchronize do
|
|
1569
|
+
if @close_generation != generation
|
|
1570
|
+
@io.close
|
|
1571
|
+
@io = nil
|
|
1572
|
+
return
|
|
1573
|
+
end
|
|
1518
1574
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
@
|
|
1522
|
-
|
|
1575
|
+
# Clear pending flush calls and reset state before restarting loops
|
|
1576
|
+
@flush_queue.clear
|
|
1577
|
+
@pings_outstanding = 0
|
|
1578
|
+
@pongs_received = 0
|
|
1523
1579
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
@io.write(cmds.join) unless cmds.empty?
|
|
1529
|
-
@status = CONNECTED
|
|
1530
|
-
@pending_size = 0
|
|
1580
|
+
# Replay all subscriptions
|
|
1581
|
+
@subs.each_pair do |sid, sub|
|
|
1582
|
+
@io.write("SUB #{sub.subject} #{sub.queue} #{sid}#{CR_LF}")
|
|
1583
|
+
end
|
|
1531
1584
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1585
|
+
# Flush anything which was left pending, in case of errors during flush
|
|
1586
|
+
# then we should raise error then retry the reconnect logic
|
|
1587
|
+
cmds = []
|
|
1588
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
|
1589
|
+
@io.write(cmds.join) unless cmds.empty?
|
|
1590
|
+
@status = CONNECTED
|
|
1591
|
+
@pending_size = 0
|
|
1535
1592
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1593
|
+
# Reset parser state here to avoid unknown protocol errors
|
|
1594
|
+
# on reconnect...
|
|
1595
|
+
@parser.reset!
|
|
1539
1596
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1597
|
+
# Now connected to NATS, and we can restart parser loop, flusher
|
|
1598
|
+
# and ping interval
|
|
1599
|
+
start_threads!
|
|
1600
|
+
end
|
|
1601
|
+
|
|
1602
|
+
@reconnect_cb&.call
|
|
1603
|
+
end
|
|
1604
|
+
|
|
1605
|
+
def closed_since?(generation)
|
|
1606
|
+
synchronize { @close_generation != generation }
|
|
1543
1607
|
end
|
|
1544
1608
|
|
|
1545
|
-
def close_connection(conn_status, do_cbs=true)
|
|
1609
|
+
def close_connection(conn_status, do_cbs = true)
|
|
1546
1610
|
synchronize do
|
|
1547
1611
|
@connect_called = false
|
|
1548
1612
|
if @status == CLOSED
|
|
1549
1613
|
@status = conn_status
|
|
1550
1614
|
return
|
|
1551
1615
|
end
|
|
1616
|
+
@close_generation += 1
|
|
1552
1617
|
end
|
|
1553
1618
|
|
|
1554
|
-
|
|
1555
|
-
@flush_queue << :fallout if @flush_queue
|
|
1556
|
-
Thread.pass
|
|
1619
|
+
stop_threads!
|
|
1557
1620
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
if @ping_interval_thread and @ping_interval_thread.alive?
|
|
1561
|
-
@ping_interval_thread.exit
|
|
1562
|
-
end
|
|
1563
|
-
|
|
1564
|
-
if @flusher_thread and @flusher_thread.alive?
|
|
1565
|
-
@flusher_thread.exit
|
|
1566
|
-
end
|
|
1567
|
-
|
|
1568
|
-
if @read_loop_thread and @read_loop_thread.alive?
|
|
1569
|
-
@read_loop_thread.exit
|
|
1570
|
-
end
|
|
1621
|
+
@subscription_executor&.shutdown
|
|
1622
|
+
@subscription_executor&.wait_for_termination(options[:close_timeout])
|
|
1571
1623
|
|
|
1572
1624
|
# TODO: Delete any other state which we are not using here too.
|
|
1573
1625
|
synchronize do
|
|
@@ -1580,24 +1632,26 @@ module NATS
|
|
|
1580
1632
|
|
|
1581
1633
|
# Try to write any pending flushes in case
|
|
1582
1634
|
# we have a connection then close it.
|
|
1583
|
-
should_flush =
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1635
|
+
should_flush = @pending_queue && @io && @io.socket && !@io.closed?
|
|
1636
|
+
if should_flush
|
|
1637
|
+
begin
|
|
1638
|
+
cmds = []
|
|
1639
|
+
cmds << @pending_queue.pop until @pending_queue.empty?
|
|
1640
|
+
|
|
1641
|
+
# FIXME: Fails when empty on TLS connection?
|
|
1642
|
+
@io.write(cmds.join) unless cmds.empty?
|
|
1643
|
+
rescue => e
|
|
1644
|
+
@last_err = e
|
|
1645
|
+
err_cb_call(self, e, nil) if @err_cb
|
|
1646
|
+
end
|
|
1647
|
+
end
|
|
1594
1648
|
|
|
1595
1649
|
# Destroy any remaining subscriptions.
|
|
1596
1650
|
@subs.clear
|
|
1597
1651
|
|
|
1598
1652
|
if do_cbs
|
|
1599
|
-
@disconnect_cb
|
|
1600
|
-
@close_cb
|
|
1653
|
+
@disconnect_cb&.call(@last_err)
|
|
1654
|
+
@close_cb&.call
|
|
1601
1655
|
end
|
|
1602
1656
|
|
|
1603
1657
|
@status = conn_status
|
|
@@ -1611,26 +1665,66 @@ module NATS
|
|
|
1611
1665
|
end
|
|
1612
1666
|
end
|
|
1613
1667
|
|
|
1668
|
+
# Asks the read loop, flusher and ping threads to stop, wakes them from
|
|
1669
|
+
# wherever they block, and waits for them to finish.
|
|
1670
|
+
#
|
|
1671
|
+
# They are never killed: Thread#exit on a thread that is waiting for
|
|
1672
|
+
# the client lock can swallow the lock's wakeup, so that the next
|
|
1673
|
+
# thread waiting for the lock sleeps forever on an unlocked lock (#183).
|
|
1674
|
+
def stop_threads!(timeout = NATS::IO::THREADS_STOP_TIMEOUT)
|
|
1675
|
+
threads = [@read_loop_thread, @flusher_thread, @ping_interval_thread].compact
|
|
1676
|
+
threads.each { |t| t[:nats_stop]&.stop! } # also wakes the ping thread
|
|
1677
|
+
|
|
1678
|
+
begin
|
|
1679
|
+
@flush_queue&.push(:fallout, true)
|
|
1680
|
+
rescue ThreadError
|
|
1681
|
+
# Queue is full, so the flusher is awake already.
|
|
1682
|
+
end
|
|
1683
|
+
# Wake the read loop if it is blocked on the socket. Only while it
|
|
1684
|
+
# runs: shutdown acts on the socket itself, which a forked child
|
|
1685
|
+
# (whose threads are gone) shares with the parent.
|
|
1686
|
+
if @read_loop_thread&.alive? && @read_loop_thread != Thread.current
|
|
1687
|
+
@io&.shutdown_read
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
1691
|
+
threads.each do |t|
|
|
1692
|
+
next if t == Thread.current
|
|
1693
|
+
|
|
1694
|
+
t.join([deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max)
|
|
1695
|
+
end
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
def stopping?
|
|
1699
|
+
Thread.current[:nats_stop]&.stopped?
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
def start_thread(name, stop, &block)
|
|
1703
|
+
thread = Thread.new(&block)
|
|
1704
|
+
thread[:nats_stop] = stop
|
|
1705
|
+
thread.name = name
|
|
1706
|
+
thread.abort_on_exception = true
|
|
1707
|
+
thread
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1614
1710
|
def start_threads!
|
|
1615
1711
|
# Reading loop for gathering data
|
|
1616
|
-
@read_loop_thread =
|
|
1617
|
-
@read_loop_thread.name = "nats:read_loop"
|
|
1618
|
-
@read_loop_thread.abort_on_exception = true
|
|
1712
|
+
@read_loop_thread = start_thread("nats:read_loop", NATS::IO::StopSignal.new) { read_loop }
|
|
1619
1713
|
|
|
1620
1714
|
# Flusher loop for sending commands
|
|
1621
|
-
@flusher_thread =
|
|
1622
|
-
@flusher_thread.name = "nats:flusher_loop"
|
|
1623
|
-
@flusher_thread.abort_on_exception = true
|
|
1715
|
+
@flusher_thread = start_thread("nats:flusher_loop", NATS::IO::StopSignal.new) { flusher_loop }
|
|
1624
1716
|
|
|
1625
1717
|
# Ping interval handling for keeping alive the connection
|
|
1626
|
-
|
|
1627
|
-
@ping_interval_thread
|
|
1628
|
-
@ping_interval_thread.abort_on_exception = true
|
|
1718
|
+
ping_stop = NATS::IO::StopSignal.new
|
|
1719
|
+
@ping_interval_thread = start_thread("nats:ping_loop", ping_stop) { ping_interval_loop(ping_stop) }
|
|
1629
1720
|
|
|
1630
1721
|
# Subscription handling thread pool
|
|
1631
1722
|
@subscription_executor = Concurrent::ThreadPoolExecutor.new(
|
|
1632
|
-
name:
|
|
1723
|
+
name: "nats:subscription", # threads will be given names like nats:subscription-worker-1
|
|
1633
1724
|
max_threads: NATS::IO::DEFAULT_TOTAL_SUB_CONCURRENCY,
|
|
1725
|
+
# JRuby has a bug on certain Java version of not creating new threads:
|
|
1726
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/864
|
|
1727
|
+
min_threads: defined?(JRUBY_VERSION) ? 2 : 0
|
|
1634
1728
|
)
|
|
1635
1729
|
end
|
|
1636
1730
|
|
|
@@ -1638,7 +1732,7 @@ module NATS
|
|
|
1638
1732
|
# for the new style request response.
|
|
1639
1733
|
def start_resp_mux_sub!
|
|
1640
1734
|
@resp_sub_prefix = new_inbox
|
|
1641
|
-
@resp_map = Hash.new { |h,k| h[k] = { }
|
|
1735
|
+
@resp_map = Hash.new { |h, k| h[k] = {} }
|
|
1642
1736
|
|
|
1643
1737
|
@resp_sub = Subscription.new
|
|
1644
1738
|
@resp_sub.subject = "#{@resp_sub_prefix}.*"
|
|
@@ -1652,7 +1746,7 @@ module NATS
|
|
|
1652
1746
|
@resp_sub.callback = proc do |msg|
|
|
1653
1747
|
# Pick the token and signal the request under the mutex
|
|
1654
1748
|
# from the subscription itself.
|
|
1655
|
-
token = msg.subject.split(
|
|
1749
|
+
token = msg.subject.split(".").last
|
|
1656
1750
|
future = nil
|
|
1657
1751
|
synchronize do
|
|
1658
1752
|
future = @resp_map[token][:future]
|
|
@@ -1684,7 +1778,7 @@ module NATS
|
|
|
1684
1778
|
return false if server[:error_received]
|
|
1685
1779
|
|
|
1686
1780
|
# We will retry a number of times to reconnect to a server.
|
|
1687
|
-
|
|
1781
|
+
server[:reconnect_attempts] <= @options[:max_reconnect_attempts]
|
|
1688
1782
|
end
|
|
1689
1783
|
|
|
1690
1784
|
def should_delay_connect?(server)
|
|
@@ -1701,35 +1795,34 @@ module NATS
|
|
|
1701
1795
|
|
|
1702
1796
|
def create_socket
|
|
1703
1797
|
socket_class = case @uri.scheme
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1798
|
+
when "nats", "tls"
|
|
1799
|
+
NATS::IO::Socket
|
|
1800
|
+
when "ws", "wss"
|
|
1801
|
+
require_relative "websocket"
|
|
1802
|
+
NATS::IO::WebSocket
|
|
1803
|
+
else
|
|
1804
|
+
raise NotImplementedError, "#{@uri.scheme} protocol is not supported, check NATS cluster URL spelling"
|
|
1805
|
+
end
|
|
1712
1806
|
|
|
1713
1807
|
socket_class.new(
|
|
1714
1808
|
uri: @uri,
|
|
1715
|
-
tls: {
|
|
1716
|
-
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
|
1809
|
+
tls: {context: tls_context, hostname: @hostname},
|
|
1810
|
+
connect_timeout: NATS::IO::DEFAULT_CONNECT_TIMEOUT
|
|
1717
1811
|
)
|
|
1718
1812
|
end
|
|
1719
1813
|
|
|
1720
1814
|
def setup_nkeys_connect
|
|
1721
1815
|
begin
|
|
1722
|
-
require
|
|
1723
|
-
require
|
|
1816
|
+
require "nkeys"
|
|
1817
|
+
require "base64"
|
|
1724
1818
|
rescue LoadError
|
|
1725
1819
|
raise(Error, "nkeys is not installed")
|
|
1726
1820
|
end
|
|
1727
1821
|
|
|
1728
|
-
|
|
1729
|
-
when @nkeys_seed
|
|
1822
|
+
if @nkeys_seed
|
|
1730
1823
|
@user_nkey_cb = nkey_cb_for_nkey_file(@nkeys_seed)
|
|
1731
1824
|
@signature_cb = signature_cb_for_nkey_file(@nkeys_seed)
|
|
1732
|
-
|
|
1825
|
+
elsif @user_credentials
|
|
1733
1826
|
# When the credentials are within a single decorated file.
|
|
1734
1827
|
@user_jwt_cb = jwt_cb_for_creds_file(@user_credentials)
|
|
1735
1828
|
@signature_cb = signature_cb_for_creds_file(@user_credentials)
|
|
@@ -1739,18 +1832,18 @@ module NATS
|
|
|
1739
1832
|
def signature_cb_for_nkey_file(nkey)
|
|
1740
1833
|
proc { |nonce|
|
|
1741
1834
|
seed = File.read(nkey).chomp
|
|
1742
|
-
kp = NKEYS
|
|
1835
|
+
kp = NKEYS.from_seed(seed)
|
|
1743
1836
|
raw_signed = kp.sign(nonce)
|
|
1744
1837
|
kp.wipe!
|
|
1745
1838
|
encoded = Base64.urlsafe_encode64(raw_signed)
|
|
1746
|
-
encoded.gsub(
|
|
1839
|
+
encoded.gsub("=", "")
|
|
1747
1840
|
}
|
|
1748
1841
|
end
|
|
1749
1842
|
|
|
1750
1843
|
def nkey_cb_for_nkey_file(nkey)
|
|
1751
1844
|
proc {
|
|
1752
1845
|
seed = File.read(nkey).chomp
|
|
1753
|
-
kp = NKEYS
|
|
1846
|
+
kp = NKEYS.from_seed(seed)
|
|
1754
1847
|
|
|
1755
1848
|
# Take a copy since original will be gone with the wipe.
|
|
1756
1849
|
pub_key = kp.public_key.dup
|
|
@@ -1762,21 +1855,20 @@ module NATS
|
|
|
1762
1855
|
|
|
1763
1856
|
def jwt_cb_for_creds_file(creds)
|
|
1764
1857
|
proc {
|
|
1765
|
-
jwt_start = "BEGIN NATS USER JWT"
|
|
1858
|
+
jwt_start = "BEGIN NATS USER JWT"
|
|
1766
1859
|
found = false
|
|
1767
1860
|
jwt = nil
|
|
1768
1861
|
|
|
1769
1862
|
File.readlines(creds).each do |line|
|
|
1770
|
-
|
|
1771
|
-
when found
|
|
1863
|
+
if found
|
|
1772
1864
|
jwt = line.chomp
|
|
1773
1865
|
break
|
|
1774
|
-
|
|
1866
|
+
elsif line.include?(jwt_start)
|
|
1775
1867
|
found = true
|
|
1776
1868
|
end
|
|
1777
1869
|
end
|
|
1778
1870
|
|
|
1779
|
-
raise(Error, "No JWT found in #{creds}") if
|
|
1871
|
+
raise(Error, "No JWT found in #{creds}") if !found
|
|
1780
1872
|
|
|
1781
1873
|
jwt
|
|
1782
1874
|
}
|
|
@@ -1784,23 +1876,22 @@ module NATS
|
|
|
1784
1876
|
|
|
1785
1877
|
def signature_cb_for_creds_file(creds)
|
|
1786
1878
|
proc { |nonce|
|
|
1787
|
-
seed_start = "BEGIN USER NKEY SEED"
|
|
1879
|
+
seed_start = "BEGIN USER NKEY SEED"
|
|
1788
1880
|
found = false
|
|
1789
1881
|
seed = nil
|
|
1790
1882
|
|
|
1791
1883
|
File.readlines(creds).each do |line|
|
|
1792
|
-
|
|
1793
|
-
when found
|
|
1884
|
+
if found
|
|
1794
1885
|
seed = line.chomp
|
|
1795
1886
|
break
|
|
1796
|
-
|
|
1887
|
+
elsif line.include?(seed_start)
|
|
1797
1888
|
found = true
|
|
1798
1889
|
end
|
|
1799
1890
|
end
|
|
1800
1891
|
|
|
1801
|
-
raise(Error, "No nkey user seed found in #{creds}") if
|
|
1892
|
+
raise(Error, "No nkey user seed found in #{creds}") if !found
|
|
1802
1893
|
|
|
1803
|
-
kp = NKEYS
|
|
1894
|
+
kp = NKEYS.from_seed(seed)
|
|
1804
1895
|
raw_signed = kp.sign(nonce)
|
|
1805
1896
|
|
|
1806
1897
|
# seed is a reference so also cleared when doing wipe,
|
|
@@ -1809,14 +1900,12 @@ module NATS
|
|
|
1809
1900
|
encoded = Base64.urlsafe_encode64(raw_signed)
|
|
1810
1901
|
|
|
1811
1902
|
# Remove padding
|
|
1812
|
-
encoded.gsub(
|
|
1903
|
+
encoded.gsub("=", "")
|
|
1813
1904
|
}
|
|
1814
1905
|
end
|
|
1815
1906
|
|
|
1816
1907
|
def process_uri(uris)
|
|
1817
|
-
uris.split(
|
|
1818
|
-
opts = {}
|
|
1819
|
-
|
|
1908
|
+
uris.gsub(/\s+/, "").split(",").map do |uri|
|
|
1820
1909
|
# Scheme
|
|
1821
1910
|
uri = "nats://#{uri}" if !uri.include?("://")
|
|
1822
1911
|
|
|
@@ -1858,9 +1947,48 @@ module NATS
|
|
|
1858
1947
|
DEFAULT_CONNECT_TIMEOUT = 2
|
|
1859
1948
|
DEFAULT_READ_WRITE_TIMEOUT = 2
|
|
1860
1949
|
DEFAULT_DRAIN_TIMEOUT = 30
|
|
1950
|
+
DEFAULT_CLOSE_TIMEOUT = 30
|
|
1951
|
+
|
|
1952
|
+
# How long to wait for the read loop, flusher and ping threads to
|
|
1953
|
+
# stop on close or reconnect.
|
|
1954
|
+
THREADS_STOP_TIMEOUT = 5
|
|
1955
|
+
|
|
1956
|
+
# Asks a background thread to stop, and lets it sleep until then.
|
|
1957
|
+
class StopSignal
|
|
1958
|
+
def initialize
|
|
1959
|
+
@mutex = Mutex.new
|
|
1960
|
+
@cond = ConditionVariable.new
|
|
1961
|
+
@stopped = false
|
|
1962
|
+
end
|
|
1963
|
+
|
|
1964
|
+
def stop!
|
|
1965
|
+
@mutex.synchronize do
|
|
1966
|
+
@stopped = true
|
|
1967
|
+
@cond.broadcast
|
|
1968
|
+
end
|
|
1969
|
+
end
|
|
1970
|
+
|
|
1971
|
+
def stopped?
|
|
1972
|
+
@stopped
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1975
|
+
# Sleeps up to timeout seconds; returns whether stop! was called.
|
|
1976
|
+
def wait(timeout)
|
|
1977
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
1978
|
+
@mutex.synchronize do
|
|
1979
|
+
until @stopped
|
|
1980
|
+
left = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1981
|
+
break if left <= 0
|
|
1982
|
+
|
|
1983
|
+
@cond.wait(@mutex, left)
|
|
1984
|
+
end
|
|
1985
|
+
@stopped
|
|
1986
|
+
end
|
|
1987
|
+
end
|
|
1988
|
+
end
|
|
1861
1989
|
|
|
1862
1990
|
# Default Pending Limits
|
|
1863
|
-
DEFAULT_SUB_PENDING_MSGS_LIMIT
|
|
1991
|
+
DEFAULT_SUB_PENDING_MSGS_LIMIT = 65536
|
|
1864
1992
|
DEFAULT_SUB_PENDING_BYTES_LIMIT = 65536 * 1024
|
|
1865
1993
|
|
|
1866
1994
|
DEFAULT_TOTAL_SUB_CONCURRENCY = 24
|
|
@@ -1870,7 +1998,7 @@ module NATS
|
|
|
1870
1998
|
class Socket
|
|
1871
1999
|
attr_accessor :socket
|
|
1872
2000
|
|
|
1873
|
-
def initialize(options={})
|
|
2001
|
+
def initialize(options = {})
|
|
1874
2002
|
@uri = options[:uri]
|
|
1875
2003
|
@connect_timeout = options[:connect_timeout]
|
|
1876
2004
|
@write_timeout = options[:write_timeout]
|
|
@@ -1882,13 +2010,11 @@ module NATS
|
|
|
1882
2010
|
def connect
|
|
1883
2011
|
addrinfo = ::Socket.getaddrinfo(@uri.hostname, nil, ::Socket::AF_UNSPEC, ::Socket::SOCK_STREAM)
|
|
1884
2012
|
addrinfo.each_with_index do |ai, i|
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
raise e if addrinfo.length == i+1
|
|
1891
|
-
end
|
|
2013
|
+
@socket = connect_addrinfo(ai, @uri.port, @connect_timeout)
|
|
2014
|
+
break
|
|
2015
|
+
rescue SystemCallError => e
|
|
2016
|
+
# Give up if no more available
|
|
2017
|
+
raise e if addrinfo.length == i + 1
|
|
1892
2018
|
end
|
|
1893
2019
|
|
|
1894
2020
|
# Set TCP no delay by default
|
|
@@ -1911,7 +2037,7 @@ module NATS
|
|
|
1911
2037
|
@socket = tls_socket
|
|
1912
2038
|
end
|
|
1913
2039
|
|
|
1914
|
-
def read_line(deadline=nil)
|
|
2040
|
+
def read_line(deadline = nil)
|
|
1915
2041
|
# FIXME: Should accumulate and read in a non blocking way instead
|
|
1916
2042
|
unless ::IO.select([@socket], nil, nil, deadline)
|
|
1917
2043
|
raise NATS::IO::SocketTimeoutError
|
|
@@ -1919,10 +2045,9 @@ module NATS
|
|
|
1919
2045
|
@socket.gets
|
|
1920
2046
|
end
|
|
1921
2047
|
|
|
1922
|
-
def read(max_bytes, deadline=nil)
|
|
1923
|
-
|
|
2048
|
+
def read(max_bytes, deadline = nil)
|
|
1924
2049
|
begin
|
|
1925
|
-
|
|
2050
|
+
@socket.read_nonblock(max_bytes)
|
|
1926
2051
|
rescue ::IO::WaitReadable
|
|
1927
2052
|
if ::IO.select([@socket], nil, nil, deadline)
|
|
1928
2053
|
retry
|
|
@@ -1937,7 +2062,7 @@ module NATS
|
|
|
1937
2062
|
end
|
|
1938
2063
|
end
|
|
1939
2064
|
rescue EOFError => e
|
|
1940
|
-
if RUBY_ENGINE ==
|
|
2065
|
+
if (RUBY_ENGINE == "jruby") && (e.message == "No message available")
|
|
1941
2066
|
# FIXME: <EOFError: No message available> can happen in jruby
|
|
1942
2067
|
# even though seems it is temporary and eventually possible
|
|
1943
2068
|
# to read from socket.
|
|
@@ -1946,32 +2071,29 @@ module NATS
|
|
|
1946
2071
|
raise Errno::ECONNRESET
|
|
1947
2072
|
end
|
|
1948
2073
|
|
|
1949
|
-
def write(data, deadline=nil)
|
|
2074
|
+
def write(data, deadline = nil)
|
|
1950
2075
|
length = data.bytesize
|
|
1951
2076
|
total_written = 0
|
|
1952
2077
|
|
|
1953
2078
|
loop do
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
raise NATS::IO::SocketTimeoutError
|
|
1971
|
-
end
|
|
2079
|
+
written = @socket.write_nonblock(data)
|
|
2080
|
+
|
|
2081
|
+
total_written += written
|
|
2082
|
+
break total_written if total_written >= length
|
|
2083
|
+
data = data.byteslice(written..-1)
|
|
2084
|
+
rescue ::IO::WaitWritable
|
|
2085
|
+
if ::IO.select(nil, [@socket], nil, deadline)
|
|
2086
|
+
retry
|
|
2087
|
+
else
|
|
2088
|
+
raise NATS::IO::SocketTimeoutError
|
|
2089
|
+
end
|
|
2090
|
+
rescue ::IO::WaitReadable
|
|
2091
|
+
if ::IO.select([@socket], nil, nil, deadline)
|
|
2092
|
+
retry
|
|
2093
|
+
else
|
|
2094
|
+
raise NATS::IO::SocketTimeoutError
|
|
1972
2095
|
end
|
|
1973
2096
|
end
|
|
1974
|
-
|
|
1975
2097
|
rescue EOFError
|
|
1976
2098
|
raise Errno::ECONNRESET
|
|
1977
2099
|
end
|
|
@@ -1980,6 +2102,15 @@ module NATS
|
|
|
1980
2102
|
@socket.close
|
|
1981
2103
|
end
|
|
1982
2104
|
|
|
2105
|
+
# Makes a read blocked on the socket in another thread return,
|
|
2106
|
+
# while writes keep working.
|
|
2107
|
+
def shutdown_read
|
|
2108
|
+
io = @socket.respond_to?(:to_io) ? @socket.to_io : @socket
|
|
2109
|
+
io.shutdown(::Socket::SHUT_RD)
|
|
2110
|
+
rescue IOError, SystemCallError
|
|
2111
|
+
# Already closed or not connected.
|
|
2112
|
+
end
|
|
2113
|
+
|
|
1983
2114
|
def closed?
|
|
1984
2115
|
@socket.closed?
|
|
1985
2116
|
end
|
|
@@ -2006,6 +2137,12 @@ module NATS
|
|
|
2006
2137
|
end
|
|
2007
2138
|
|
|
2008
2139
|
sock
|
|
2140
|
+
rescue IOError => e
|
|
2141
|
+
# JRuby raises a plain IOError for a refused connection.
|
|
2142
|
+
if (RUBY_ENGINE == "jruby") && e.message.include?("Connection refused")
|
|
2143
|
+
raise Errno::ECONNREFUSED
|
|
2144
|
+
end
|
|
2145
|
+
raise
|
|
2009
2146
|
end
|
|
2010
2147
|
end
|
|
2011
2148
|
end
|
|
@@ -2016,14 +2153,13 @@ module NATS
|
|
|
2016
2153
|
# Implementation of MonotonicTime adapted from
|
|
2017
2154
|
# https://github.com/ruby-concurrency/concurrent-ruby/
|
|
2018
2155
|
class << self
|
|
2019
|
-
|
|
2020
|
-
when defined?(Process::CLOCK_MONOTONIC)
|
|
2156
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
|
2021
2157
|
def now
|
|
2022
2158
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
2023
2159
|
end
|
|
2024
|
-
|
|
2160
|
+
elsif RUBY_ENGINE == "jruby"
|
|
2025
2161
|
def now
|
|
2026
|
-
java.lang.System.nanoTime
|
|
2162
|
+
java.lang.System.nanoTime / 1_000_000_000.0
|
|
2027
2163
|
end
|
|
2028
2164
|
else
|
|
2029
2165
|
def now
|