mongo 1.3.1 → 1.4.0

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 (75) hide show
  1. data/README.md +9 -6
  2. data/Rakefile +3 -4
  3. data/docs/HISTORY.md +20 -2
  4. data/docs/READ_PREFERENCE.md +39 -0
  5. data/docs/RELEASES.md +1 -1
  6. data/docs/REPLICA_SETS.md +23 -2
  7. data/docs/TAILABLE_CURSORS.md +51 -0
  8. data/docs/TUTORIAL.md +4 -4
  9. data/docs/WRITE_CONCERN.md +5 -2
  10. data/lib/mongo.rb +7 -22
  11. data/lib/mongo/collection.rb +96 -29
  12. data/lib/mongo/connection.rb +107 -62
  13. data/lib/mongo/cursor.rb +136 -57
  14. data/lib/mongo/db.rb +26 -5
  15. data/lib/mongo/exceptions.rb +17 -1
  16. data/lib/mongo/gridfs/grid.rb +1 -1
  17. data/lib/mongo/repl_set_connection.rb +273 -156
  18. data/lib/mongo/util/logging.rb +42 -0
  19. data/lib/mongo/util/node.rb +183 -0
  20. data/lib/mongo/util/pool.rb +76 -13
  21. data/lib/mongo/util/pool_manager.rb +208 -0
  22. data/lib/mongo/util/ssl_socket.rb +38 -0
  23. data/lib/mongo/util/support.rb +9 -1
  24. data/lib/mongo/util/timeout.rb +42 -0
  25. data/lib/mongo/version.rb +3 -0
  26. data/mongo.gemspec +2 -2
  27. data/test/bson/binary_test.rb +1 -1
  28. data/test/bson/bson_string_test.rb +30 -0
  29. data/test/bson/bson_test.rb +6 -3
  30. data/test/bson/byte_buffer_test.rb +1 -1
  31. data/test/bson/hash_with_indifferent_access_test.rb +1 -1
  32. data/test/bson/json_test.rb +1 -1
  33. data/test/bson/object_id_test.rb +2 -18
  34. data/test/bson/ordered_hash_test.rb +38 -3
  35. data/test/bson/test_helper.rb +46 -0
  36. data/test/bson/timestamp_test.rb +32 -10
  37. data/test/collection_test.rb +89 -3
  38. data/test/connection_test.rb +35 -20
  39. data/test/cursor_test.rb +63 -2
  40. data/test/db_test.rb +12 -2
  41. data/test/pool_test.rb +21 -0
  42. data/test/replica_sets/connect_test.rb +26 -13
  43. data/test/replica_sets/connection_string_test.rb +1 -4
  44. data/test/replica_sets/count_test.rb +1 -0
  45. data/test/replica_sets/insert_test.rb +1 -0
  46. data/test/replica_sets/pooled_insert_test.rb +4 -1
  47. data/test/replica_sets/query_secondaries.rb +2 -1
  48. data/test/replica_sets/query_test.rb +2 -1
  49. data/test/replica_sets/read_preference_test.rb +43 -0
  50. data/test/replica_sets/refresh_test.rb +123 -0
  51. data/test/replica_sets/replication_ack_test.rb +9 -4
  52. data/test/replica_sets/rs_test_helper.rb +2 -2
  53. data/test/timeout_test.rb +14 -0
  54. data/test/tools/repl_set_manager.rb +134 -23
  55. data/test/unit/collection_test.rb +6 -8
  56. data/test/unit/connection_test.rb +4 -4
  57. data/test/unit/cursor_test.rb +23 -5
  58. data/test/unit/db_test.rb +2 -0
  59. data/test/unit/grid_test.rb +2 -0
  60. data/test/unit/node_test.rb +73 -0
  61. data/test/unit/pool_manager_test.rb +47 -0
  62. data/test/unit/read_test.rb +101 -0
  63. metadata +214 -138
  64. data/lib/mongo/test.rb +0 -20
  65. data/test/async/collection_test.rb +0 -224
  66. data/test/async/connection_test.rb +0 -24
  67. data/test/async/cursor_test.rb +0 -162
  68. data/test/async/worker_pool_test.rb +0 -99
  69. data/test/load/resque/load.rb +0 -21
  70. data/test/load/resque/processor.rb +0 -26
  71. data/test/load/unicorn/unicorn.rb +0 -29
  72. data/test/tools/load.rb +0 -58
  73. data/test/tools/sharding_manager.rb +0 -202
  74. data/test/tools/test.rb +0 -4
  75. data/test/unit/repl_set_connection_test.rb +0 -59
@@ -63,7 +63,7 @@ module Mongo
63
63
  # @option opts [Boolean] :strict (False) If true, collections must exist to be accessed and must
64
64
  # not exist to be created. See DB#collection and DB#create_collection.
65
65
  #
66
- # @option opts [Object, #create_pk(doc)] :pk (Mongo::ObjectId) A primary key factory object,
66
+ # @option opts [Object, #create_pk(doc)] :pk (BSON::ObjectId) A primary key factory object,
67
67
  # which should take a hash and return a hash which merges the original hash with any primary key
68
68
  # fields the factory wishes to inject. (NOTE: if the object already has a primary key,
69
69
  # the factory should not inject a new key).
@@ -82,6 +82,12 @@ module Mongo
82
82
  @strict = opts[:strict]
83
83
  @pk_factory = opts[:pk]
84
84
  @safe = opts.fetch(:safe, @connection.safe)
85
+ if value = opts[:read]
86
+ Mongo::Support.validate_read_preference(value)
87
+ else
88
+ value = @connection.read_preference
89
+ end
90
+ @read_preference = value.is_a?(Hash) ? value.dup : value
85
91
  @cache_time = opts[:cache_time] || 300 #5 minutes.
86
92
  end
87
93
 
@@ -121,13 +127,14 @@ module Mongo
121
127
  auth['user'] = username
122
128
  auth['nonce'] = nonce
123
129
  auth['key'] = Mongo::Support.auth_key(username, password, nonce)
124
- if ok?(self.command(auth, :check_response => false, :socket => opts[:socket]))
130
+ if ok?(doc = self.command(auth, :check_response => false, :socket => opts[:socket]))
125
131
  if save_auth
126
132
  @connection.add_auth(@name, username, password)
127
133
  end
128
134
  true
129
135
  else
130
- raise(Mongo::AuthenticationError, "Failed to authenticate user '#{username}' on db '#{self.name}'")
136
+ message = "Failed to authenticate user '#{username}' on db '#{self.name}'"
137
+ raise Mongo::AuthenticationError.new(message, doc['code'], doc)
131
138
  end
132
139
  end
133
140
 
@@ -269,7 +276,8 @@ module Mongo
269
276
  #
270
277
  # @return [Mongo::Collection]
271
278
  def create_collection(name, opts={})
272
- if collection_names.include?(name.to_s)
279
+ name = name.to_s
280
+ if collection_names.include?(name)
273
281
  if strict?
274
282
  raise MongoDBError, "Collection #{name} already exists. " +
275
283
  "Currently in strict mode."
@@ -503,7 +511,13 @@ module Mongo
503
511
  if result.nil?
504
512
  raise OperationFailure, "Database command '#{selector.keys.first}' failed: returned null."
505
513
  elsif (check_response && !ok?(result))
506
- raise OperationFailure, "Database command '#{selector.keys.first}' failed: #{result.inspect}"
514
+ message = "Database command '#{selector.keys.first}' failed: ("
515
+ message << result.map do |key, value|
516
+ "#{key}: '#{value}'"
517
+ end.join('; ')
518
+ message << ').'
519
+ code = result['code'] || result['assertionCode']
520
+ raise OperationFailure.new(message, code, result)
507
521
  else
508
522
  result
509
523
  end
@@ -608,6 +622,13 @@ module Mongo
608
622
  doc
609
623
  end
610
624
 
625
+ # The value of the read preference. This will be
626
+ # either +:primary+, +:secondary+, or an object
627
+ # representing the tags to be read from.
628
+ def read_preference
629
+ @read_preference
630
+ end
631
+
611
632
  private
612
633
 
613
634
  def system_command_collection
@@ -22,7 +22,20 @@ module Mongo
22
22
  class MongoRubyError < StandardError; end
23
23
 
24
24
  # Raised when MongoDB itself has returned an error.
25
- class MongoDBError < RuntimeError; end
25
+ class MongoDBError < RuntimeError
26
+
27
+ # @return The entire failed command's response object, if available.
28
+ attr_reader :result
29
+
30
+ # @return The failed command's error code, if availab.e
31
+ attr_reader :error_code
32
+
33
+ def initialize(message=nil, error_code=nil, result=nil)
34
+ @error_code = error_code
35
+ @result = result
36
+ super(message)
37
+ end
38
+ end
26
39
 
27
40
  # Raised when configuration options cause connections, queries, etc., to fail.
28
41
  class ConfigurationError < MongoRubyError; end
@@ -48,6 +61,9 @@ module Mongo
48
61
  # Raised on failures in connection to the database server.
49
62
  class ConnectionTimeoutError < MongoRubyError; end
50
63
 
64
+ # Raised when no tags in a read preference maps to a given connection.
65
+ class NodeWithTagsNotFound < MongoRubyError; end
66
+
51
67
  # Raised when a connection operation fails.
52
68
  class ConnectionFailure < MongoDBError; end
53
69
 
@@ -63,7 +63,7 @@ module Mongo
63
63
  # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
64
64
  # will be validated using an md5 hash. If validation fails, an exception will be raised.
65
65
  #
66
- # @return [Mongo::ObjectId] the file's id.
66
+ # @return [BSON::ObjectId] the file's id.
67
67
  def put(data, opts={})
68
68
  opts = opts.dup
69
69
  filename = opts[:filename]
@@ -16,11 +16,15 @@
16
16
  # limitations under the License.
17
17
  # ++
18
18
 
19
+ require 'sync'
20
+
19
21
  module Mongo
20
22
 
21
23
  # Instantiates and manages connections to a MongoDB replica set.
22
24
  class ReplSetConnection < Connection
23
- attr_reader :nodes, :secondaries, :arbiters, :read_pool, :secondary_pools
25
+ attr_reader :nodes, :secondaries, :arbiters, :secondary_pools,
26
+ :replica_set_name, :read_pool, :seeds, :tags_to_pools,
27
+ :refresh_interval, :refresh_mode
24
28
 
25
29
  # Create a connection to a MongoDB replica set.
26
30
  #
@@ -38,14 +42,30 @@ module Mongo
38
42
  # propogated to DB objects instantiated off of this Connection. This
39
43
  # default can be overridden upon instantiation of any DB by explicity setting a :safe value
40
44
  # on initialization.
41
- # @option options [Boolean] :read_secondary(false) If true, a random secondary node will be chosen,
42
- # and all reads will be directed to that node.
43
- # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
45
+ # @option options [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
46
+ # objects created from this connection object. If +:secondary+ is chosen, reads will be sent
47
+ # to one of the closest available secondary nodes. If a secondary node cannot be located, the
48
+ # read will be sent to the primary.
49
+ # @option options [Logger] :logger (nil) Logger instance to receive driver operation log.
44
50
  # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
45
51
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
46
- # @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
52
+ # @option options [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
47
53
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
48
54
  # Note: this setting is relevant only for multi-threaded applications.
55
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
56
+ # Disabled by default.
57
+ # @option opts [Float] :connect_timeout (nil) The number of seconds to wait before timing out a
58
+ # connection attempt.
59
+ # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
60
+ # @option opts [Boolean] :refresh_mode (:sync) Set this to :async to enable a background thread that
61
+ # periodically updates the state of the connection. If, for example, you initially connect while a secondary
62
+ # is down, this will reconnect to that secondary behind the scenes to
63
+ # prevent you from having to reconnect manually. If set to :sync, refresh will happen
64
+ # synchronously. If +false+, no automatic refresh will occur unless there's a connection failure.
65
+ # @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
66
+ # between calls to check the replica set's state.
67
+ # @option opts [Boolean] :require_primary (true) If true, require a primary node for the connection
68
+ # to succeed. Otherwise, connection will succeed as long as there's at least one secondary node.
49
69
  #
50
70
  # @example Connect to a replica set and provide two seed nodes. Note that the number of seed nodes does
51
71
  # not have to be equal to the number of replica set members. The purpose of seed nodes is to permit
@@ -63,6 +83,8 @@ module Mongo
63
83
  # @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
64
84
  # driver fails to connect to a replica set with that name.
65
85
  def initialize(*args)
86
+ extend Sync_m
87
+
66
88
  if args.last.is_a?(Hash)
67
89
  opts = args.pop
68
90
  else
@@ -70,70 +92,146 @@ module Mongo
70
92
  end
71
93
 
72
94
  unless args.length > 0
73
- raise MongoArgumentError, "A ReplSetConnection requires at least one node."
95
+ raise MongoArgumentError, "A ReplSetConnection requires at least one seed node."
74
96
  end
75
97
 
76
- # Get seed nodes
77
- @nodes = args
98
+ # The list of seed nodes
99
+ @seeds = args
78
100
 
79
- # Replica set name
80
- @replica_set = opts[:rs_name]
101
+ # TODO: get rid of this
102
+ @nodes = @seeds.dup
81
103
 
82
- # Cache the various node types when connecting to a replica set.
83
- @secondaries = []
84
- @arbiters = []
104
+ # The members of the replica set, stored as instances of Mongo::Node.
105
+ @members = []
106
+
107
+ # Connection pool for primary node
108
+ @primary = nil
109
+ @primary_pool = nil
85
110
 
86
111
  # Connection pools for each secondary node
112
+ @secondaries = []
87
113
  @secondary_pools = []
114
+
115
+ # The secondary pool to which we'll be sending reads.
116
+ # This may be identical to the primary pool.
88
117
  @read_pool = nil
89
118
 
119
+ # A list of arbiter addresses (for client information only)
120
+ @arbiters = []
121
+
122
+ # Refresh
123
+ @refresh_mode = opts.fetch(:refresh_mode, :sync)
124
+ @refresh_interval = opts[:refresh_interval] || 90
125
+
126
+ if ![:sync, :async, false].include?(@refresh_mode)
127
+ raise MongoArgumentError,
128
+ "Refresh mode must be one of :sync, :async, or false."
129
+ end
130
+
90
131
  # Are we allowing reads from secondaries?
91
- @read_secondary = opts.fetch(:read_secondary, false)
92
- @slave_okay = false
132
+ if opts[:read_secondary]
133
+ warn ":read_secondary options has now been deprecated and will " +
134
+ "be removed in driver v2.0. Use the :read option instead."
135
+ @read_secondary = opts.fetch(:read_secondary, false)
136
+ @read = :secondary
137
+ else
138
+ @read = opts.fetch(:read, :primary)
139
+ Mongo::Support.validate_read_preference(@read)
140
+ end
141
+
142
+ @connected = false
143
+
144
+ # Store the refresher thread
145
+ @refresh_thread = nil
146
+
147
+ # Maps
148
+ @sockets_to_pools = {}
149
+ @tags_to_pools = {}
150
+
151
+ # Replica set name
152
+ if opts[:rs_name]
153
+ warn ":rs_name option has been deprecated and will be removed in v2.0. " +
154
+ "Please use :name instead."
155
+ @replica_set_name = opts[:rs_name]
156
+ else
157
+ @replica_set_name = opts[:name]
158
+ end
159
+
160
+ # Require a primary node to connect?
161
+ @require_primary = opts.fetch(:require_primary, true)
93
162
 
94
163
  setup(opts)
95
164
  end
96
165
 
97
- # Create a new socket and attempt to connect to master.
98
- # If successful, sets host and port to master and returns the socket.
99
- #
100
- # If connecting to a replica set, this method will replace the
101
- # initially-provided seed list with any nodes known to the set.
102
- #
103
- # @raise [ConnectionFailure] if unable to connect to any host or port.
166
+ def inspect
167
+ "<Mongo::ReplSetConnection:0x#{self.object_id.to_s(16)} @seeds=#{@seeds} " +
168
+ "@connected=#{@connected}>"
169
+ end
170
+
171
+ # Initiate a connection to the replica set.
104
172
  def connect
105
- close
106
- @nodes_to_try = @nodes.clone
173
+ log(:info, "Connecting...")
174
+ sync_synchronize(:EX) do
175
+ return if @connected
176
+ manager = PoolManager.new(self, @seeds)
177
+ manager.connect
107
178
 
108
- while connecting?
109
- node = @nodes_to_try.shift
110
- config = check_is_master(node)
179
+ update_config(manager)
180
+ initiate_refresh_mode
111
181
 
112
- if is_primary?(config)
113
- set_primary(node)
182
+ if @require_primary && @primary.nil? #TODO: in v2.0, we'll let this be optional and do a lazy connect.
183
+ raise ConnectionFailure, "Failed to connect to primary node."
184
+ elsif !@read_pool
185
+ raise ConnectionFailure, "Failed to connect to any node."
114
186
  else
115
- set_auxillary(node, config)
187
+ @connected = true
116
188
  end
117
189
  end
190
+ end
118
191
 
119
- pick_secondary_for_read if @read_secondary
192
+ # Note: this method must be called from within
193
+ # an exclusive lock.
194
+ def update_config(manager)
195
+ @arbiters = manager.arbiters.nil? ? [] : manager.arbiters.dup
196
+ @primary = manager.primary.nil? ? nil : manager.primary.dup
197
+ @secondaries = manager.secondaries.dup
198
+ @hosts = manager.hosts.dup
199
+
200
+ @primary_pool = manager.primary_pool
201
+ @read_pool = manager.read_pool
202
+ @secondary_pools = manager.secondary_pools
203
+ @tags_to_pools = manager.tags_to_pools
204
+ @seeds = manager.seeds
205
+ @manager = manager
206
+ @nodes = manager.nodes
207
+ @max_bson_size = manager.max_bson_size
208
+ end
120
209
 
121
- if connected?
122
- BSON::BSON_CODER.update_max_bson_size(self)
123
- else
124
- if @secondary_pools.empty?
125
- close # close any existing pools and sockets
126
- raise ConnectionFailure, "Failed to connect any given host:port"
127
- else
128
- close # close any existing pools and sockets
129
- raise ConnectionFailure, "Failed to connect to primary node."
130
- end
210
+ # Refresh the current replica set configuration.
211
+ def refresh(opts={})
212
+ return false if !connected?
213
+
214
+ # Return if another thread is already in the process of refreshing.
215
+ return if sync_exclusive?
216
+
217
+ sync_synchronize(:EX) do
218
+ log(:info, "Refreshing...")
219
+ @background_manager ||= PoolManager.new(self, @seeds)
220
+ @background_manager.connect
221
+ update_config(@background_manager)
131
222
  end
223
+
224
+ return true
132
225
  end
133
- alias :reconnect :connect
134
226
 
227
+ def connected?
228
+ !@primary_pool.nil? || !@read_pool.nil?
229
+ end
230
+
231
+ # @deprecated
135
232
  def connecting?
136
- @nodes_to_try.length > 0
233
+ warn "ReplSetConnection#connecting? is deprecated and will be removed in v2.0."
234
+ false
137
235
  end
138
236
 
139
237
  # The replica set primary's host name.
@@ -150,27 +248,59 @@ module Mongo
150
248
  super
151
249
  end
152
250
 
251
+ def nodes
252
+ warn "ReplSetConnection#nodes is DEPRECATED and will be removed in v2.0. " +
253
+ "Please use ReplSetConnection#seeds instead."
254
+ @seeds
255
+ end
256
+
153
257
  # Determine whether we're reading from a primary node. If false,
154
258
  # this connection connects to a secondary node and @read_secondaries is true.
155
259
  #
156
260
  # @return [Boolean]
157
261
  def read_primary?
158
- !@read_pool
262
+ sync_synchronize(:SH) do
263
+ @read_pool == @primary_pool
264
+ end
159
265
  end
160
266
  alias :primary? :read_primary?
161
267
 
268
+ def read_preference
269
+ @read
270
+ end
271
+
162
272
  # Close the connection to the database.
163
273
  def close
164
- super
165
- @read_pool = nil
166
- @secondary_pools.each do |pool|
167
- pool.close
274
+ sync_synchronize(:EX) do
275
+ @connected = false
276
+ super
277
+
278
+ if @refresh_thread
279
+ @refresh_thread.kill
280
+ @refresh_thread = nil
281
+ end
282
+
283
+ if @nodes
284
+ @nodes.each do |member|
285
+ member.close
286
+ end
287
+ end
288
+
289
+ @nodes = []
290
+ @read_pool = nil
291
+
292
+ if @secondary_pools
293
+ @secondary_pools.each do |pool|
294
+ pool.close
295
+ end
296
+ end
297
+
298
+ @secondaries = []
299
+ @secondary_pools = []
300
+ @arbiters = []
301
+ @tags_to_pools.clear
302
+ @sockets_to_pools.clear
168
303
  end
169
- @secondaries = []
170
- @secondary_pools = []
171
- @arbiters = []
172
- @nodes_tried = []
173
- @nodes_to_try = []
174
304
  end
175
305
 
176
306
  # If a ConnectionFailure is raised, this method will be called
@@ -178,15 +308,19 @@ module Mongo
178
308
  # @deprecated
179
309
  def reset_connection
180
310
  close
181
- warn "ReplSetConnection#reset_connection is now deprecated. " +
311
+ warn "ReplSetConnection#reset_connection is now deprecated and will be removed in v2.0. " +
182
312
  "Use ReplSetConnection#close instead."
183
313
  end
184
314
 
185
- # Is it okay to connect to a slave?
315
+ # Returns +true+ if it's okay to read from a secondary node.
316
+ # Since this is a replica set, this must always be true.
186
317
  #
187
- # @return [Boolean]
318
+ # This method exist primarily so that Cursor objects will
319
+ # generate query messages with a slaveOkay value of +true+.
320
+ #
321
+ # @return [Boolean] +true+
188
322
  def slave_ok?
189
- @read_secondary || @slave_ok
323
+ true
190
324
  end
191
325
 
192
326
  def authenticate_pools
@@ -205,138 +339,121 @@ module Mongo
205
339
 
206
340
  private
207
341
 
208
- def check_is_master(node)
209
- begin
210
- host, port = *node
211
- socket = TCPSocket.new(host, port)
212
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
213
-
214
- config = self['admin'].command({:ismaster => 1}, :socket => socket)
215
-
216
- check_set_name(config, socket)
217
- rescue OperationFailure, SocketError, SystemCallError, IOError => ex
218
- # It's necessary to rescue here. The #connect method will keep trying
219
- # until it has no more nodes to try and raise a ConnectionFailure if
220
- # it can't connect to a primary.
221
- ensure
222
- socket.close if socket
223
- @nodes_tried << node
224
-
225
- if config
226
- nodes = []
227
- nodes += config['hosts'] if config['hosts']
228
- nodes += config['arbiters'] if config['arbiters']
229
- nodes += config['passives'] if config['passives']
230
- update_node_list(nodes)
231
-
232
- if config['msg'] && @logger
233
- @logger.warn("MONGODB #{config['msg']}")
342
+ def initiate_refresh_mode
343
+ if @refresh_mode == :async
344
+ return if @refresh_thread && @refresh_thread.alive?
345
+ @refresh_thread = Thread.new do
346
+ while true do
347
+ sleep(@refresh_interval)
348
+ refresh
234
349
  end
235
350
  end
236
351
  end
237
352
 
238
- config
239
- end
240
-
241
- # Primary, when connecting to a replica can, can only be a true primary node.
242
- # (And not a slave, which is possible when connecting with the standard
243
- # Connection class.
244
- def is_primary?(config)
245
- config && (config['ismaster'] == 1 || config['ismaster'] == true)
353
+ @last_refresh = Time.now
246
354
  end
247
355
 
248
- # Pick a node randomly from the set of possible secondaries.
249
- def pick_secondary_for_read
250
- if (size = @secondary_pools.size) > 0
251
- @read_pool = @secondary_pools[rand(size)]
252
- end
253
- end
356
+ # Checkout a socket for reading (i.e., a secondary node).
357
+ # Note that @read_pool might point to the primary pool
358
+ # if no read pool has been defined.
359
+ def checkout_reader
360
+ connect unless connected?
361
+ socket = get_socket_from_pool(@read_pool)
254
362
 
255
- # Make sure that we're connected to the expected replica set.
256
- def check_set_name(config, socket)
257
- if @replica_set
258
- config = self['admin'].command({:replSetGetStatus => 1},
259
- :socket => socket, :check_response => false)
260
-
261
- if !Mongo::Support.ok?(config)
262
- raise ReplicaSetConnectionError, config['errmsg']
263
- elsif config['set'] != @replica_set
264
- raise ReplicaSetConnectionError,
265
- "Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set}'"
266
- end
363
+ if !socket
364
+ refresh
365
+ socket = get_socket_from_pool(@primary_pool)
267
366
  end
268
- end
269
367
 
270
- # Determines what kind of node we have and caches its host
271
- # and port so that users can easily connect manually.
272
- def set_auxillary(node, config)
273
- if config
274
- if config['secondary']
275
- host, port = *node
276
- @secondaries << node unless @secondaries.include?(node)
277
- @secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
278
- elsif config['arbiterOnly']
279
- @arbiters << node unless @arbiters.include?(node)
280
- end
368
+ if socket
369
+ socket
370
+ else
371
+ raise ConnectionFailure.new("Could not connect to a node for reading.")
281
372
  end
282
373
  end
283
374
 
284
- # Update the list of known nodes. Only applies to replica sets,
285
- # where the response to the ismaster command will return a list
286
- # of known hosts.
375
+ # Checkout a socket connected to a node with one of
376
+ # the provided tags. If no such node exists, raise
377
+ # an exception.
287
378
  #
288
- # @param hosts [Array] a list of hosts, specified as string-encoded
289
- # host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"]
290
- #
291
- # @return [Array] the updated list of nodes
292
- def update_node_list(hosts)
293
- new_nodes = hosts.map do |host|
294
- if !host.respond_to?(:split)
295
- warn "Could not parse host #{host.inspect}."
296
- next
379
+ # NOTE: will be available in driver release v2.0.
380
+ def checkout_tagged(tags)
381
+ sync_synchronize(:SH) do
382
+ tags.each do |k, v|
383
+ pool = @tags_to_pools[{k.to_s => v}]
384
+ if pool
385
+ socket = pool.checkout
386
+ @sockets_to_pools[socket] = pool
387
+ return socket
388
+ end
297
389
  end
298
-
299
- host, port = host.split(':')
300
- [host, port ? port.to_i : Connection::DEFAULT_PORT]
301
390
  end
302
391
 
303
- # Replace the list of seed nodes with the canonical list.
304
- @nodes = new_nodes.clone
305
-
306
- @nodes_to_try = new_nodes - @nodes_tried
392
+ raise NodeWithTagsNotFound,
393
+ "Could not find a connection tagged with #{tags}."
307
394
  end
308
395
 
309
- # Checkout a socket for reading (i.e., a secondary node).
310
- def checkout_reader
396
+ # Checkout a socket for writing (i.e., a primary node).
397
+ def checkout_writer
311
398
  connect unless connected?
399
+ socket = get_socket_from_pool(@primary_pool)
400
+
401
+ if !socket
402
+ refresh
403
+ socket = get_socket_from_pool(@primary_pool)
404
+ end
312
405
 
313
- if @read_pool
314
- @read_pool.checkout
406
+ if socket
407
+ socket
315
408
  else
316
- checkout_writer
409
+ raise ConnectionFailure.new("Could not connect to primary node.")
317
410
  end
318
411
  end
319
412
 
320
- # Checkout a socket for writing (i.e., a primary node).
321
- def checkout_writer
322
- connect unless connected?
413
+ def get_socket_from_pool(pool)
414
+ begin
415
+ sync_synchronize(:SH) do
416
+ if pool
417
+ socket = pool.checkout
418
+ @sockets_to_pools[socket] = pool
419
+ socket
420
+ end
421
+ end
323
422
 
324
- @primary_pool.checkout
423
+ rescue ConnectionFailure => ex
424
+ log(:info, "Failed to checkout from #{pool} with #{ex.class}; #{ex.message}")
425
+ return nil
426
+ end
325
427
  end
326
428
 
327
429
  # Checkin a socket used for reading.
328
430
  def checkin_reader(socket)
329
- if @read_pool
330
- @read_pool.checkin(socket)
331
- else
332
- checkin_writer(socket)
333
- end
431
+ warn "ReplSetConnection#checkin_writer is deprecated and will be removed " +
432
+ "in driver v2.0. Use ReplSetConnection#checkin instead."
433
+ checkin(socket)
334
434
  end
335
435
 
336
436
  # Checkin a socket used for writing.
337
437
  def checkin_writer(socket)
338
- if @primary_pool
339
- @primary_pool.checkin(socket)
438
+ warn "ReplSetConnection#checkin_writer is deprecated and will be removed " +
439
+ "in driver v2.0. Use ReplSetConnection#checkin instead."
440
+ checkin(socket)
441
+ end
442
+
443
+ def checkin(socket)
444
+ sync_synchronize(:SH) do
445
+ if pool = @sockets_to_pools[socket]
446
+ pool.checkin(socket)
447
+ elsif socket
448
+ socket.close
449
+ end
450
+ end
451
+
452
+ # Refresh synchronously every @refresh_interval seconds
453
+ # if synchronous refresh mode is enabled.
454
+ if @refresh_mode == :sync &&
455
+ ((Time.now - @last_refresh) > @refresh_interval)
456
+ refresh
340
457
  end
341
458
  end
342
459
  end