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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a1804a68dcb41e7e76ca9f53fc6b13fc44605090
4
- data.tar.gz: c2b611901423068cdcd6e6add32b18b9079e47fd
3
+ metadata.gz: d7cfcdf1243c5e666ab143dfc2455382b6e005d4
4
+ data.tar.gz: 5b14efd741a22149d8b2d2f32cc2b9f1ae3d26d2
5
5
  SHA512:
6
- metadata.gz: 7dbaaa96f906080b70992031edbe1d257fe5248f9ff72be6df19050b4554cacce53382946196395d24cae27a3db15de4d4acf07c67097c45d76883423f092419
7
- data.tar.gz: e4dcd3d62141d47273518fc0252755495162cd0a4d49432647838a06d65203ee4bbbbabe026217567a253ba4da531d293a5e29f6c321e97548419433d1529602
6
+ metadata.gz: 30bf47ea7e3bb6baf8e52adc60ca7d8d2ef5ee532dd3effe9ae0994d017ee43bce26ff4035901b329fd2865cf9d6f35fb6e5c92515e7afb536f5d471eb91640f
7
+ data.tar.gz: 19a9a15800568b21b583a690c0a3d56dd40841c15369971afd83d38e54b5f1e2752727eb6027b4a9ef42c84317af455c95530216cddd1221dc1c9e38fa226e1c
@@ -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 { |e| raise e }
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
- current = select_next_server
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
- current[:was_connected] = true
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
- current[:reconnect_attempts] = 0
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 { @last_err = e }
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
- current = server_pool.first
393
- current[:error_received] = true
394
- if current[:auth_required]
395
- @err_cb.call(NATS::IO::AuthError.new(err))
396
- else
397
- @err_cb.call(NATS::IO::ServerError.new(err))
398
- end
399
-
400
- # Otherwise, capture the error under a lock and close
401
- # the connection gracefully.
402
- synchronize do
403
- @last_err = NATS::IO::ServerError.new(err)
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
- cs[:user] = @uri.user
620
- cs[:pass] = @uri.password
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
- # Reconnect under a different thread than the one
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
- if @pings_outstanding > @options[:max_outstanding_pings]
752
- # FIXME: Check if we have to dispatch callbacks.
753
- close
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
- current = select_next_server
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
- current[:reconnect_attempts] = 0
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
- # We will retry a number of times to reconnect to a server
968
- # unless we got a hard error from it already.
969
- server[:reconnect_attempts] <= @options[:max_reconnect_attempts] && !server[:error_received]
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 *NBIO_READ_EXCEPTIONS
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 *NBIO_WRITE_EXCEPTIONS
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 *NBIO_WRITE_EXCEPTIONS
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 *NBIO_READ_EXCEPTIONS => e
1107
+ rescue ::IO::WaitReadable
1077
1108
  if ::IO.select([@socket], nil, nil, deadline)
1078
1109
  retry
1079
1110
  else
@@ -24,8 +24,11 @@ module NATS
24
24
  class Parser
25
25
  def initialize(nc)
26
26
  @nc = nc
27
+ reset!
28
+ end
29
+
30
+ def reset!
27
31
  @buf = nil
28
- @needed = nil
29
32
  @parse_state = AWAITING_CONTROL_LINE
30
33
 
31
34
  @sub = nil
@@ -1,7 +1,7 @@
1
1
  module NATS
2
2
  module IO
3
3
  # NOTE: These are all announced to the server on CONNECT
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  LANG = "#{RUBY_ENGINE}2".freeze
6
6
  PROTOCOL = 1
7
7
  end
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.0
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: 2016-12-30 00:00:00.000000000 Z
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.