nats 0.4.28 → 0.5.0.beta.12

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTZjYWZjNDUxYmZmMzU2NTIxZjY0ODgwNTBlZWI4MGIwNTA1Y2U2NQ==
5
+ data.tar.gz: !binary |-
6
+ MjVkYjliNDVkMzNkNDA4ZjIxYjBhNjgxMzcxNDQ3MDQ5NzRiYjFjOA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ODFlNmMxMjkzMzgwYmIwM2I5NWMxNzE3YWY2ZjUxYzEwNDZiMzQyYWQ5M2Q5
10
+ MzQ2ZGUzYTZiYmU1NTBhZDczMmZjZjBhMzlhM2Q4ZDkzYzNhYjMzYmRlNzFk
11
+ Yzc2ZjUxNjEyNWUwZDQ3NzcxMDY0NGVmMzdlMDZlN2FlYzExMWQ=
12
+ data.tar.gz: !binary |-
13
+ M2U4MTVjMmYyMzIxODI4NjZjZjhmNDkwOGE1MDM2ZWRmN2I4ZjJjMTYwMTQx
14
+ MWY0MTA2NmJhNzQ3ZWI3NGI1YmY4ODFmMzk2YzRmMzMwOWJjYjJlMGE3MDAy
15
+ N2EzNjM4MGZjOTQzOTgwMTYzNzk1YjY3ZTYzMTdjZTcyYmI0MDA=
data/HISTORY.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # HISTORY
2
2
 
3
+ ## v0.5.0.beta.12 (October 1, 2013)
4
+ - Fixed issue #58, reconnects not stopped on auth failures
5
+ - Fixed leaking ping timers on auth failures
6
+ - Created AuthError
7
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.5.0.beta.11...v0.5.0.beta.12
8
+
9
+ ## v0.5.0.beta.11 (July 26, 2013)
10
+ - Bi-directional Route designation
11
+ - Upgrade to EM 1.x
12
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.5.0.beta.1...v0.5.0.beta.11
13
+
14
+ ## v0.5.0.beta.1 (Sept 10, 2012)
15
+ - Clustering support for nats-servers
16
+ - Reconnect client logic cluster aware (explicit servers only for now)
17
+ - See full list @ https://github.com/derekcollison/nats/compare/v0.4.26...v0.5.0.beta.1
18
+
3
19
  ## v0.4.28 (September 22, 2012)
4
20
  - Binary payload bug fix
5
21
  - Lock EM to version 0.12.10, 1.0 does not pass tests currently.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A lightweight publish-subscribe and distributed queueing messaging system.
4
4
 
5
- [![Build Status](https://secure.travis-ci.org/derekcollison/nats.png)](http://travis-ci.org/derekcollison/nats)
5
+ [![Build Status](https://secure.travis-ci.org/derekcollison/nats.png?branch=cluster)](http://travis-ci.org/derekcollison/nats)
6
6
 
7
7
  ## Supported Platforms
8
8
 
@@ -12,11 +12,14 @@ This gem currently works on the following Ruby platforms:
12
12
  - Rubinius
13
13
  - JRuby
14
14
 
15
+ ## Additional Clients
16
+
15
17
  There are several other client language bindings as well.
16
18
 
17
- [Node.js](https://github.com/derekcollison/node_nats)
18
- [Go - Apcera](https://github.com/apcera/nats)
19
- [Go - CloudFoundry](https://github.com/cloudfoundry/gonats)
19
+ - [Node.js](https://github.com/derekcollison/node_nats)
20
+ - [Go](https://github.com/apcera/nats)
21
+ - [Java](https://github.com/tyagihas/java_nats)
22
+ - [Java - Spring](https://github.com/mheath/jnats)
20
23
 
21
24
  ## Getting Started
22
25
 
@@ -112,7 +115,7 @@ See examples and benchmarks for more information..
112
115
 
113
116
  (The MIT License)
114
117
 
115
- Copyright (c) 2010-2012 Derek Collison
118
+ Copyright (c) 2010-2013 Derek Collison
116
119
 
117
120
  Permission is hereby granted, free of charge, to any person obtaining a copy
118
121
  of this software and associated documentation files (the "Software"), to
data/bin/nats-queue CHANGED
@@ -7,14 +7,14 @@ require 'nats/client'
7
7
  ['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
8
8
 
9
9
  def usage
10
- puts "Usage: nats-queue <subject> <queue name> [-s server] [-t]"; exit
10
+ puts "Usage: nats-queue <subject> <queue name> [-s server] [-t] [-r]"; exit
11
11
  end
12
12
 
13
13
  args = ARGV.dup
14
14
  opts_parser = OptionParser.new do |opts|
15
15
  opts.on('-s SERVER') { |server| $nats_server = server }
16
- opts.on('-t') { $show_time = true }
17
-
16
+ opts.on('-t','--time') { $show_time = true }
17
+ opts.on('-r','--raw') { $show_raw = true }
18
18
  end
19
19
  args = opts_parser.parse!(args)
20
20
 
@@ -30,11 +30,20 @@ def header
30
30
  "#{time_prefix}[\##{$i+=1}]"
31
31
  end
32
32
 
33
+ def decorate sub, msg
34
+ if $show_raw
35
+ msg
36
+ else
37
+ "#{header} Received on [#{sub}] : '#{msg}'"
38
+ end
39
+ end
40
+
41
+
33
42
  NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
34
43
 
35
44
  NATS.start(:uri => $nats_server, :autostart => true) do
36
- puts "Listening on [#{subject}], queue group [#{queue_group}]"
45
+ puts "Listening on [#{subject}], queue group [#{queue_group}]" unless $show_raw
37
46
  NATS.subscribe(subject, :queue => queue_group) { |msg, _, sub|
38
- puts "#{header} Received on [#{sub}] : '#{msg}'"
47
+ puts decorate(sub, msg)
39
48
  }
40
49
  end
data/bin/nats-request CHANGED
@@ -7,13 +7,14 @@ require 'nats/client'
7
7
  ['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
8
8
 
9
9
  def usage
10
- puts "Usage: nats-request <subject> <msg> [-s server] [-t] [-n responses]"; exit
10
+ puts "Usage: nats-request <subject> <msg> [-s server] [-t] [-r] [-n responses]"; exit
11
11
  end
12
12
 
13
13
  args = ARGV.dup
14
14
  opts_parser = OptionParser.new do |opts|
15
15
  opts.on('-s SERVER') { |server| $nats_server = server }
16
- opts.on('-t') { $show_time = true }
16
+ opts.on('-t','--time') { $show_time = true }
17
+ opts.on('-r','--raw') { $show_raw = true }
17
18
  opts.on('-n RESPONSES') { |responses| $responses = Integer(responses) if Integer(responses) > 0 }
18
19
  end
19
20
  args = opts_parser.parse!(args)
@@ -31,11 +32,19 @@ def header
31
32
  "#{time_prefix}[\##{$i+=1}]"
32
33
  end
33
34
 
35
+ def decorate msg
36
+ if $show_raw
37
+ msg
38
+ else
39
+ "#{header} Replied with : '#{msg}'"
40
+ end
41
+ end
42
+
34
43
  NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
35
44
 
36
45
  NATS.start(:uri => $nats_server, :autostart => true) do
37
46
  NATS.request(subject, msg) { |(msg, reply)|
38
- puts "#{header} Replied with : '#{msg}'"
47
+ puts decorate(msg)
39
48
  exit! if $responses && ($responses-=1) < 1
40
49
  }
41
50
  end
data/bin/nats-sub CHANGED
@@ -7,14 +7,14 @@ require 'nats/client'
7
7
  ['TERM', 'INT'].each { |s| trap(s) { puts; exit! } }
8
8
 
9
9
  def usage
10
- puts "Usage: nats-sub <subject> [-s server] [-t]"; exit
10
+ puts "Usage: nats-sub <subject> [-s server] [-t] [-r]"; exit
11
11
  end
12
12
 
13
13
  args = ARGV.dup
14
14
  opts_parser = OptionParser.new do |opts|
15
15
  opts.on('-s SERVER') { |server| $nats_server = server }
16
- opts.on('-t') { $show_time = true }
17
-
16
+ opts.on('-t','--time') { $show_time = true }
17
+ opts.on('-r','--raw') { $show_raw = true }
18
18
  end
19
19
  args = opts_parser.parse!(args)
20
20
 
@@ -30,9 +30,17 @@ def header
30
30
  "#{time_prefix}[\##{$i+=1}]"
31
31
  end
32
32
 
33
+ def decorate sub, msg
34
+ if $show_raw
35
+ msg
36
+ else
37
+ "#{header} Received on [#{sub}] : '#{msg}'"
38
+ end
39
+ end
40
+
33
41
  NATS.on_error { |err| puts "Server Error: #{err}"; exit! }
34
42
 
35
43
  NATS.start(:uri => $nats_server, :autostart => true) do
36
- puts "Listening on [#{subject}]"
37
- NATS.subscribe(subject) { |msg, _, sub| puts "#{header} Received on [#{sub}] : '#{msg}'" }
44
+ puts "Listening on [#{subject}]" unless $show_raw
45
+ NATS.subscribe(subject) { |msg, _, sub| puts decorate(sub, msg) }
38
46
  end
data/lib/nats/client.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'securerandom'
2
3
 
3
4
  ep = File.expand_path(File.dirname(__FILE__))
4
5
 
@@ -8,7 +9,7 @@ require "#{ep}/ext/json"
8
9
 
9
10
  module NATS
10
11
 
11
- VERSION = '0.4.28'.freeze
12
+ VERSION = "0.5.0.beta.12".freeze
12
13
 
13
14
  DEFAULT_PORT = 4222
14
15
  DEFAULT_URI = "nats://localhost:#{DEFAULT_PORT}".freeze
@@ -21,6 +22,10 @@ module NATS
21
22
  # Maximum outbound size per client to trigger FP, 20MB
22
23
  FAST_PRODUCER_THRESHOLD = (10*1024*1024)
23
24
 
25
+ # Ping intervals
26
+ DEFAULT_PING_INTERVAL = 120
27
+ DEFAULT_PING_MAX = 2
28
+
24
29
  # Protocol
25
30
  # @private
26
31
  MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\r\n/i #:nodoc:
@@ -66,6 +71,9 @@ module NATS
66
71
  # When we cannot connect to the server (either initially or after a reconnect), this is raised/passed
67
72
  class ConnectError < Error; end #:nodoc:
68
73
 
74
+ # When we cannot connect to the server because authorization failed.
75
+ class AuthError < ConnectError; end #:nodoc:
76
+
69
77
  class << self
70
78
  attr_reader :client, :reactor_was_running, :err_cb, :err_cb_overridden #:nodoc:
71
79
  attr_reader :reconnect_cb #:nodoc
@@ -87,6 +95,8 @@ module NATS
87
95
  # @option opts [Boolean] :ssl Boolean that is sent to server for setting TLS/SSL mode.
88
96
  # @option opts [Integer] :max_reconnect_attempts Integer that can be used to set the max number of reconnect tries
89
97
  # @option opts [Integer] :reconnect_time_wait Integer that can be used to set the number of seconds to wait between reconnect tries
98
+ # @option opts [Integer] :ping_interval Integer that can be used to set the ping interval in seconds.
99
+ # @option opts [Integer] :max_outstanding_pings Integer that can be used to set the max number of outstanding pings before declaring a connection closed.
90
100
  # @param [Block] &blk called when the connection is completed. Connection will be passed to the block.
91
101
  # @return [NATS] connection to the server.
92
102
  def connect(opts={}, &blk)
@@ -97,6 +107,8 @@ module NATS
97
107
  opts[:ssl] = false if opts[:ssl].nil?
98
108
  opts[:max_reconnect_attempts] = MAX_RECONNECT_ATTEMPTS if opts[:max_reconnect_attempts].nil?
99
109
  opts[:reconnect_time_wait] = RECONNECT_TIME_WAIT if opts[:reconnect_time_wait].nil?
110
+ opts[:ping_interval] = DEFAULT_PING_INTERVAL if opts[:ping_interval].nil?
111
+ opts[:max_outstanding_pings] = DEFAULT_PING_MAX if opts[:max_outstanding_pings].nil?
100
112
 
101
113
  # Override with ENV
102
114
  opts[:uri] ||= ENV['NATS_URI'] || DEFAULT_URI
@@ -108,7 +120,19 @@ module NATS
108
120
  opts[:ssl] = ENV['NATS_SSL'].downcase == 'true' unless ENV['NATS_SSL'].nil?
109
121
  opts[:max_reconnect_attempts] = ENV['NATS_MAX_RECONNECT_ATTEMPTS'].to_i unless ENV['NATS_MAX_RECONNECT_ATTEMPTS'].nil?
110
122
  opts[:reconnect_time_wait] = ENV['NATS_RECONNECT_TIME_WAIT'].to_i unless ENV['NATS_RECONNECT_TIME_WAIT'].nil?
111
- @uri = opts[:uri] = opts[:uri].is_a?(URI) ? opts[:uri] : URI.parse(opts[:uri])
123
+
124
+ opts[:ping_interval] = ENV['NATS_PING_INTERVAL'].to_i unless ENV['NATS_PING_INTERVAL'].nil?
125
+ opts[:max_outstanding_pings] = ENV['NATS_MAX_OUTSTANDING_PINGS'].to_i unless ENV['NATS_MAX_OUTSTANDING_PINGS'].nil?
126
+
127
+ uri = opts[:uris] || opts[:servers] || opts[:uri]
128
+
129
+ # If they pass an array here just pass along to the real connection, and use first as the first attempt..
130
+ # Real connection will do proper walk throughs etc..
131
+ unless uri.nil?
132
+ u = uri.kind_of?(Array) ? uri.first : uri
133
+ @uri = u.is_a?(URI) ? u.dup : URI.parse(u)
134
+ end
135
+
112
136
  @err_cb = proc { |e| raise e } unless err_cb
113
137
  check_autostart(@uri) if opts[:autostart] == true
114
138
 
@@ -122,7 +146,7 @@ module NATS
122
146
  def start(*args, &blk)
123
147
  @reactor_was_running = EM.reactor_running?
124
148
  unless (@reactor_was_running || blk)
125
- raise(Error, "EM needs to be running when NATS.start called without a run block")
149
+ raise(Error, "EM needs to be running when NATS.start is called without a run block")
126
150
  end
127
151
  # Setup optimized select versions
128
152
  EM.epoll; EM.kqueue
@@ -172,6 +196,7 @@ module NATS
172
196
  # @param [Block] &callback called when a reconnect attempt is made.
173
197
  def on_reconnect(&callback)
174
198
  @reconnect_cb = callback
199
+ @client.on_reconnect(&callback) unless @client.nil?
175
200
  end
176
201
 
177
202
  # Publish a message using the default client connection.
@@ -207,9 +232,7 @@ module NATS
207
232
  # Returns a subject that can be used for "directed" communications.
208
233
  # @return [String]
209
234
  def create_inbox
210
- v = [rand(0x0010000),rand(0x0010000),rand(0x0010000),
211
- rand(0x0010000),rand(0x0010000),rand(0x1000000)]
212
- "_INBOX.%04x%04x%04x%04x%04x%06x" % v
235
+ "_INBOX.#{SecureRandom.hex(13)}"
213
236
  end
214
237
 
215
238
  # Flushes all messages and subscriptions in the default connection
@@ -272,8 +295,8 @@ module NATS
272
295
 
273
296
  end
274
297
 
275
- attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden #:nodoc:
276
- attr_reader :closing, :reconnecting, :options, :server_info #:nodoc
298
+ attr_reader :connected, :connect_cb, :err_cb, :err_cb_overridden, :pongs_received #:nodoc:
299
+ attr_reader :closing, :reconnecting, :server_pool, :options, :server_info #:nodoc
277
300
  attr_reader :msgs_received, :msgs_sent, :bytes_received, :bytes_sent, :pings
278
301
 
279
302
  alias :connected? :connected
@@ -281,16 +304,14 @@ module NATS
281
304
  alias :reconnecting? :reconnecting
282
305
 
283
306
  def initialize(options)
284
- @uri = options[:uri]
285
- @uri.user = options[:user] if options[:user]
286
- @uri.password = options[:pass] if options[:pass]
287
- @ssl = options[:ssl] if options[:ssl]
288
307
  @options = options
308
+ process_uri_options
309
+ @ssl = options[:ssl] if options[:ssl]
289
310
  @ssid, @subs = 1, {}
290
311
  @err_cb = NATS.err_cb
291
312
  @reconnect_timer, @needed = nil, nil
292
313
  @reconnect_cb = NATS.reconnect_cb
293
- @connected, @closing, @reconnecting = false, false, false
314
+ @connected, @closing, @reconnecting, @conn_cb_called = false, false, false, false
294
315
  @msgs_received = @msgs_sent = @bytes_received = @bytes_sent = @pings = 0
295
316
  @pending_size = 0
296
317
  send_connect_command
@@ -418,7 +439,8 @@ module NATS
418
439
  # Close the connection to the server.
419
440
  def close
420
441
  @closing = true
421
- EM.cancel_timer(@reconnect_timer) if @reconnect_timer
442
+ cancel_ping_timer
443
+ cancel_reconnect_timer
422
444
  close_connection_after_writing if connected?
423
445
  process_disconnect if reconnecting?
424
446
  end
@@ -432,14 +454,22 @@ module NATS
432
454
  err_cb_overridden || NATS.err_cb_overridden
433
455
  end
434
456
 
435
- def send_connect_command #:nodoc:
457
+ def auth_connection?
458
+ !@uri.user.nil?
459
+ end
460
+
461
+ def connect_command #:nodoc:
436
462
  cs = { :verbose => @options[:verbose], :pedantic => @options[:pedantic] }
437
- if @uri.user
463
+ if auth_connection?
438
464
  cs[:user] = @uri.user
439
465
  cs[:pass] = @uri.password
440
466
  end
441
467
  cs[:ssl_required] = @ssl if @ssl
442
- send_command("CONNECT #{cs.to_json}#{CR_LF}", true)
468
+ "CONNECT #{cs.to_json}#{CR_LF}"
469
+ end
470
+
471
+ def send_connect_command #:nodoc:
472
+ send_command(connect_command, true)
443
473
  end
444
474
 
445
475
  def queue_server_rt(&cb) #:nodoc:
@@ -465,8 +495,6 @@ module NATS
465
495
  @subs.delete(sid) if (sub[:received] == sub[:max])
466
496
  end
467
497
 
468
- return unsubscribe(sid) if (sub[:max] && (sub[:received] > sub[:max]))
469
-
470
498
  if cb = sub[:callback]
471
499
  case cb.arity
472
500
  when 0 then cb.call
@@ -503,7 +531,13 @@ module NATS
503
531
  @buf = $'
504
532
  when ERR
505
533
  @buf = $'
506
- err_cb.call(NATS::ServerError.new($1))
534
+ current = server_pool.first
535
+ current[:error_received] = true
536
+ if current[:auth_required] && !current[:auth_ok]
537
+ err_cb.call(NATS::AuthError.new($1))
538
+ else
539
+ err_cb.call(NATS::ServerError.new($1))
540
+ end
507
541
  when PING
508
542
  @pings += 1
509
543
  @buf = $'
@@ -547,6 +581,12 @@ module NATS
547
581
  err_cb.call(NATS::ClientError.new('TLS/SSL not supported by server'))
548
582
  end
549
583
  end
584
+ if @server_info[:auth_required]
585
+ current = server_pool.first
586
+ current[:auth_required] = true
587
+ queue_server_rt { current[:auth_ok] = true }
588
+ flush_pending
589
+ end
550
590
  @server_info
551
591
  end
552
592
 
@@ -555,38 +595,103 @@ module NATS
555
595
  flush_pending
556
596
  end
557
597
 
598
+ def cancel_ping_timer
599
+ if @ping_timer
600
+ EM.cancel_timer(@ping_timer)
601
+ @ping_timer = nil
602
+ end
603
+ end
604
+
558
605
  def connection_completed #:nodoc:
559
606
  @connected = true unless @ssl
607
+
608
+ current = server_pool.first
609
+ current[:was_connected] = true
610
+ current[:reconnect_attempts] = 0
611
+
560
612
  if reconnecting?
561
- EM.cancel_timer(@reconnect_timer)
562
- send_connect_command
613
+ cancel_reconnect_timer
563
614
  @subs.each_pair { |k, v| send_command("SUB #{v[:subject]} #{v[:queue]} #{k}#{CR_LF}") }
564
615
  end
565
- flush_pending unless @ssl
616
+
566
617
  unless user_err_cb? or reconnecting?
567
618
  @err_cb = proc { |e| raise e }
568
619
  end
569
- if (connect_cb and not reconnecting?)
620
+
621
+ flush_pending unless @ssl
622
+
623
+ if (connect_cb and not @conn_cb_called)
570
624
  # We will round trip the server here to make sure all state from any pending commands
571
625
  # has been processed before calling the connect callback.
572
- queue_server_rt { connect_cb.call(self) }
626
+ queue_server_rt do
627
+ connect_cb.call(self)
628
+ @conn_cb_called = true
629
+ end
573
630
  end
574
631
  @reconnecting = false
575
632
  @parse_state = AWAITING_CONTROL_LINE
633
+
634
+ # Initialize ping timer and processing
635
+ @pings_outstanding = 0
636
+ @pongs_received = 0
637
+ @ping_timer = EM.add_periodic_timer(@options[:ping_interval]) { send_ping }
638
+ end
639
+
640
+ def send_ping #:nodoc:
641
+ return if @closing
642
+ if @pings_outstanding > @options[:max_outstanding_pings]
643
+ close_connection
644
+ #close
645
+ return
646
+ end
647
+ @pings_outstanding += 1
648
+ queue_server_rt { process_pong }
649
+ flush_pending
650
+ end
651
+
652
+ def process_pong
653
+ @pongs_received += 1
654
+ @pings_outstanding -= 1
655
+ end
656
+
657
+ def should_delay_connect?(server)
658
+ server[:was_connected] && server[:reconnect_attempts] >= 1
576
659
  end
577
660
 
578
- def schedule_reconnect(wait=RECONNECT_TIME_WAIT) #:nodoc:
661
+ def schedule_reconnect #:nodoc:
579
662
  @reconnecting = true
580
- @reconnect_attempts = 0
581
663
  @connected = false
582
- @reconnect_timer = EM.add_periodic_timer(wait) { attempt_reconnect }
664
+ @reconnect_timer = EM.add_timer(@options[:reconnect_time_wait]) { attempt_reconnect }
583
665
  end
584
666
 
585
667
  def unbind #:nodoc:
586
- if connected? and not closing? and not reconnecting? and @options[:reconnect]
587
- schedule_reconnect(@options[:reconnect_time_wait])
588
- else
589
- process_disconnect unless reconnecting?
668
+ # If we are closing or shouldn't reconnect, go ahead and disconnect.
669
+ process_disconnect and return if (closing? or should_not_reconnect?)
670
+
671
+ @reconnecting = true if connected?
672
+ @connected = false
673
+ @pending = @pongs = nil
674
+ cancel_ping_timer
675
+
676
+ schedule_primary_and_connect
677
+ end
678
+
679
+ def multiple_servers_available?
680
+ server_pool && server_pool.size > 1
681
+ end
682
+
683
+ def had_error?
684
+ server_pool.first && server_pool.first[:error_received]
685
+ end
686
+
687
+ def should_not_reconnect?
688
+ !@options[:reconnect]
689
+ end
690
+
691
+ def cancel_reconnect_timer
692
+ if @reconnect_timer
693
+ EM.cancel_timer(@reconnect_timer)
694
+ @reconnect_timer = nil
590
695
  end
591
696
  end
592
697
 
@@ -597,18 +702,26 @@ module NATS
597
702
 
598
703
  def process_disconnect #:nodoc:
599
704
  err_cb.call(NATS::ConnectError.new(disconnect_error_string)) if not closing? and @err_cb
705
+ true # Chaining
600
706
  ensure
601
- EM.cancel_timer(@reconnect_timer) if @reconnect_timer
707
+ cancel_ping_timer
708
+ cancel_reconnect_timer
602
709
  if (NATS.client == self)
603
710
  NATS.clear_client
604
711
  EM.stop if ((connected? || reconnecting?) and closing? and not NATS.reactor_was_running?)
605
712
  end
606
713
  @connected = @reconnecting = false
607
- true # Chaining
714
+ end
715
+
716
+ def can_reuse_server?(server) #:nodoc:
717
+ reconnecting? && server[:was_connected] && server[:reconnect_attempts] <= @options[:max_reconnect_attempts]
608
718
  end
609
719
 
610
720
  def attempt_reconnect #:nodoc:
611
- process_disconnect and return if (@reconnect_attempts += 1) > @options[:max_reconnect_attempts]
721
+ @reconnect_timer = nil
722
+ current = server_pool.first
723
+ current[:reconnect_attempts] += 1 if current[:reconnect_attempts]
724
+ send_connect_command
612
725
  EM.reconnect(@uri.host, @uri.port, self)
613
726
  @reconnect_cb.call unless @reconnect_cb.nil?
614
727
  end
@@ -626,6 +739,47 @@ module NATS
626
739
  true
627
740
  end
628
741
 
742
+ # Parse out URIs which can now be an array of server choices
743
+ # The server pool will contain both explicit and implicit members.
744
+ def process_uri_options #:nodoc
745
+ @server_pool = []
746
+ uri = options[:uris] || options[:servers] || options[:uri]
747
+ uri = uri.kind_of?(Array) ? uri : [uri]
748
+ uri.each { |u| server_pool << { :uri => u.is_a?(URI) ? u.dup : URI.parse(u) } }
749
+ server_pool.shuffle! unless options[:dont_randomize_servers]
750
+ bind_primary
751
+ end
752
+
753
+ def connected_server
754
+ connected? ? @uri : nil
755
+ end
756
+
757
+ def bind_primary #:nodoc:
758
+ first = server_pool.first
759
+ @uri = first[:uri]
760
+ @uri.user = options[:user] if options[:user]
761
+ @uri.password = options[:pass] if options[:pass]
762
+ first
763
+ end
764
+
765
+ # We have failed on an attempt at the primary (first) server, rotate and try again
766
+ def schedule_primary_and_connect #:nodoc:
767
+ # Dump the one we were trying if it wasn't connected
768
+ current = server_pool.shift
769
+ server_pool << current if (current && can_reuse_server?(current) && !current[:error_received])
770
+ # If we are out of options, go ahead and disconnect.
771
+ process_disconnect and return if server_pool.empty?
772
+ # bind new one
773
+ next_server = bind_primary
774
+ # If the next one was connected and we are trying to reconnect
775
+ # set up timer if we tried once already.
776
+ if should_delay_connect?(next_server)
777
+ schedule_reconnect
778
+ else
779
+ attempt_reconnect
780
+ end
781
+ end
782
+
629
783
  def inspect #:nodoc:
630
784
  "<nats client v#{NATS::VERSION}>"
631
785
  end