nats 0.4.28 → 0.5.0.beta.12

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