mongo 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -74,6 +74,8 @@ module Mongo
74
74
  @db, @name = db, name
75
75
  @connection = @db.connection
76
76
  @logger = @connection.logger
77
+ @cache_time = @db.cache_time
78
+ @cache = Hash.new(0)
77
79
  unless pk_factory
78
80
  @safe = options.has_key?(:safe) ? options[:safe] : @db.safe
79
81
  end
@@ -243,10 +245,10 @@ module Mongo
243
245
  def save(doc, opts={})
244
246
  if doc.has_key?(:_id) || doc.has_key?('_id')
245
247
  id = doc[:_id] || doc['_id']
246
- update({:_id => id}, doc, :upsert => true, :safe => opts[:safe])
248
+ update({:_id => id}, doc, :upsert => true, :safe => opts.fetch(:safe, @safe))
247
249
  id
248
250
  else
249
- insert(doc, :safe => opts[:safe])
251
+ insert(doc, :safe => opts.fetch(:safe, @safe))
250
252
  end
251
253
  end
252
254
 
@@ -256,8 +258,7 @@ module Mongo
256
258
  # a document (as a hash) or array of documents to be inserted.
257
259
  #
258
260
  # @return [ObjectId, Array]
259
- # the _id of the inserted document or a list of _ids of all inserted documents.
260
- # Note: the object may have been modified by the database's PK factory, if it has one.
261
+ # The _id of the inserted document or a list of _ids of all inserted documents.
261
262
  #
262
263
  # @option opts [Boolean, Hash] :safe (+false+)
263
264
  # run the operation in safe mode, which run a getlasterror command on the
@@ -298,7 +299,8 @@ module Mongo
298
299
  # @example remove only documents that have expired:
299
300
  # users.remove({:expire => {"$lte" => Time.now}})
300
301
  #
301
- # @return [True]
302
+ # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
303
+ # Otherwise, returns true.
302
304
  #
303
305
  # @raise [Mongo::OperationFailure] an exception will be raised iff safe mode is enabled
304
306
  # and the operation fails.
@@ -317,15 +319,12 @@ module Mongo
317
319
  @logger.debug("MONGODB #{@db.name}['#{@name}'].remove(#{selector.inspect})") if @logger
318
320
  if safe
319
321
  @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
320
- # the return value of send_message_with_safe_check isn't actually meaningful --
321
- # only the fact that it didn't raise an error is -- so just return true
322
- true
323
322
  else
324
323
  @connection.send_message(Mongo::Constants::OP_DELETE, message)
325
324
  end
326
325
  end
327
326
 
328
- # Update a single document in this collection.
327
+ # Update one or more documents in this collection.
329
328
  #
330
329
  # @param [Hash] selector
331
330
  # a hash specifying elements which must be present for a document to be updated. Note:
@@ -346,6 +345,9 @@ module Mongo
346
345
  # options set on this collection, its database object, or the current collection.
347
346
  # See the options for DB#get_last_error for details.
348
347
  #
348
+ # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode.
349
+ # Otherwise, returns true.
350
+ #
349
351
  # @core update update-instance_method
350
352
  def update(selector, document, options={})
351
353
  # Initial byte is 0.
@@ -380,6 +382,9 @@ module Mongo
380
382
  # Also note that it is permissible to create compound indexes that include a geospatial index as
381
383
  # long as the geospatial index comes first.
382
384
  #
385
+ # If your code calls create_index frequently, you can use Collection#ensure_index to cache these calls
386
+ # and thereby prevent excessive round trips to the database.
387
+ #
383
388
  # @option opts [Boolean] :unique (false) if true, this index will enforce a uniqueness constraint.
384
389
  # @option opts [Boolean] :background (false) indicate that the index should be built in the background. This
385
390
  # feature is only available in MongoDB >= 1.3.2.
@@ -407,43 +412,45 @@ module Mongo
407
412
  # @core indexes create_index-instance_method
408
413
  def create_index(spec, opts={})
409
414
  opts[:dropDups] = opts.delete(:drop_dups) if opts[:drop_dups]
410
- field_spec = BSON::OrderedHash.new
411
- if spec.is_a?(String) || spec.is_a?(Symbol)
412
- field_spec[spec.to_s] = 1
413
- elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
414
- spec.each do |f|
415
- if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
416
- field_spec[f[0].to_s] = f[1]
417
- else
418
- raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
419
- "should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
420
- end
421
- end
422
- else
423
- raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
424
- "should be either a string, symbol, or an array of arrays."
425
- end
426
-
415
+ field_spec = parse_index_spec(spec)
427
416
  name = opts.delete(:name) || generate_index_name(field_spec)
428
417
 
429
- selector = {
430
- :name => name,
431
- :ns => "#{@db.name}.#{@name}",
432
- :key => field_spec
433
- }
434
- selector.merge!(opts)
435
-
436
- insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, false)
437
- response = @db.get_last_error
418
+ generate_indexes(field_spec, name, opts)
419
+ name
420
+ end
438
421
 
439
- if response['err']
440
- if response['code'] == 11000 && selector[:dropDups]
441
- # NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
442
- else
443
- raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
444
- "#{response['err']}"
445
- end
422
+ # Calls create_index and sets a flag to not do so again for another X minutes.
423
+ # this time can be specified as an option when initializing a Mongo::DB object as options[:cache_time]
424
+ # Any changes to an index will be propogated through regardless of cache time (e.g., a change of index direction)
425
+ #
426
+ # The parameters and options for this methods are the same as those for Collection#create_index.
427
+ #
428
+ # @example Call sequence:
429
+ # Time t: @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
430
+ # sets the 5 minute cache
431
+ # Time t+2min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- doesn't do anything
432
+ # Time t+3min : @posts.ensure_index([['something_else', Mongo::ASCENDING]) -- calls create_index
433
+ # and sets 5 minute cache
434
+ # Time t+10min : @posts.ensure_index([['subject', Mongo::ASCENDING]) -- calls create_index and
435
+ # resets the 5 minute counter
436
+ #
437
+ # @return [String] the name of the index.
438
+ def ensure_index(spec, opts={})
439
+ valid = BSON::OrderedHash.new
440
+ now = Time.now.utc.to_i
441
+ field_spec = parse_index_spec(spec)
442
+
443
+ field_spec.each do |key, value|
444
+ cache_key = generate_index_name({key => value})
445
+ timeout = @cache[cache_key] || 0
446
+ valid[key] = value if timeout <= now
446
447
  end
448
+
449
+ name = opts.delete(:name) || generate_index_name(valid)
450
+ generate_indexes(valid, name, opts) if valid.any?
451
+
452
+ # Reset the cache here in case there are any errors inserting. Best to be safe.
453
+ name.each {|n| @cache[n] = now + @cache_time}
447
454
  name
448
455
  end
449
456
 
@@ -453,6 +460,7 @@ module Mongo
453
460
  #
454
461
  # @core indexes
455
462
  def drop_index(name)
463
+ @cache[name] = nil
456
464
  @db.drop_index(@name, name)
457
465
  end
458
466
 
@@ -460,6 +468,7 @@ module Mongo
460
468
  #
461
469
  # @core indexes
462
470
  def drop_indexes
471
+ @cache = {}
463
472
 
464
473
  # Note: calling drop_indexes with no args will drop them all.
465
474
  @db.drop_index(@name, '*')
@@ -470,7 +479,6 @@ module Mongo
470
479
  @db.drop_collection(@name)
471
480
  end
472
481
 
473
-
474
482
  # Atomically update and return a document using MongoDB's findAndModify command. (MongoDB > 1.3.0)
475
483
  #
476
484
  # @option opts [Hash] :query ({}) a query selector document for matching the desired document.
@@ -630,10 +638,12 @@ module Mongo
630
638
  # Rename this collection.
631
639
  #
632
640
  # Note: If operating in auth mode, the client must be authorized as an admin to
633
- # perform this operation.
641
+ # perform this operation.
634
642
  #
635
643
  # @param [String] new_name the new name for this collection
636
644
  #
645
+ # @return [String] the name of the new collection.
646
+ #
637
647
  # @raise [Mongo::InvalidNSName] if +new_name+ is an invalid collection name.
638
648
  def rename(new_name)
639
649
  case new_name
@@ -655,6 +665,7 @@ module Mongo
655
665
  end
656
666
 
657
667
  @db.rename_collection(@name, new_name)
668
+ @name = new_name
658
669
  end
659
670
 
660
671
  # Get information on the indexes for this collection.
@@ -709,6 +720,49 @@ module Mongo
709
720
 
710
721
  private
711
722
 
723
+ def parse_index_spec(spec)
724
+ field_spec = BSON::OrderedHash.new
725
+ if spec.is_a?(String) || spec.is_a?(Symbol)
726
+ field_spec[spec.to_s] = 1
727
+ elsif spec.is_a?(Array) && spec.all? {|field| field.is_a?(Array) }
728
+ spec.each do |f|
729
+ if [Mongo::ASCENDING, Mongo::DESCENDING, Mongo::GEO2D].include?(f[1])
730
+ field_spec[f[0].to_s] = f[1]
731
+ else
732
+ raise MongoArgumentError, "Invalid index field #{f[1].inspect}; " +
733
+ "should be one of Mongo::ASCENDING (1), Mongo::DESCENDING (-1) or Mongo::GEO2D ('2d')."
734
+ end
735
+ end
736
+ else
737
+ raise MongoArgumentError, "Invalid index specification #{spec.inspect}; " +
738
+ "should be either a string, symbol, or an array of arrays."
739
+ end
740
+ field_spec
741
+ end
742
+
743
+ def generate_indexes(field_spec, name, opts)
744
+ selector = {
745
+ :name => name,
746
+ :ns => "#{@db.name}.#{@name}",
747
+ :key => field_spec
748
+ }
749
+ selector.merge!(opts)
750
+
751
+ begin
752
+ insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true)
753
+
754
+ rescue Mongo::OperationFailure => e
755
+ if selector[:dropDups] && e.message =~ /^11000/
756
+ # NOP. If the user is intentionally dropping dups, we can ignore duplicate key errors.
757
+ else
758
+ raise Mongo::OperationFailure, "Failed to create index #{selector.inspect} with the following error: " +
759
+ "#{e.message}"
760
+ end
761
+ end
762
+
763
+ nil
764
+ end
765
+
712
766
  # Sends a Mongo::Constants::OP_INSERT message to the database.
713
767
  # Takes an array of +documents+, an optional +collection_name+, and a
714
768
  # +check_keys+ setting.
@@ -38,21 +38,19 @@ module Mongo
38
38
  MONGODB_URI_MATCHER = /(([-_.\w\d]+):([-_\w\d]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
39
39
  MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
40
40
 
41
- attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out, :primary, :secondaries, :arbiters,
42
- :safe
41
+ attr_reader :logger, :size, :nodes, :auths, :primary, :secondaries, :arbiters,
42
+ :safe, :primary_pool, :read_pool, :secondary_pools
43
43
 
44
44
  # Counter for generating unique request ids.
45
45
  @@current_request_id = 0
46
46
 
47
- # Create a connection to MongoDB.
47
+ # Create a connection to single MongoDB instance.
48
48
  #
49
- # If connecting to just one server, you may specify whether connection to slave is permitted.
49
+ # You may specify whether connection to slave is permitted.
50
50
  # In all cases, the default host is "localhost" and the default port is 27017.
51
51
  #
52
- # To specify more than one host pair to be used as seeds in a replica set
53
- # or replica pair, use Connection.multi. If you're only specifying one node in the
54
- # replica set, you can use Connection.new, as any other host known to the set will be
55
- # cached.
52
+ # To specify more than one host pair to be used as seeds in a replica set,
53
+ # use Connection.multi.
56
54
  #
57
55
  # Once connected to a replica set, you can find out which nodes are primary, secondary, and
58
56
  # arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
@@ -69,12 +67,11 @@ module Mongo
69
67
  # @option options [Boolean] :slave_ok (false) Must be set to +true+ when connecting
70
68
  # to a single, slave node.
71
69
  # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
72
- # @option options [String] :name (nil) The name of the replica set to connect to. An exception will be
73
- # raised if unable to connect to a replica set with this name.
74
- # @option options [Integer] :pool_size (1) The maximum number of socket connections that can be
75
- # opened to the database.
76
- # @option options [Float] :timeout (5.0) When all of the connections to the pool are checked out,
70
+ # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
71
+ # connection pool. Note: this setting is relevant only for multi-threaded applications.
72
+ # @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
77
73
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
74
+ # Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
78
75
  #
79
76
  # @example localhost, 27017
80
77
  # Connection.new
@@ -88,7 +85,7 @@ module Mongo
88
85
  # @example localhost, 3000, where this node may be a slave
89
86
  # Connection.new("localhost", 3000, :slave_ok => true)
90
87
  #
91
- # @see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby Replica pairs in Ruby
88
+ # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
92
89
  #
93
90
  # @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
94
91
  # driver fails to connect to a replica set with that name.
@@ -107,13 +104,13 @@ module Mongo
107
104
  @host = @port = nil
108
105
 
109
106
  # Replica set name
110
- @replica_set_name = options[:name]
107
+ @replica_set_name = options[:rs_name]
111
108
 
112
109
  # Lock for request ids.
113
110
  @id_lock = Mutex.new
114
111
 
115
112
  # Pool size and timeout.
116
- @size = options[:pool_size] || 1
113
+ @pool_size = options[:pool_size] || 1
117
114
  @timeout = options[:timeout] || 5.0
118
115
 
119
116
  # Mutex for synchronizing pool access
@@ -129,15 +126,8 @@ module Mongo
129
126
  # Condition variable for signal and wait
130
127
  @queue = ConditionVariable.new
131
128
 
132
- @sockets = []
133
- @checked_out = []
134
-
135
129
  # slave_ok can be true only if one node is specified
136
- if @nodes.length > 1 && options[:slave_ok]
137
- raise MongoArgumentError, "Can't specify more than one node when :slave_ok is true."
138
- else
139
- @slave_ok = options[:slave_ok]
140
- end
130
+ @slave_ok = options[:slave_ok]
141
131
 
142
132
  # Cache the various node types
143
133
  # when connecting to a replica set.
@@ -145,8 +135,14 @@ module Mongo
145
135
  @secondaries = []
146
136
  @arbiters = []
147
137
 
138
+ # Connection pool for primay node
139
+ @primary_pool = nil
140
+
141
+ # Connection pools for each secondary node
142
+ @secondary_pools = []
143
+ @read_pool = nil
144
+
148
145
  @logger = options[:logger] || nil
149
- @options = options
150
146
 
151
147
  should_connect = options.fetch(:connect, true)
152
148
  connect if should_connect
@@ -154,30 +150,37 @@ module Mongo
154
150
 
155
151
  # Initialize a connection to a MongoDB replica set using an array of seed nodes.
156
152
  #
157
- # Note that, even when connecting to a replica set, you can use Connection.new specifying
158
- # just a single node. If the replica set is up, the remaining nodes in the set will be cached
159
- # for failover.
153
+ # The seed nodes specified will be used on the initial connection to the replica set, but note
154
+ # that this list of nodes will be replced by the list of canonical nodes returned by running the
155
+ # is_master command on the replica set.
160
156
  #
161
157
  # @param nodes [Array] An array of arrays, each of which specifies a host and port.
162
- # @param opts Takes the same options as Connection.new
158
+ # @param opts [Hash] Any of the available options that can be passed to Connection.new.
163
159
  #
164
- # @example
165
- # Connection.multi([["db1.example.com", 27017],
166
- # ["db2.example.com", 27017]])
160
+ # @option options [String] :rs_name (nil) The name of the replica set to connect to. An exception will be
161
+ # raised if unable to connect to a replica set with this name.
162
+ # @option options [Boolean] :read_secondary (false) When true, this connection object will pick a random slave
163
+ # to send reads to.
167
164
  #
168
165
  # @example
166
+ # Connection.multi([["db1.example.com", 27017], ["db2.example.com", 27017]])
167
+ #
168
+ # @example This connection will read from a random secondary node.
169
169
  # Connection.multi([["db1.example.com", 27017], ["db2.example.com", 27017], ["db3.example.com", 27017]],
170
- # :pool_size => 20, :timeout => 5)
170
+ # :read_secondary => true)
171
171
  #
172
172
  # @return [Mongo::Connection]
173
173
  def self.multi(nodes, opts={})
174
174
  unless nodes.length > 0 && nodes.all? {|n| n.is_a? Array}
175
175
  raise MongoArgumentError, "Connection.multi requires at least one node to be specified."
176
176
  end
177
+
177
178
  # Block returns an array, the first element being an array of nodes and the second an array
178
179
  # of authorizations for the database.
179
180
  new(nil, nil, opts) do |con|
180
181
  nodes.map do |node|
182
+ con.instance_variable_set(:@replica_set, true)
183
+ con.instance_variable_set(:@read_secondary, true) if opts[:read_secondary]
181
184
  con.pair_val_to_connection(node)
182
185
  end
183
186
  end
@@ -389,12 +392,9 @@ module Mongo
389
392
  #
390
393
  # @return [Boolean]
391
394
  def slave_ok?
392
- @slave_ok
395
+ @read_secondary || @slave_ok
393
396
  end
394
397
 
395
-
396
- ## Connections and pooling ##
397
-
398
398
  # Send a message to MongoDB, adding the necessary headers.
399
399
  #
400
400
  # @param [Integer] operation a MongoDB opcode.
@@ -404,10 +404,10 @@ module Mongo
404
404
  def send_message(operation, message, log_message=nil)
405
405
  begin
406
406
  packed_message = add_message_headers(operation, message).to_s
407
- socket = checkout
407
+ socket = checkout_writer
408
408
  send_message_on_socket(packed_message, socket)
409
409
  ensure
410
- checkin(socket)
410
+ checkin_writer(socket)
411
411
  end
412
412
  end
413
413
 
@@ -422,14 +422,12 @@ module Mongo
422
422
  #
423
423
  # @see DB#get_last_error for valid last error params.
424
424
  #
425
- # @return [Array]
426
- # An array whose indexes include [0] documents returned, [1] number of document received,
427
- # and [3] a cursor_id.
425
+ # @return [Hash] The document returned by the call to getlasterror.
428
426
  def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false)
429
427
  message_with_headers = add_message_headers(operation, message)
430
428
  message_with_check = last_error_message(db_name, last_error_params)
431
429
  begin
432
- sock = checkout
430
+ sock = checkout_writer
433
431
  packed_message = message_with_headers.append!(message_with_check).to_s
434
432
  docs = num_received = cursor_id = ''
435
433
  @safe_mutexes[sock].synchronize do
@@ -437,13 +435,15 @@ module Mongo
437
435
  docs, num_received, cursor_id = receive(sock)
438
436
  end
439
437
  ensure
440
- checkin(sock)
438
+ checkin_writer(sock)
441
439
  end
440
+
442
441
  if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
443
442
  close if error == "not master"
444
- raise Mongo::OperationFailure, error
443
+ raise Mongo::OperationFailure, docs[0]['code'].to_s + ': ' + error
445
444
  end
446
- [docs, num_received, cursor_id]
445
+
446
+ docs[0]
447
447
  end
448
448
 
449
449
  # Sends a message to the database and waits for the response.
@@ -455,10 +455,10 @@ module Mongo
455
455
  # @return [Array]
456
456
  # An array whose indexes include [0] documents returned, [1] number of document received,
457
457
  # and [3] a cursor_id.
458
- def receive_message(operation, message, log_message=nil, socket=nil)
458
+ def receive_message(operation, message, log_message=nil, socket=nil, command=false)
459
459
  packed_message = add_message_headers(operation, message).to_s
460
460
  begin
461
- sock = socket || checkout
461
+ sock = socket || (command ? checkout_writer : checkout_reader)
462
462
 
463
463
  result = ''
464
464
  @safe_mutexes[sock].synchronize do
@@ -466,7 +466,7 @@ module Mongo
466
466
  result = receive(sock)
467
467
  end
468
468
  ensure
469
- checkin(sock)
469
+ command ? checkin_writer(sock) : checkin_reader(sock)
470
470
  end
471
471
  result
472
472
  end
@@ -480,33 +480,43 @@ module Mongo
480
480
  # @raise [ConnectionFailure] if unable to connect to any host or port.
481
481
  def connect
482
482
  reset_connection
483
+ @nodes_to_try = @nodes.clone
483
484
 
484
- while !connected? && !(nodes_to_try = @nodes - @nodes_tried).empty?
485
- nodes_to_try.each do |node|
486
- config = check_is_master(node)
487
- if is_primary?(config)
488
- set_primary(node)
489
- else
490
- set_auxillary(node, config)
491
- end
485
+ while connecting?
486
+ node = @nodes_to_try.shift
487
+ config = check_is_master(node)
488
+
489
+ if is_primary?(config)
490
+ set_primary(node)
491
+ else
492
+ set_auxillary(node, config)
492
493
  end
493
494
  end
494
495
 
496
+ pick_secondary_for_read
497
+
495
498
  raise ConnectionFailure, "failed to connect to any given host:port" unless connected?
496
499
  end
497
500
 
501
+ def connecting?
502
+ !(connected? && @nodes_to_try.empty?)
503
+ end
504
+
505
+ # It's possible that we defined connected as all nodes being connected???
506
+ # NOTE: Do check if this needs to be more stringent.
507
+ # Probably not since if any node raises a connection failure, all nodes will be closed.
498
508
  def connected?
499
- @host && @port
509
+ @primary_pool && @primary_pool.host && @primary_pool.port
500
510
  end
501
511
 
502
512
  # Close the connection to the database.
503
513
  def close
504
- @sockets.each do |sock|
505
- sock.close
514
+ @primary_pool.close if @primary_pool
515
+ @primary_pool = nil
516
+ @read_pool = nil
517
+ @secondary_pools.each do |pool|
518
+ pool.close
506
519
  end
507
- @host = @port = nil
508
- @sockets.clear
509
- @checked_out.clear
510
520
  end
511
521
 
512
522
  ## Configuration helper methods
@@ -583,18 +593,59 @@ module Mongo
583
593
  nodes
584
594
  end
585
595
 
596
+ # Checkout a socket for reading (i.e., a secondary node).
597
+ def checkout_reader
598
+ connect unless connected?
599
+
600
+ if @read_pool
601
+ @read_pool.checkout
602
+ else
603
+ checkout_writer
604
+ end
605
+ end
606
+
607
+ # Checkout a socket for writing (i.e., a primary node).
608
+ def checkout_writer
609
+ connect unless connected?
610
+
611
+ @primary_pool.checkout
612
+ end
613
+
614
+ # Checkin a socket used for reading.
615
+ def checkin_reader(socket)
616
+ if @read_pool
617
+ @read_pool.checkin(socket)
618
+ else
619
+ checkin_writer(socket)
620
+ end
621
+ end
622
+
623
+ # Checkin a socket used for writing.
624
+ def checkin_writer(socket)
625
+ if @primary_pool
626
+ @primary_pool.checkin(socket)
627
+ end
628
+ end
629
+
586
630
  private
587
631
 
632
+ # Pick a node randomly from the set of possibly secondaries.
633
+ def pick_secondary_for_read
634
+ if (size = @secondary_pools.size) > 1
635
+ @read_pool = @secondary_pools[rand(size)]
636
+ end
637
+ end
638
+
588
639
  # If a ConnectionFailure is raised, this method will be called
589
640
  # to close the connection and reset connection values.
590
641
  def reset_connection
591
642
  close
592
- @host = nil
593
- @port = nil
594
643
  @primary = nil
595
- @secondaries = []
596
- @arbiters = []
597
- @nodes_tried = []
644
+ @secondaries = []
645
+ @secondary_pools = []
646
+ @arbiters = []
647
+ @nodes_tried = []
648
+ @nodes_to_try = []
598
649
  end
599
650
 
600
651
  # Primary is defined as either a master node or a slave if
@@ -603,7 +654,7 @@ module Mongo
603
654
  # If a primary node is discovered, we set the the @host and @port and
604
655
  # apply any saved authentication.
605
656
  def is_primary?(config)
606
- config && (config['ismaster'] == 1 || config['ismaster'] == true) || @slave_ok
657
+ config && (config['ismaster'] == 1 || config['ismaster'] == true) || !@replica_set && @slave_ok
607
658
  end
608
659
 
609
660
  def check_is_master(node)
@@ -621,8 +672,9 @@ module Mongo
621
672
  @nodes_tried << node
622
673
  if config
623
674
  update_node_list(config['hosts']) if config['hosts']
624
- if @logger
625
- @logger.warn("MONGODB #{config['msg']}") if config['msg']
675
+
676
+ if config['msg'] && @logger
677
+ @logger.warn("MONGODB #{config['msg']}")
626
678
  end
627
679
  end
628
680
 
@@ -650,8 +702,9 @@ module Mongo
650
702
  # Set the specified node as primary, and
651
703
  # apply any saved authentication credentials.
652
704
  def set_primary(node)
653
- @host, @port = *node
654
- @primary = [@host, @port]
705
+ host, port = *node
706
+ @primary = [host, port]
707
+ @primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
655
708
  apply_saved_authentication
656
709
  end
657
710
 
@@ -660,7 +713,11 @@ module Mongo
660
713
  def set_auxillary(node, config)
661
714
  if config
662
715
  if config['secondary']
716
+ host, port = *node
663
717
  @secondaries << node unless @secondaries.include?(node)
718
+ if @read_secondary
719
+ @secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
720
+ end
664
721
  elsif config['arbiterOnly']
665
722
  @arbiters << node unless @arbiters.include?(node)
666
723
  end
@@ -686,73 +743,10 @@ module Mongo
686
743
  [host, port.to_i]
687
744
  end
688
745
 
689
- @nodes |= new_nodes
690
- end
746
+ # Replace the list of seed nodes with the canonical list.
747
+ @nodes = new_nodes.clone
691
748
 
692
- # Return a socket to the pool.
693
- def checkin(socket)
694
- @connection_mutex.synchronize do
695
- @checked_out.delete(socket)
696
- @queue.signal
697
- end
698
- true
699
- end
700
-
701
- # Adds a new socket to the pool and checks it out.
702
- #
703
- # This method is called exclusively from #checkout;
704
- # therefore, it runs within a mutex.
705
- def checkout_new_socket
706
- begin
707
- socket = TCPSocket.new(@host, @port)
708
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
709
- rescue => ex
710
- raise ConnectionFailure, "Failed to connect socket: #{ex}"
711
- end
712
- @sockets << socket
713
- @checked_out << socket
714
- socket
715
- end
716
-
717
- # Checks out the first available socket from the pool.
718
- #
719
- # This method is called exclusively from #checkout;
720
- # therefore, it runs within a mutex.
721
- def checkout_existing_socket
722
- socket = (@sockets - @checked_out).first
723
- @checked_out << socket
724
- socket
725
- end
726
-
727
- # Check out an existing socket or create a new socket if the maximum
728
- # pool size has not been exceeded. Otherwise, wait for the next
729
- # available socket.
730
- def checkout
731
- connect if !connected?
732
- start_time = Time.now
733
- loop do
734
- if (Time.now - start_time) > @timeout
735
- raise ConnectionTimeoutError, "could not obtain connection within " +
736
- "#{@timeout} seconds. The max pool size is currently #{@size}; " +
737
- "consider increasing the pool size or timeout."
738
- end
739
-
740
- @connection_mutex.synchronize do
741
- socket = if @checked_out.size < @sockets.size
742
- checkout_existing_socket
743
- elsif @sockets.size < @size
744
- checkout_new_socket
745
- end
746
-
747
- return socket if socket
748
-
749
- # Otherwise, wait
750
- if @logger
751
- @logger.warn "MONGODB Waiting for available connection; #{@checked_out.size} of #{@size} connections checked out."
752
- end
753
- @queue.wait(@connection_mutex)
754
- end
755
- end
749
+ @nodes_to_try = new_nodes - @nodes_tried
756
750
  end
757
751
 
758
752
  def receive(sock)
@@ -842,17 +836,17 @@ module Mongo
842
836
  headers = [
843
837
  # Message size.
844
838
  16 + message.size,
845
-
839
+
846
840
  # Unique request id.
847
841
  get_request_id,
848
-
842
+
849
843
  # Response id.
850
844
  0,
851
-
845
+
852
846
  # Opcode.
853
847
  operation
854
848
  ].pack('VVVV')
855
-
849
+
856
850
  message.prepend!(headers)
857
851
  end
858
852
 
@@ -898,7 +892,7 @@ module Mongo
898
892
  end
899
893
  message
900
894
  end
901
-
895
+
902
896
  # Low-level data for receiving data from socket.
903
897
  # Unlike #receive_message_on_socket, this method immediately discards the data
904
898
  # and only returns the number of bytes read.
@@ -921,10 +915,10 @@ module Mongo
921
915
  end
922
916
  bytes_read
923
917
  end
924
-
918
+
925
919
  if defined?(Encoding)
926
920
  BINARY_ENCODING = Encoding.find("binary")
927
-
921
+
928
922
  def new_binary_string
929
923
  "".force_encoding(BINARY_ENCODING)
930
924
  end