mongo 1.3.1 → 1.4.0

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