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.
- data/README.md +8 -4
- data/Rakefile +2 -2
- data/docs/FAQ.md +112 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +12 -0
- data/docs/REPLICA_SETS.md +77 -0
- data/docs/TUTORIAL.md +1 -1
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo.rb +2 -1
- data/lib/mongo/collection.rb +98 -44
- data/lib/mongo/connection.rb +140 -146
- data/lib/mongo/cursor.rb +27 -15
- data/lib/mongo/db.rb +5 -0
- data/lib/mongo/gridfs/grid.rb +1 -1
- data/lib/mongo/util/pool.rb +15 -3
- data/test/bson/object_id_test.rb +5 -0
- data/test/collection_test.rb +70 -1
- data/test/connection_test.rb +10 -16
- data/test/cursor_test.rb +4 -8
- data/test/db_api_test.rb +5 -17
- data/test/replica_sets/query_secondaries.rb +40 -0
- data/test/replica_sets/query_test.rb +1 -1
- data/test/rs.rb +20 -0
- data/test/safe_test.rb +27 -1
- data/test/unit/collection_test.rb +49 -0
- data/test/unit/connection_test.rb +24 -21
- data/test/unit/pool_test.rb +9 -0
- metadata +95 -85
data/lib/mongo/collection.rb
CHANGED
@@ -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
|
248
|
+
update({:_id => id}, doc, :upsert => true, :safe => opts.fetch(:safe, @safe))
|
247
249
|
id
|
248
250
|
else
|
249
|
-
insert(doc, :safe => opts
|
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
|
-
#
|
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 [
|
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
|
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 =
|
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
|
-
|
430
|
-
|
431
|
-
|
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
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
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.
|
data/lib/mongo/connection.rb
CHANGED
@@ -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, :
|
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
|
-
#
|
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
|
-
#
|
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 [
|
73
|
-
#
|
74
|
-
# @option options [
|
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://
|
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[:
|
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
|
-
@
|
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
|
-
|
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
|
-
#
|
158
|
-
#
|
159
|
-
#
|
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
|
158
|
+
# @param opts [Hash] Any of the available options that can be passed to Connection.new.
|
163
159
|
#
|
164
|
-
# @
|
165
|
-
#
|
166
|
-
#
|
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
|
-
# :
|
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 =
|
407
|
+
socket = checkout_writer
|
408
408
|
send_message_on_socket(packed_message, socket)
|
409
409
|
ensure
|
410
|
-
|
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 [
|
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 =
|
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
|
-
|
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
|
-
|
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 ||
|
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
|
-
|
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
|
485
|
-
nodes_to_try.
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
-
@
|
505
|
-
|
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
|
-
@
|
597
|
-
@
|
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
|
-
|
625
|
-
|
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
|
-
|
654
|
-
@primary = [
|
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
|
-
|
690
|
-
|
746
|
+
# Replace the list of seed nodes with the canonical list.
|
747
|
+
@nodes = new_nodes.clone
|
691
748
|
|
692
|
-
|
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
|