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