mongo 1.1.2 → 1.1.3

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.
@@ -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