mongo 1.6.4 → 1.7.0.rc0

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