mongo 1.6.4 → 1.7.0.rc0

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.
Files changed (50) hide show
  1. data/README.md +13 -13
  2. data/Rakefile +7 -10
  3. data/docs/{GridFS.md → GRID_FS.md} +0 -0
  4. data/docs/HISTORY.md +16 -0
  5. data/docs/READ_PREFERENCE.md +70 -10
  6. data/docs/TUTORIAL.md +2 -2
  7. data/lib/mongo.rb +2 -0
  8. data/lib/mongo/collection.rb +62 -11
  9. data/lib/mongo/connection.rb +31 -41
  10. data/lib/mongo/cursor.rb +42 -86
  11. data/lib/mongo/db.rb +10 -8
  12. data/lib/mongo/networking.rb +30 -65
  13. data/lib/mongo/repl_set_connection.rb +91 -170
  14. data/lib/mongo/sharded_connection.rb +221 -0
  15. data/lib/mongo/util/node.rb +29 -36
  16. data/lib/mongo/util/pool.rb +10 -3
  17. data/lib/mongo/util/pool_manager.rb +77 -90
  18. data/lib/mongo/util/sharding_pool_manager.rb +143 -0
  19. data/lib/mongo/util/support.rb +22 -2
  20. data/lib/mongo/util/tcp_socket.rb +10 -15
  21. data/lib/mongo/util/uri_parser.rb +17 -10
  22. data/lib/mongo/version.rb +1 -1
  23. data/test/collection_test.rb +133 -1
  24. data/test/connection_test.rb +50 -4
  25. data/test/db_api_test.rb +3 -3
  26. data/test/db_test.rb +6 -1
  27. data/test/replica_sets/basic_test.rb +3 -6
  28. data/test/replica_sets/complex_connect_test.rb +14 -2
  29. data/test/replica_sets/complex_read_preference_test.rb +237 -0
  30. data/test/replica_sets/connect_test.rb +47 -67
  31. data/test/replica_sets/count_test.rb +1 -1
  32. data/test/replica_sets/cursor_test.rb +70 -0
  33. data/test/replica_sets/read_preference_test.rb +171 -118
  34. data/test/replica_sets/refresh_test.rb +3 -3
  35. data/test/replica_sets/refresh_with_threads_test.rb +2 -2
  36. data/test/replica_sets/rs_test_helper.rb +2 -2
  37. data/test/sharded_cluster/basic_test.rb +112 -0
  38. data/test/sharded_cluster/mongo_config_test.rb +126 -0
  39. data/test/sharded_cluster/sc_test_helper.rb +39 -0
  40. data/test/test_helper.rb +3 -3
  41. data/test/threading/threading_with_large_pool_test.rb +1 -1
  42. data/test/tools/mongo_config.rb +307 -0
  43. data/test/tools/repl_set_manager.rb +12 -12
  44. data/test/unit/collection_test.rb +1 -1
  45. data/test/unit/cursor_test.rb +11 -6
  46. data/test/unit/db_test.rb +4 -0
  47. data/test/unit/grid_test.rb +2 -0
  48. data/test/unit/read_test.rb +39 -8
  49. data/test/uri_test.rb +4 -8
  50. metadata +144 -127
@@ -0,0 +1,221 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module Mongo
20
+
21
+ # Instantiates and manages connections to a MongoDB sharded cluster for high availability.
22
+ class ShardedConnection < ReplSetConnection
23
+
24
+ SHARDED_CLUSTER_OPTS = [:refresh_mode, :refresh_interval]
25
+
26
+ attr_reader :seeds, :refresh_interval, :refresh_mode,
27
+ :refresh_version, :manager
28
+
29
+ # Create a connection to a MongoDB sharded cluster.
30
+ #
31
+ # If no args are provided, it will check <code>ENV["MONGODB_URI"]</code>.
32
+ #
33
+ # @param [Array] seeds "host:port" strings
34
+ #
35
+ # @option opts [String] :name (nil) The name of the sharded cluster to connect to. You
36
+ # can use this option to verify that you're connecting to the right sharded cluster.
37
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
38
+ # propagated to DB objects instantiated off of this Connection. This
39
+ # default can be overridden upon instantiation of any DB by explicitly setting a :safe value
40
+ # on initialization.
41
+ # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
42
+ # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
43
+ # connection pool. Note: this setting is relevant only for multi-threaded applications.
44
+ # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
45
+ # this is the number of seconds to wait for a new connection to be released before throwing an exception.
46
+ # Note: this setting is relevant only for multi-threaded applications.
47
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
48
+ # @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
49
+ # connection attempt.
50
+ # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
51
+ # @option opts [Boolean] :refresh_mode (false) Set this to :sync to periodically update the
52
+ # state of the connection every :refresh_interval seconds. Sharded cluster connection failures
53
+ # will always trigger a complete refresh. This option is useful when you want to add new nodes
54
+ # or remove sharded cluster nodes not currently in use by the driver.
55
+ # @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
56
+ # between calls to check the sharded cluster's state.
57
+ # Note: that the number of seed nodes does not have to be equal to the number of sharded cluster members.
58
+ # The purpose of seed nodes is to permit the driver to find at least one sharded cluster member even if a member is down.
59
+ #
60
+ # @example Connect to a sharded cluster and provide two seed nodes.
61
+ # Mongo::ShardedConnection.new(['localhost:30000', 'localhost:30001'])
62
+ #
63
+ # @raise [MongoArgumentError] This is raised for usage errors.
64
+ #
65
+ # @raise [ConnectionFailure] This is raised for the various connection failures.
66
+ def initialize(*args)
67
+ opts = args.last.is_a?(Hash) ? args.pop : {}
68
+
69
+ nodes = args.flatten
70
+
71
+ if nodes.empty? and ENV.has_key?('MONGODB_URI')
72
+ parser = URIParser.new ENV['MONGODB_URI']
73
+ if parser.direct?
74
+ raise MongoArgumentError, "Mongo::ShardedConnection.new called with no arguments, but ENV['MONGODB_URI'] implies a direct connection."
75
+ end
76
+ opts = parser.connection_options.merge! opts
77
+ nodes = [parser.nodes]
78
+ end
79
+
80
+ unless nodes.length > 0
81
+ raise MongoArgumentError, "A ShardedConnection requires at least one seed node."
82
+ end
83
+
84
+ @seeds = nodes.map do |host_port|
85
+ host, port = host_port.split(":")
86
+ [ host, port.to_i ]
87
+ end
88
+
89
+ # TODO: add a method for replacing this list of node.
90
+ @seeds.freeze
91
+
92
+ # Refresh
93
+ @last_refresh = Time.now
94
+ @refresh_version = 0
95
+
96
+ # No connection manager by default.
97
+ @manager = nil
98
+ @old_managers = []
99
+
100
+ # Lock for request ids.
101
+ @id_lock = Mutex.new
102
+
103
+ @pool_mutex = Mutex.new
104
+ @connected = false
105
+
106
+ @safe_mutex_lock = Mutex.new
107
+ @safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
108
+
109
+ @connect_mutex = Mutex.new
110
+ @refresh_mutex = Mutex.new
111
+
112
+ check_opts(opts)
113
+ setup(opts)
114
+ end
115
+
116
+ def valid_opts
117
+ GENERIC_OPTS + SHARDED_CLUSTER_OPTS
118
+ end
119
+
120
+ def inspect
121
+ "<Mongo::ShardedConnection:0x#{self.object_id.to_s(16)} @seeds=#{@seeds.inspect} " +
122
+ "@connected=#{@connected}>"
123
+ end
124
+
125
+ # Initiate a connection to the sharded cluster.
126
+ def connect(force = !@connected)
127
+ return unless force
128
+ log(:info, "Connecting...")
129
+ @connect_mutex.synchronize do
130
+ discovered_seeds = @manager ? @manager.seeds : []
131
+ @old_managers << @manager if @manager
132
+ @manager = ShardingPoolManager.new(self, discovered_seeds | @seeds)
133
+
134
+ Thread.current[:managers] ||= Hash.new
135
+ Thread.current[:managers][self] = @manager
136
+
137
+ @manager.connect
138
+ @refresh_version += 1
139
+ @last_refresh = Time.now
140
+ @connected = true
141
+ end
142
+ end
143
+
144
+ # Force a hard refresh of this connection's view
145
+ # of the sharded cluster.
146
+ #
147
+ # @return [Boolean] +true+ if hard refresh
148
+ # occurred. +false+ is returned when unable
149
+ # to get the refresh lock.
150
+ def hard_refresh!
151
+ log(:info, "Initiating hard refresh...")
152
+ connect(true)
153
+ return true
154
+ end
155
+
156
+ def connected?
157
+ @connected && @manager.primary_pool
158
+ end
159
+
160
+ # Returns +true+ if it's okay to read from a secondary node.
161
+ # Since this is a sharded cluster, this must always be false.
162
+ #
163
+ # This method exist primarily so that Cursor objects will
164
+ # generate query messages with a slaveOkay value of +true+.
165
+ #
166
+ # @return [Boolean] +true+
167
+ def slave_ok?
168
+ false
169
+ end
170
+
171
+ def checkout(&block)
172
+ 2.times do
173
+ if connected?
174
+ sync_refresh
175
+ else
176
+ connect
177
+ end
178
+
179
+ begin
180
+ socket = block.call
181
+ rescue => ex
182
+ checkin(socket) if socket
183
+ raise ex
184
+ end
185
+
186
+ if socket
187
+ return socket
188
+ else
189
+ @connected = false
190
+ #raise ConnectionFailure.new("Could not checkout a socket.")
191
+ end
192
+ end
193
+ end
194
+
195
+ private
196
+
197
+ # Parse option hash
198
+ def setup(opts)
199
+ # Refresh
200
+ @refresh_mode = opts.fetch(:refresh_mode, false)
201
+ @refresh_interval = opts.fetch(:refresh_interval, 90)
202
+
203
+ if @refresh_mode && @refresh_interval < 60
204
+ @refresh_interval = 60 unless ENV['TEST_MODE'] = 'TRUE'
205
+ end
206
+
207
+ if @refresh_mode == :async
208
+ warn ":async refresh mode has been deprecated. Refresh
209
+ mode will be disabled."
210
+ elsif ![:sync, false].include?(@refresh_mode)
211
+ raise MongoArgumentError,
212
+ "Refresh mode must be either :sync or false."
213
+ end
214
+
215
+ opts[:connect_timeout] = opts[:connect_timeout] || 30
216
+
217
+ super opts
218
+ end
219
+
220
+ end
221
+ end
@@ -1,24 +1,18 @@
1
1
  module Mongo
2
2
  class Node
3
3
 
4
- attr_accessor :host, :port, :address, :config, :connection, :socket,
5
- :last_state
4
+ attr_accessor :host, :port, :address, :config, :connection, :socket, :last_state
6
5
 
7
- def initialize(connection, data)
6
+ def initialize(connection, host_port)
8
7
  @connection = connection
9
- if data.is_a?(String)
10
- @host, @port = split_nodes(data)
11
- else
12
- @host = data[0]
13
- @port = data[1].nil? ? Connection::DEFAULT_PORT : data[1].to_i
14
- end
15
- @address = "#{host}:#{port}"
8
+ @host, @port = split_node(host_port)
9
+ @address = "#{@host}:#{@port}"
16
10
  @config = nil
17
11
  @socket = nil
18
12
  end
19
13
 
20
14
  def eql?(other)
21
- other.is_a?(Node) && host == other.host && port == other.port
15
+ other.is_a?(Node) && @address == other.address
22
16
  end
23
17
  alias :== :eql?
24
18
 
@@ -35,16 +29,12 @@ module Mongo
35
29
  # return nil.
36
30
  def connect
37
31
  begin
38
- socket = nil
39
32
  socket = @connection.socket_class.new(@host, @port,
40
33
  @connection.op_timeout, @connection.connect_timeout
41
34
  )
42
-
43
- return nil if socket.nil?
44
35
  rescue OperationTimeout, ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError => ex
45
36
  @connection.log(:debug, "Failed connection to #{host_string} with #{ex.class}, #{ex.message}.")
46
37
  socket.close if socket
47
- return nil
48
38
  end
49
39
 
50
40
  @socket = socket
@@ -65,10 +55,10 @@ module Mongo
65
55
  def active?
66
56
  begin
67
57
  result = @connection['admin'].command({:ping => 1}, :socket => @socket)
68
- return result['ok'] == 1
69
58
  rescue OperationFailure, SocketError, SystemCallError, IOError
70
59
  return nil
71
60
  end
61
+ result['ok'] == 1
72
62
  end
73
63
 
74
64
  # Get the configuration for the provided node as returned by the
@@ -78,7 +68,7 @@ module Mongo
78
68
  begin
79
69
  @config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
80
70
 
81
- if @config['msg'] && @logger
71
+ if @config['msg']
82
72
  @connection.log(:warn, "#{config['msg']}")
83
73
  end
84
74
 
@@ -89,11 +79,7 @@ module Mongo
89
79
  "#{ex.class}: #{ex.message}")
90
80
 
91
81
  # Socket may already be nil from issuing command
92
- if @socket && !@socket.closed?
93
- @socket.close
94
- end
95
-
96
- return nil
82
+ close
97
83
  end
98
84
 
99
85
  @config
@@ -119,18 +105,10 @@ module Mongo
119
105
  return [] unless config['arbiters']
120
106
 
121
107
  config['arbiters'].map do |arbiter|
122
- split_nodes(arbiter)
108
+ split_node(arbiter)
123
109
  end
124
110
  end
125
111
 
126
- def tags
127
- connect unless connected?
128
- set_config unless @config
129
- return {} unless config['tags'] && !config['tags'].empty?
130
-
131
- config['tags']
132
- end
133
-
134
112
  def primary?
135
113
  @config['ismaster'] == true || @config['ismaster'] == 1
136
114
  end
@@ -139,6 +117,10 @@ module Mongo
139
117
  @config['secondary'] == true || @config['secondary'] == 1
140
118
  end
141
119
 
120
+ def tags
121
+ @config['tags'] || {}
122
+ end
123
+
142
124
  def host_port
143
125
  [@host, @port]
144
126
  end
@@ -147,19 +129,30 @@ module Mongo
147
129
  address.hash
148
130
  end
149
131
 
132
+ def healthy?
133
+ if @config.has_key?('secondary')
134
+ @config['ismaster'] || @config['secondary']
135
+ else
136
+ true
137
+ end
138
+ end
139
+
150
140
  private
151
141
 
152
- def split_nodes(host_string)
153
- data = host_string.split(":")
154
- host = data[0]
155
- port = data[1].nil? ? Connection::DEFAULT_PORT : data[1].to_i
142
+ def split_node(host_port)
143
+ if host_port.is_a?(String)
144
+ host_port = host_port.split(":")
145
+ end
146
+
147
+ host = host_port[0]
148
+ port = host_port[1].nil? ? Connection::DEFAULT_PORT : host_port[1].to_i
156
149
 
157
150
  [host, port]
158
151
  end
159
152
 
160
153
  # Ensure that this node is a healty member of a replica set.
161
154
  def check_set_membership(config)
162
- if !config['hosts']
155
+ if !config.has_key?('hosts')
163
156
  message = "Will not connect to #{host_string} because it's not a member " +
164
157
  "of a replica set."
165
158
  raise ConnectionFailure, message
@@ -72,7 +72,13 @@ module Mongo
72
72
  close_sockets(@sockets)
73
73
  @closed = true
74
74
  end
75
+ @node.close if @node
75
76
  end
77
+ true
78
+ end
79
+
80
+ def tags
81
+ @node.tags
76
82
  end
77
83
 
78
84
  def closed?
@@ -81,7 +87,8 @@ module Mongo
81
87
 
82
88
  def inspect
83
89
  "#<Mongo::Pool:0x#{self.object_id.to_s(16)} @host=#{@host} @port=#{port} " +
84
- "@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available.>"
90
+ "@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available " +
91
+ "up=#{!closed?}>"
85
92
  end
86
93
 
87
94
  def host_string
@@ -132,8 +139,8 @@ module Mongo
132
139
 
133
140
  def ping
134
141
  begin
135
- return self.connection['admin'].command({:ping => 1}, :socket => @node.socket)
136
- rescue OperationFailure, SocketError, SystemCallError, IOError
142
+ return self.connection['admin'].command({:ping => 1}, :socket => @node.socket, :timeout => 1)
143
+ rescue ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError
137
144
  return false
138
145
  end
139
146
  end
@@ -2,8 +2,10 @@ module Mongo
2
2
  class PoolManager
3
3
 
4
4
  attr_reader :connection, :arbiters, :primary, :secondaries, :primary_pool,
5
- :read_pool, :secondary_pool, :secondary_pools, :hosts, :nodes,
6
- :max_bson_size, :tags_to_pools, :tag_map, :members
5
+ :secondary_pool, :secondary_pools, :hosts, :nodes, :members, :seeds,
6
+ :max_bson_size
7
+
8
+ attr_accessor :pinned_pools
7
9
 
8
10
  # Create a new set of connection pools.
9
11
  #
@@ -13,8 +15,8 @@ module Mongo
13
15
  # time. The union of these lists will be used when attempting to connect,
14
16
  # with the newly-discovered nodes being used first.
15
17
  def initialize(connection, seeds=[])
18
+ @pinned_pools = {}
16
19
  @connection = connection
17
- @original_seeds = connection.seeds
18
20
  @seeds = seeds
19
21
  @previously_connected = false
20
22
  end
@@ -30,8 +32,6 @@ module Mongo
30
32
  members = connect_to_members
31
33
  initialize_pools(members)
32
34
  cache_discovered_seeds(members)
33
- set_read_pool
34
- set_tag_mappings
35
35
 
36
36
  @members = members
37
37
  @previously_connected = true
@@ -90,38 +90,54 @@ module Mongo
90
90
 
91
91
  def close(opts={})
92
92
  begin
93
- if @primary_pool
94
- @primary_pool.close(opts)
95
- end
93
+ pools.each { |pool| pool.close(opts) }
94
+ rescue ConnectionFailure
95
+ end
96
+ end
96
97
 
97
- if @secondary_pools
98
- @secondary_pools.each do |pool|
99
- pool.close(opts)
100
- end
101
- end
98
+ def read
99
+ read_pool.host_port
100
+ end
102
101
 
103
- if @members
104
- @members.each do |member|
105
- member.close
106
- end
102
+ def read_pool(mode=@connection.read_preference, tags=@connection.tag_sets,
103
+ acceptable_latency=@connection.acceptable_latency)
104
+
105
+ if mode == :primary && !tags.empty?
106
+ raise MongoArgumentError, "Read preferecy :primary cannot be combined with tags"
107
+ end
108
+
109
+ pinned = @pinned_pools[Thread.current]
110
+ if mode && pinned && select_pool([pinned], tags, acceptable_latency) && !pinned.closed?
111
+ pool = pinned
112
+ else
113
+ pool = case mode
114
+ when :primary
115
+ @primary_pool
116
+ when :primary_preferred
117
+ @primary_pool || select_pool(@secondary_pools, tags, acceptable_latency)
118
+ when :secondary
119
+ select_pool(@secondary_pools, tags, acceptable_latency)
120
+ when :secondary_preferred
121
+ select_pool(@secondary_pools, tags, acceptable_latency) || @primary_pool
122
+ when :nearest
123
+ select_pool(pools, tags, acceptable_latency)
107
124
  end
125
+ end
108
126
 
109
- rescue ConnectionFailure
127
+ unless pool
128
+ raise ConnectionFailure, "No replica set member available for query " +
129
+ "with read preference matching mode #{mode} and tags matching #{tags}."
110
130
  end
111
- end
112
131
 
113
- # The set of nodes that this class has discovered and
114
- # successfully connected to.
115
- def seeds
116
- @seeds || []
132
+ pool
117
133
  end
118
134
 
119
- private
120
-
121
135
  def pools
122
- [@primary_pool, *@secondary_pools]
136
+ [@primary_pool, *@secondary_pools].compact
123
137
  end
124
138
 
139
+ private
140
+
125
141
  def validate_existing_member(member)
126
142
  config = member.set_config
127
143
  if !config
@@ -145,6 +161,7 @@ module Mongo
145
161
  def initialize_data
146
162
  @primary = nil
147
163
  @primary_pool = nil
164
+ @read = nil
148
165
  @read_pool = nil
149
166
  @arbiters = []
150
167
  @secondaries = []
@@ -152,9 +169,8 @@ module Mongo
152
169
  @secondary_pools = []
153
170
  @hosts = Set.new
154
171
  @members = Set.new
155
- @tags_to_pools = {}
156
- @tag_map = {}
157
172
  @refresh_required = false
173
+ @pinned_pools = {}
158
174
  end
159
175
 
160
176
  # Connect to each member of the replica set
@@ -167,7 +183,7 @@ module Mongo
167
183
 
168
184
  seed.node_list.each do |host|
169
185
  node = Mongo::Node.new(self.connection, host)
170
- if node.connect && node.set_config
186
+ if node.connect && node.set_config && node.healthy?
171
187
  members << node
172
188
  end
173
189
  end
@@ -180,13 +196,6 @@ module Mongo
180
196
  members
181
197
  end
182
198
 
183
- def associate_tags_with_pool(tags, pool)
184
- tags.each_key do |key|
185
- @tags_to_pools[{key => tags[key]}] ||= []
186
- @tags_to_pools[{key => tags[key]}] << pool
187
- end
188
- end
189
-
190
199
  # Initialize the connection pools for the primary and secondary nodes.
191
200
  def initialize_pools(members)
192
201
  members.each do |member|
@@ -208,68 +217,48 @@ module Mongo
208
217
  member.last_state = :primary
209
218
  @primary = member.host_port
210
219
  @primary_pool = Pool.new(self.connection, member.host, member.port,
211
- :size => self.connection.pool_size,
212
- :timeout => self.connection.pool_timeout,
213
- :node => member)
214
- associate_tags_with_pool(member.tags, @primary_pool)
220
+ :size => self.connection.pool_size,
221
+ :timeout => self.connection.pool_timeout,
222
+ :node => member
223
+ )
215
224
  end
216
225
 
217
226
  def assign_secondary(member)
218
227
  member.last_state = :secondary
219
228
  @secondaries << member.host_port
220
229
  pool = Pool.new(self.connection, member.host, member.port,
221
- :size => self.connection.pool_size,
222
- :timeout => self.connection.pool_timeout,
223
- :node => member)
230
+ :size => self.connection.pool_size,
231
+ :timeout => self.connection.pool_timeout,
232
+ :node => member
233
+ )
224
234
  @secondary_pools << pool
225
- associate_tags_with_pool(member.tags, pool)
226
235
  end
227
236
 
228
- # If there's more than one pool associated with
229
- # a given tag, choose a close one using the bucket method.
230
- def set_tag_mappings
231
- @tags_to_pools.each do |key, pool_list|
232
- if pool_list.length == 1
233
- @tag_map[key] = pool_list.first
234
- else
235
- @tag_map[key] = nearby_pool_from_set(pool_list)
236
- end
237
- end
238
- end
239
-
240
- # Pick a node from the set of possible secondaries.
241
- # If more than one node is available, use the ping
242
- # time to figure out which nodes to choose from.
243
- def set_read_pool
244
- if @secondary_pools.empty?
245
- @read_pool = @primary_pool
246
- elsif @secondary_pools.size == 1
247
- @read_pool = @secondary_pools[0]
248
- @secondary_pool = @read_pool
249
- else
250
- @read_pool = nearby_pool_from_set(@secondary_pools)
251
- @secondary_pool = @read_pool
252
- end
253
- end
237
+ def select_pool(candidates, tag_sets, acceptable_latency)
238
+ tag_sets = [tag_sets] unless tag_sets.is_a?(Array)
254
239
 
255
- def nearby_pool_from_set(pool_set)
256
- ping_ranges = Array.new(3) { |i| Array.new }
257
- pool_set.each do |pool|
258
- case pool.ping_time
259
- when 0..150
260
- ping_ranges[0] << pool
261
- when 150..1000
262
- ping_ranges[1] << pool
263
- else
264
- ping_ranges[2] << pool
240
+ if !tag_sets.empty?
241
+ matches = []
242
+ tag_sets.detect do |tag_set|
243
+ matches = candidates.select do |candidate|
244
+ tag_set.none? { |k,v| candidate.tags[k.to_s] != v } &&
245
+ candidate.ping_time
265
246
  end
247
+ !matches.empty?
266
248
  end
249
+ else
250
+ matches = candidates
251
+ end
267
252
 
268
- for list in ping_ranges do
269
- break if !list.empty?
270
- end
253
+ matches.empty? ? nil : near_pool(matches, acceptable_latency)
254
+ end
271
255
 
272
- list[rand(list.length)]
256
+ def near_pool(pool_set, acceptable_latency)
257
+ nearest_pool = pool_set.min_by { |pool| pool.ping_time }
258
+ near_pools = pool_set.select do |pool|
259
+ (pool.ping_time - nearest_pool.ping_time) <= acceptable_latency
260
+ end
261
+ near_pools[ rand(near_pools.length) ]
273
262
  end
274
263
 
275
264
  # Iterate through the list of provided seed
@@ -278,11 +267,11 @@ module Mongo
278
267
  #
279
268
  # If we don't get a response, raise an exception.
280
269
  def get_valid_seed_node
281
- seed_list.each do |seed|
270
+ @seeds.each do |seed|
282
271
  node = Mongo::Node.new(self.connection, seed)
283
272
  if !node.connect
284
273
  next
285
- elsif node.set_config
274
+ elsif node.set_config && node.healthy?
286
275
  return node
287
276
  else
288
277
  node.close
@@ -290,12 +279,10 @@ module Mongo
290
279
  end
291
280
 
292
281
  raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
293
- "#{seed_list.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
282
+ "#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
294
283
  end
295
284
 
296
- def seed_list
297
- @seeds | @original_seeds
298
- end
285
+ private
299
286
 
300
287
  def cache_discovered_seeds(members)
301
288
  @seeds = members.map { |n| n.host_port }