nats-pure 0.2.0 → 0.2.2
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/lib/nats/io/client.rb +135 -104
- data/lib/nats/io/parser.rb +4 -1
- data/lib/nats/io/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7cfcdf1243c5e666ab143dfc2455382b6e005d4
|
4
|
+
data.tar.gz: 5b14efd741a22149d8b2d2f32cc2b9f1ae3d26d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30bf47ea7e3bb6baf8e52adc60ca7d8d2ef5ee532dd3effe9ae0994d017ee43bce26ff4035901b329fd2865cf9d6f35fb6e5c92515e7afb536f5d471eb91640f
|
7
|
+
data.tar.gz: 19a9a15800568b21b583a690c0a3d56dd40841c15369971afd83d38e54b5f1e2752727eb6027b4a9ef42c84317af455c95530216cddd1221dc1c9e38fa226e1c
|
data/lib/nats/io/client.rb
CHANGED
@@ -72,6 +72,9 @@ module NATS
|
|
72
72
|
# When we cannot connect since there are no servers available.
|
73
73
|
class NoServersError < ConnectError; end
|
74
74
|
|
75
|
+
# When the connection exhausts max number of pending pings replies.
|
76
|
+
class StaleConnectionError < Error; end
|
77
|
+
|
75
78
|
# When we do not get a result within a specified time.
|
76
79
|
class Timeout < Error; end
|
77
80
|
|
@@ -137,8 +140,8 @@ module NATS
|
|
137
140
|
# Sticky error
|
138
141
|
@last_err = nil
|
139
142
|
|
140
|
-
# Async callbacks
|
141
|
-
@err_cb = proc {
|
143
|
+
# Async callbacks, no ops by default.
|
144
|
+
@err_cb = proc { }
|
142
145
|
@close_cb = proc { }
|
143
146
|
@disconnect_cb = proc { }
|
144
147
|
@reconnect_cb = proc { }
|
@@ -178,8 +181,9 @@ module NATS
|
|
178
181
|
# Check for TLS usage
|
179
182
|
@tls = @options[:tls]
|
180
183
|
|
184
|
+
srv = nil
|
181
185
|
begin
|
182
|
-
|
186
|
+
srv = select_next_server
|
183
187
|
|
184
188
|
# Create TCP socket connection to NATS
|
185
189
|
@io = create_socket
|
@@ -187,7 +191,7 @@ module NATS
|
|
187
191
|
|
188
192
|
# Capture state that we have had a TCP connection established against
|
189
193
|
# this server and could potentially be used for reconnecting.
|
190
|
-
|
194
|
+
srv[:was_connected] = true
|
191
195
|
|
192
196
|
# Connection established and now in process of sending CONNECT to NATS
|
193
197
|
@status = CONNECTING
|
@@ -196,13 +200,21 @@ module NATS
|
|
196
200
|
process_connect_init
|
197
201
|
|
198
202
|
# Reset reconnection attempts if connection is valid
|
199
|
-
|
203
|
+
srv[:reconnect_attempts] = 0
|
204
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
205
|
+
|
206
|
+
# Add back to rotation since successfully connected
|
207
|
+
server_pool << srv
|
200
208
|
rescue NoServersError => e
|
201
209
|
@disconnect_cb.call(e) if @disconnect_cb
|
202
210
|
raise @last_err || e
|
203
211
|
rescue => e
|
204
212
|
# Capture sticky error
|
205
|
-
synchronize
|
213
|
+
synchronize do
|
214
|
+
@last_err = e
|
215
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
216
|
+
server_pool << srv if can_reuse_server?(srv)
|
217
|
+
end
|
206
218
|
|
207
219
|
@err_cb.call(e) if @err_cb
|
208
220
|
|
@@ -385,26 +397,23 @@ module NATS
|
|
385
397
|
|
386
398
|
# Handles protocol errors being sent by the server.
|
387
399
|
def process_err(err)
|
388
|
-
# FIXME: In case of a stale connection, then handle as process_op_error
|
389
|
-
|
390
400
|
# In case of permissions violation then dispatch the error callback
|
391
401
|
# while holding the lock.
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
402
|
+
e = synchronize do
|
403
|
+
current = server_pool.first
|
404
|
+
case
|
405
|
+
when err =~ /'Stale Connection'/
|
406
|
+
@last_err = NATS::IO::StaleConnectionError.new(err)
|
407
|
+
when current && current[:auth_required]
|
408
|
+
# We cannot recover from auth errors so mark it to avoid
|
409
|
+
# retrying to unecessarily next time.
|
410
|
+
current[:error_received] = true
|
411
|
+
@last_err = NATS::IO::AuthError.new(err)
|
412
|
+
else
|
413
|
+
@last_err = NATS::IO::ServerError.new(err)
|
414
|
+
end
|
404
415
|
end
|
405
|
-
|
406
|
-
# Process disconnect under a different thread as reading loop
|
407
|
-
Thread.new { close }
|
416
|
+
process_op_error(e)
|
408
417
|
end
|
409
418
|
|
410
419
|
def process_msg(subject, sid, reply, data)
|
@@ -456,6 +465,58 @@ module NATS
|
|
456
465
|
end
|
457
466
|
end
|
458
467
|
|
468
|
+
def process_info(line)
|
469
|
+
parsed_info = JSON.parse(line)
|
470
|
+
|
471
|
+
# INFO can be received asynchronously too,
|
472
|
+
# so has to be done under the lock.
|
473
|
+
synchronize do
|
474
|
+
# Symbolize keys from parsed info line
|
475
|
+
@server_info = parsed_info.reduce({}) do |info, (k,v)|
|
476
|
+
info[k.to_sym] = v
|
477
|
+
|
478
|
+
info
|
479
|
+
end
|
480
|
+
|
481
|
+
# Detect any announced server that we might not be aware of...
|
482
|
+
connect_urls = @server_info[:connect_urls]
|
483
|
+
if connect_urls
|
484
|
+
srvs = []
|
485
|
+
connect_urls.each do |url|
|
486
|
+
u = URI.parse("nats://#{url}")
|
487
|
+
|
488
|
+
# Skip in case it is the current server which we already know
|
489
|
+
next if @uri.host == u.host && @uri.port == u.port
|
490
|
+
|
491
|
+
present = server_pool.detect do |srv|
|
492
|
+
srv[:uri].host == u.host && srv[:uri].port == u.port
|
493
|
+
end
|
494
|
+
|
495
|
+
if not present
|
496
|
+
# Let explicit user and pass options set the credentials.
|
497
|
+
u.user = options[:user] if options[:user]
|
498
|
+
u.password = options[:pass] if options[:pass]
|
499
|
+
|
500
|
+
# Use creds from the current server if not set explicitly.
|
501
|
+
if @uri
|
502
|
+
u.user ||= @uri.user if @uri.user
|
503
|
+
u.password ||= @uri.password if @uri.password
|
504
|
+
end
|
505
|
+
|
506
|
+
srv = { :uri => u, :reconnect_attempts => 0, :discovered => true }
|
507
|
+
srvs << srv
|
508
|
+
end
|
509
|
+
end
|
510
|
+
srvs.shuffle! unless @options[:dont_randomize_servers]
|
511
|
+
|
512
|
+
# Include in server pool but keep current one as the first one.
|
513
|
+
server_pool.push(*srvs)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
@server_info
|
518
|
+
end
|
519
|
+
|
459
520
|
# Close connection to NATS, flushing in case connection is alive
|
460
521
|
# and there are any pending messages, should not be used while
|
461
522
|
# holding the lock.
|
@@ -521,13 +582,6 @@ module NATS
|
|
521
582
|
srv[:reconnect_attempts] ||= 0
|
522
583
|
srv[:reconnect_attempts] += 1
|
523
584
|
|
524
|
-
# In case there was an error from the server we will
|
525
|
-
# take it out from rotation unless we specify infinite
|
526
|
-
# reconnects via setting :max_reconnect_attempts to -1
|
527
|
-
if options[:max_reconnect_attempts] < 0 || can_reuse_server?(srv)
|
528
|
-
server_pool << srv
|
529
|
-
end
|
530
|
-
|
531
585
|
# Back off in case we are reconnecting to it and have been connected
|
532
586
|
sleep @options[:reconnect_time_wait] if should_delay_connect?(srv)
|
533
587
|
|
@@ -539,53 +593,6 @@ module NATS
|
|
539
593
|
srv
|
540
594
|
end
|
541
595
|
|
542
|
-
def process_info(line)
|
543
|
-
parsed_info = JSON.parse(line)
|
544
|
-
|
545
|
-
# INFO can be received asynchronously too,
|
546
|
-
# so has to be done under the lock.
|
547
|
-
synchronize do
|
548
|
-
# Symbolize keys from parsed info line
|
549
|
-
@server_info = parsed_info.reduce({}) do |info, (k,v)|
|
550
|
-
info[k.to_sym] = v
|
551
|
-
|
552
|
-
info
|
553
|
-
end
|
554
|
-
|
555
|
-
# Detect any announced server that we might not be aware of...
|
556
|
-
connect_urls = @server_info[:connect_urls]
|
557
|
-
if connect_urls
|
558
|
-
srvs = []
|
559
|
-
connect_urls.each do |url|
|
560
|
-
u = URI.parse("nats://#{url}")
|
561
|
-
present = server_pool.detect do |srv|
|
562
|
-
srv[:uri].host == u.host && srv[:uri].port == u.port
|
563
|
-
end
|
564
|
-
|
565
|
-
if not present
|
566
|
-
# Let explicit user and pass options set the credentials.
|
567
|
-
u.user = options[:user] if options[:user]
|
568
|
-
u.password = options[:pass] if options[:pass]
|
569
|
-
|
570
|
-
# Use creds from the current server if not set explicitly.
|
571
|
-
if @uri
|
572
|
-
u.user ||= @uri.user if @uri.user
|
573
|
-
u.password ||= @uri.password if @uri.password
|
574
|
-
end
|
575
|
-
|
576
|
-
srvs << { :uri => u, :reconnect_attempts => 0, :discovered => true }
|
577
|
-
end
|
578
|
-
end
|
579
|
-
srvs.shuffle! unless @options[:dont_randomize_servers]
|
580
|
-
|
581
|
-
# Include in server pool but keep current one as the first one.
|
582
|
-
server_pool.push(*srvs)
|
583
|
-
end
|
584
|
-
end
|
585
|
-
|
586
|
-
@server_info
|
587
|
-
end
|
588
|
-
|
589
596
|
def server_using_secure_connection?
|
590
597
|
@server_info[:ssl_required] || @server_info[:tls_required]
|
591
598
|
end
|
@@ -616,8 +623,12 @@ module NATS
|
|
616
623
|
cs[:name] = @options[:name] if @options[:name]
|
617
624
|
|
618
625
|
if auth_connection?
|
619
|
-
|
620
|
-
|
626
|
+
if @uri.password
|
627
|
+
cs[:user] = @uri.user
|
628
|
+
cs[:pass] = @uri.password
|
629
|
+
else
|
630
|
+
cs[:auth_token] = @uri.user
|
631
|
+
end
|
621
632
|
end
|
622
633
|
|
623
634
|
"CONNECT #{cs.to_json}#{CR_LF}"
|
@@ -641,6 +652,7 @@ module NATS
|
|
641
652
|
|
642
653
|
synchronize do
|
643
654
|
@last_err = e
|
655
|
+
@err_cb.call(e) if @err_cb
|
644
656
|
|
645
657
|
# If we were connected and configured to reconnect,
|
646
658
|
# then trigger disconnect and start reconnection logic
|
@@ -651,8 +663,8 @@ module NATS
|
|
651
663
|
|
652
664
|
# TODO: Reconnecting pending buffer?
|
653
665
|
|
654
|
-
#
|
655
|
-
# which got the error.
|
666
|
+
# Do reconnect under a different thread than the one
|
667
|
+
# in which we got the error.
|
656
668
|
Thread.new do
|
657
669
|
begin
|
658
670
|
# Abort currently running reads in case they're around
|
@@ -734,7 +746,6 @@ module NATS
|
|
734
746
|
@err_cb.call(e) if @err_cb
|
735
747
|
end
|
736
748
|
|
737
|
-
# TODO: Thread.exit?
|
738
749
|
process_op_error(e)
|
739
750
|
return
|
740
751
|
end if @io
|
@@ -748,12 +759,16 @@ module NATS
|
|
748
759
|
def ping_interval_loop
|
749
760
|
loop do
|
750
761
|
sleep @options[:ping_interval]
|
751
|
-
|
752
|
-
|
753
|
-
|
762
|
+
|
763
|
+
# Skip ping interval until connected
|
764
|
+
next if !connected?
|
765
|
+
|
766
|
+
if @pings_outstanding >= @options[:max_outstanding_pings]
|
767
|
+
process_op_error(StaleConnectionError.new("nats: stale connection"))
|
768
|
+
return
|
754
769
|
end
|
755
|
-
@pings_outstanding += 1
|
756
770
|
|
771
|
+
@pings_outstanding += 1
|
757
772
|
send_command(PING_REQUEST)
|
758
773
|
@flush_queue << :ping
|
759
774
|
end
|
@@ -763,6 +778,9 @@ module NATS
|
|
763
778
|
|
764
779
|
def process_connect_init
|
765
780
|
line = @io.read_line(options[:connect_timeout])
|
781
|
+
if !line or line.empty?
|
782
|
+
raise ConnectError.new("nats: protocol exception, INFO not received")
|
783
|
+
end
|
766
784
|
_, info_json = line.split(' ')
|
767
785
|
process_info(info_json)
|
768
786
|
|
@@ -791,11 +809,6 @@ module NATS
|
|
791
809
|
# Otherwise, use a regular connection.
|
792
810
|
end
|
793
811
|
|
794
|
-
if @server_info[:auth_required]
|
795
|
-
current = server_pool.first
|
796
|
-
current[:auth_required] = true
|
797
|
-
end
|
798
|
-
|
799
812
|
# Send connect and process synchronously. If using TLS,
|
800
813
|
# it should have handled upgrading at this point.
|
801
814
|
@io.write(connect_command)
|
@@ -831,8 +844,9 @@ module NATS
|
|
831
844
|
@last_err = nil
|
832
845
|
|
833
846
|
# Do reconnect
|
847
|
+
srv = nil
|
834
848
|
begin
|
835
|
-
|
849
|
+
srv = select_next_server
|
836
850
|
|
837
851
|
# Establish TCP connection with new server
|
838
852
|
@io = create_socket
|
@@ -843,10 +857,19 @@ module NATS
|
|
843
857
|
process_connect_init
|
844
858
|
|
845
859
|
# Reset reconnection attempts if connection is valid
|
846
|
-
|
860
|
+
srv[:reconnect_attempts] = 0
|
861
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
862
|
+
|
863
|
+
# Add back to rotation since successfully connected
|
864
|
+
server_pool << srv
|
847
865
|
rescue NoServersError => e
|
848
866
|
raise e
|
849
867
|
rescue => e
|
868
|
+
# In case there was an error from the server check
|
869
|
+
# to see whether need to take it out from rotation
|
870
|
+
srv[:auth_required] ||= true if @server_info[:auth_required]
|
871
|
+
server_pool << srv if can_reuse_server?(srv)
|
872
|
+
|
850
873
|
@last_err = e
|
851
874
|
|
852
875
|
# Trigger async error handler
|
@@ -874,6 +897,10 @@ module NATS
|
|
874
897
|
@status = CONNECTED
|
875
898
|
@pending_size = 0
|
876
899
|
|
900
|
+
# Reset parser state here to avoid unknown protocol errors
|
901
|
+
# on reconnect...
|
902
|
+
@parser.reset!
|
903
|
+
|
877
904
|
# Now connected to NATS, and we can restart parser loop, flusher
|
878
905
|
# and ping interval
|
879
906
|
start_threads!
|
@@ -964,9 +991,17 @@ module NATS
|
|
964
991
|
end
|
965
992
|
|
966
993
|
def can_reuse_server?(server)
|
967
|
-
|
968
|
-
|
969
|
-
|
994
|
+
return false if server.nil?
|
995
|
+
|
996
|
+
# We can always reuse servers with infinite reconnects settings
|
997
|
+
return true if @options[:max_reconnect_attempts] < 0
|
998
|
+
|
999
|
+
# In case of hard errors like authorization errors, drop the server
|
1000
|
+
# already since won't be able to connect.
|
1001
|
+
return false if server[:error_received]
|
1002
|
+
|
1003
|
+
# We will retry a number of times to reconnect to a server.
|
1004
|
+
return server[:reconnect_attempts] <= @options[:max_reconnect_attempts]
|
970
1005
|
end
|
971
1006
|
|
972
1007
|
def should_delay_connect?(server)
|
@@ -993,10 +1028,6 @@ module NATS
|
|
993
1028
|
class Socket
|
994
1029
|
attr_accessor :socket
|
995
1030
|
|
996
|
-
# Exceptions raised during non-blocking I/O ops that require retrying the op
|
997
|
-
NBIO_READ_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN, ::IO::WaitReadable]
|
998
|
-
NBIO_WRITE_EXCEPTIONS = [Errno::EWOULDBLOCK, Errno::EAGAIN, ::IO::WaitWritable]
|
999
|
-
|
1000
1031
|
def initialize(options={})
|
1001
1032
|
@uri = options[:uri]
|
1002
1033
|
@connect_timeout = options[:connect_timeout]
|
@@ -1033,13 +1064,13 @@ module NATS
|
|
1033
1064
|
|
1034
1065
|
begin
|
1035
1066
|
return @socket.read_nonblock(max_bytes)
|
1036
|
-
rescue
|
1067
|
+
rescue ::IO::WaitReadable
|
1037
1068
|
if ::IO.select([@socket], nil, nil, deadline)
|
1038
1069
|
retry
|
1039
1070
|
else
|
1040
1071
|
raise SocketTimeoutError
|
1041
1072
|
end
|
1042
|
-
rescue
|
1073
|
+
rescue ::IO::WaitWritable
|
1043
1074
|
if ::IO.select(nil, [@socket], nil, deadline)
|
1044
1075
|
retry
|
1045
1076
|
else
|
@@ -1067,13 +1098,13 @@ module NATS
|
|
1067
1098
|
total_written += written
|
1068
1099
|
break total_written if total_written >= length
|
1069
1100
|
data = data.byteslice(written..-1)
|
1070
|
-
rescue
|
1101
|
+
rescue ::IO::WaitWritable
|
1071
1102
|
if ::IO.select(nil, [@socket], nil, deadline)
|
1072
1103
|
retry
|
1073
1104
|
else
|
1074
1105
|
raise SocketTimeoutError
|
1075
1106
|
end
|
1076
|
-
rescue
|
1107
|
+
rescue ::IO::WaitReadable
|
1077
1108
|
if ::IO.select([@socket], nil, nil, deadline)
|
1078
1109
|
retry
|
1079
1110
|
else
|
data/lib/nats/io/parser.rb
CHANGED
data/lib/nats/io/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nats-pure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Waldemar Quevedo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: NATS is an open-source, high-performance, lightweight cloud messaging
|
14
14
|
system.
|