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 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.