mongo 1.1.4 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +50 -69
- data/docs/CREDITS.md +4 -0
- data/docs/HISTORY.md +9 -0
- data/docs/REPLICA_SETS.md +8 -10
- data/lib/mongo.rb +3 -1
- data/lib/mongo/collection.rb +2 -1
- data/lib/mongo/connection.rb +146 -314
- data/lib/mongo/db.rb +6 -2
- data/lib/mongo/gridfs/grid.rb +1 -1
- data/lib/mongo/gridfs/grid_io.rb +29 -5
- data/lib/mongo/repl_set_connection.rb +290 -0
- data/lib/mongo/util/pool.rb +6 -8
- data/lib/mongo/util/uri_parser.rb +71 -0
- data/mongo.gemspec +1 -2
- data/test/collection_test.rb +9 -7
- data/test/connection_test.rb +0 -6
- data/test/grid_file_system_test.rb +2 -2
- data/test/grid_io_test.rb +33 -1
- data/test/grid_test.rb +36 -6
- data/test/replica_sets/connect_test.rb +59 -21
- data/test/replica_sets/count_test.rb +9 -7
- data/test/replica_sets/insert_test.rb +11 -9
- data/test/replica_sets/pooled_insert_test.rb +12 -13
- data/test/replica_sets/query_secondaries.rb +48 -8
- data/test/replica_sets/query_test.rb +10 -9
- data/test/replica_sets/replication_ack_test.rb +15 -22
- data/test/replica_sets/rs_test_helper.rb +29 -0
- data/test/test_helper.rb +13 -20
- data/test/threading/{test_threading_large_pool.rb → threading_with_large_pool_test.rb} +1 -1
- data/test/tools/repl_set_manager.rb +241 -0
- data/test/tools/test.rb +13 -0
- data/test/unit/connection_test.rb +3 -85
- data/test/unit/repl_set_connection_test.rb +82 -0
- metadata +19 -21
- data/test/replica_pairs/count_test.rb +0 -34
- data/test/replica_pairs/insert_test.rb +0 -50
- data/test/replica_pairs/pooled_insert_test.rb +0 -54
- data/test/replica_pairs/query_test.rb +0 -39
- data/test/replica_sets/node_type_test.rb +0 -42
- data/test/rs.rb +0 -24
data/Rakefile
CHANGED
@@ -38,32 +38,45 @@ end
|
|
38
38
|
|
39
39
|
desc "Test the MongoDB Ruby driver."
|
40
40
|
task :test do
|
41
|
-
puts "\
|
42
|
-
puts "
|
43
|
-
puts "To test the pure ruby driver: \nrake test:ruby"
|
41
|
+
puts "\nTo test the driver with the C-extensions:\nrake test:c\n\n"
|
42
|
+
puts "To test the pure ruby driver: \nrake test:ruby\n\n"
|
44
43
|
end
|
45
44
|
|
46
45
|
namespace :test do
|
47
46
|
|
48
|
-
desc "Test the driver with the
|
47
|
+
desc "Test the driver with the C extension enabled."
|
49
48
|
task :c do
|
50
49
|
ENV['C_EXT'] = 'TRUE'
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
if ENV['TEST']
|
51
|
+
Rake::Task['test:functional'].invoke
|
52
|
+
else
|
53
|
+
Rake::Task['test:unit'].invoke
|
54
|
+
Rake::Task['test:functional'].invoke
|
55
|
+
Rake::Task['test:bson'].invoke
|
56
|
+
Rake::Task['test:pooled_threading'].invoke
|
57
|
+
Rake::Task['test:drop_databases'].invoke
|
58
|
+
end
|
56
59
|
ENV['C_EXT'] = nil
|
57
60
|
end
|
58
61
|
|
59
|
-
desc "Test the driver using pure ruby (no
|
62
|
+
desc "Test the driver using pure ruby (no C extension)"
|
60
63
|
task :ruby do
|
61
64
|
ENV['C_EXT'] = nil
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
if ENV['TEST']
|
66
|
+
Rake::Task['test:functional'].invoke
|
67
|
+
else
|
68
|
+
Rake::Task['test:unit'].invoke
|
69
|
+
Rake::Task['test:functional'].invoke
|
70
|
+
Rake::Task['test:bson'].invoke
|
71
|
+
Rake::Task['test:pooled_threading'].invoke
|
72
|
+
Rake::Task['test:drop_databases'].invoke
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Run the replica set test suite"
|
77
|
+
Rake::TestTask.new(:rs) do |t|
|
78
|
+
t.test_files = FileList['test/replica_sets/*_test.rb']
|
79
|
+
t.verbose = true
|
67
80
|
end
|
68
81
|
|
69
82
|
Rake::TestTask.new(:unit) do |t|
|
@@ -77,52 +90,7 @@ namespace :test do
|
|
77
90
|
end
|
78
91
|
|
79
92
|
Rake::TestTask.new(:pooled_threading) do |t|
|
80
|
-
t.test_files = FileList['test/threading
|
81
|
-
t.verbose = true
|
82
|
-
end
|
83
|
-
|
84
|
-
Rake::TestTask.new(:replica_pair_count) do |t|
|
85
|
-
t.test_files = FileList['test/replica_pairs/count_test.rb']
|
86
|
-
t.verbose = true
|
87
|
-
end
|
88
|
-
|
89
|
-
Rake::TestTask.new(:replica_pair_insert) do |t|
|
90
|
-
t.test_files = FileList['test/replica_pairs/insert_test.rb']
|
91
|
-
t.verbose = true
|
92
|
-
end
|
93
|
-
|
94
|
-
Rake::TestTask.new(:pooled_replica_pair_insert) do |t|
|
95
|
-
t.test_files = FileList['test/replica_pairs/pooled_insert_test.rb']
|
96
|
-
t.verbose = true
|
97
|
-
end
|
98
|
-
|
99
|
-
Rake::TestTask.new(:replica_pair_query) do |t|
|
100
|
-
t.test_files = FileList['test/replica_pairs/query_test.rb']
|
101
|
-
t.verbose = true
|
102
|
-
end
|
103
|
-
|
104
|
-
Rake::TestTask.new(:replica_set_count) do |t|
|
105
|
-
t.test_files = FileList['test/replica_sets/count_test.rb']
|
106
|
-
t.verbose = true
|
107
|
-
end
|
108
|
-
|
109
|
-
Rake::TestTask.new(:replica_set_insert) do |t|
|
110
|
-
t.test_files = FileList['test/replica_sets/insert_test.rb']
|
111
|
-
t.verbose = true
|
112
|
-
end
|
113
|
-
|
114
|
-
Rake::TestTask.new(:pooled_replica_set_insert) do |t|
|
115
|
-
t.test_files = FileList['test/replica_sets/pooled_insert_test.rb']
|
116
|
-
t.verbose = true
|
117
|
-
end
|
118
|
-
|
119
|
-
Rake::TestTask.new(:replica_set_query) do |t|
|
120
|
-
t.test_files = FileList['test/replica_sets/query_test.rb']
|
121
|
-
t.verbose = true
|
122
|
-
end
|
123
|
-
|
124
|
-
Rake::TestTask.new(:replica_set_ack) do |t|
|
125
|
-
t.test_files = FileList['test/replica_sets/replication_ack_test.rb']
|
93
|
+
t.test_files = FileList['test/threading/*_test.rb']
|
126
94
|
t.verbose = true
|
127
95
|
end
|
128
96
|
|
@@ -147,16 +115,17 @@ namespace :test do
|
|
147
115
|
end
|
148
116
|
|
149
117
|
task :drop_databases do |t|
|
150
|
-
puts "Dropping test
|
151
|
-
require
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
118
|
+
puts "Dropping test databases..."
|
119
|
+
require './lib/mongo'
|
120
|
+
con = Mongo::Connection.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
|
121
|
+
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Connection::DEFAULT_PORT)
|
122
|
+
con.database_names.each do |name|
|
123
|
+
con.drop_database(name) if name =~ /^ruby-test/
|
124
|
+
end
|
156
125
|
end
|
157
126
|
end
|
158
127
|
|
159
|
-
desc "Generate documentation"
|
128
|
+
desc "Generate RDOC documentation"
|
160
129
|
task :rdoc do
|
161
130
|
version = eval(File.read("mongo.gemspec")).version
|
162
131
|
out = File.join('html', version.to_s)
|
@@ -172,6 +141,19 @@ task :ydoc do
|
|
172
141
|
system "yardoc lib/**/*.rb lib/mongo/**/*.rb lib/bson/**/*.rb -e yard/yard_ext.rb -p yard/templates -o #{out} --title MongoRuby-#{Mongo::VERSION} --files docs/TUTORIAL.md,docs/GridFS.md,docs/FAQ.md,docs/REPLICA_SETS.md,docs/WRITE_CONCERN.md,docs/HISTORY.md,docs/CREDITS.md,docs/1.0_UPGRADE.md"
|
173
142
|
end
|
174
143
|
|
144
|
+
namespace :bamboo do
|
145
|
+
namespace :test do
|
146
|
+
task :ruby do
|
147
|
+
Rake::Task['test:ruby'].invoke
|
148
|
+
end
|
149
|
+
|
150
|
+
task :c do
|
151
|
+
Rake::Task['gem:install_extensions'].invoke
|
152
|
+
Rake::Task['test:c'].invoke
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
175
157
|
namespace :gem do
|
176
158
|
|
177
159
|
desc "Install the gem locally"
|
@@ -188,7 +170,6 @@ namespace :gem do
|
|
188
170
|
|
189
171
|
desc "Install the optional c extensions"
|
190
172
|
task :install_extensions do
|
191
|
-
sh "gem uninstall -x -a -I bson_ext"
|
192
173
|
sh "gem build bson_ext.gemspec"
|
193
174
|
sh "gem install --no-rdoc --no-ri bson_ext-*.gem"
|
194
175
|
sh "rm bson_ext-*.gem"
|
data/docs/CREDITS.md
CHANGED
data/docs/HISTORY.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# MongoDB Ruby Driver History
|
2
2
|
|
3
|
+
### 1.1.5
|
4
|
+
2010-12-15
|
5
|
+
|
6
|
+
* ReplSetConnection class. This must be used for replica set connections from
|
7
|
+
now on. You can still use Connection.multi, but that method has been deprecated.
|
8
|
+
* Automated replica set tests. rake test:rs
|
9
|
+
* Check that request and response ids match.
|
10
|
+
* Several bug fixes. See the commit history for details.
|
11
|
+
|
3
12
|
### 1.1.4
|
4
13
|
2010-11-30
|
5
14
|
|
data/docs/REPLICA_SETS.md
CHANGED
@@ -6,17 +6,17 @@ Here follow a few considerations for those using the MongoDB Ruby driver with [r
|
|
6
6
|
|
7
7
|
First, make sure that you've configured and initialized a replica set.
|
8
8
|
|
9
|
-
Use `
|
9
|
+
Use `ReplSetConnection.new` to connect to a replica set:
|
10
10
|
|
11
|
-
@connection =
|
11
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], ['n2.mydb.net', 27017], ['n3.mydb.net', 27017])
|
12
12
|
|
13
13
|
The driver will attempt to connect to a master node and, when found, will replace all seed nodes with known members of the replica set.
|
14
14
|
|
15
15
|
### Read slaves
|
16
16
|
|
17
|
-
If you want to read from a seconday node, you can pass :read_secondary => true to
|
17
|
+
If you want to read from a seconday node, you can pass :read_secondary => true to ReplSetConnection#new.
|
18
18
|
|
19
|
-
@connection =
|
19
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], ['n2.mydb.net', 27017], ['n3.mydb.net', 27017],
|
20
20
|
:read_secondary => true)
|
21
21
|
|
22
22
|
A random secondary will be chosen to be read from. In a typical multi-process Ruby application, you'll have a good distribution of reads across secondary nodes.
|
@@ -62,14 +62,12 @@ Of course, the proper way to handle connection failures will always depend on th
|
|
62
62
|
|
63
63
|
### Testing
|
64
64
|
|
65
|
-
The Ruby driver (>= 1.
|
65
|
+
The Ruby driver (>= 1.1.5) includes unit tests for verifying replica set behavior. They reside in *tests/replica_sets*. You can run them as a group with the following rake task:
|
66
66
|
|
67
|
-
rake test:
|
68
|
-
rake test:replica_set_insert
|
69
|
-
rake test:pooled_replica_set_insert
|
70
|
-
rake test:replica_set_query
|
67
|
+
rake test:rs
|
71
68
|
|
72
|
-
|
69
|
+
The suite will set up a five-node replica set by itself and ensure that driver behaves correctly even in the face
|
70
|
+
of individual node failures. Node that the `mongod` executable must be in the search path for this to work.
|
73
71
|
|
74
72
|
### Further Reading
|
75
73
|
|
data/lib/mongo.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
4
|
|
5
5
|
module Mongo
|
6
|
-
VERSION = "1.1.
|
6
|
+
VERSION = "1.1.5"
|
7
7
|
end
|
8
8
|
|
9
9
|
module Mongo
|
@@ -42,9 +42,11 @@ require 'mongo/util/support'
|
|
42
42
|
require 'mongo/util/core_ext'
|
43
43
|
require 'mongo/util/pool'
|
44
44
|
require 'mongo/util/server_version'
|
45
|
+
require 'mongo/util/uri_parser'
|
45
46
|
|
46
47
|
require 'mongo/collection'
|
47
48
|
require 'mongo/connection'
|
49
|
+
require 'mongo/repl_set_connection'
|
48
50
|
require 'mongo/cursor'
|
49
51
|
require 'mongo/db'
|
50
52
|
require 'mongo/exceptions'
|
data/lib/mongo/collection.rb
CHANGED
@@ -321,6 +321,7 @@ module Mongo
|
|
321
321
|
@connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe)
|
322
322
|
else
|
323
323
|
@connection.send_message(Mongo::Constants::OP_DELETE, message)
|
324
|
+
true
|
324
325
|
end
|
325
326
|
end
|
326
327
|
|
@@ -450,7 +451,7 @@ module Mongo
|
|
450
451
|
generate_indexes(valid, name, opts) if valid.any?
|
451
452
|
|
452
453
|
# Reset the cache here in case there are any errors inserting. Best to be safe.
|
453
|
-
|
454
|
+
@cache[name] = now + @cache_time
|
454
455
|
name
|
455
456
|
end
|
456
457
|
|
data/lib/mongo/connection.rb
CHANGED
@@ -35,11 +35,7 @@ module Mongo
|
|
35
35
|
STANDARD_HEADER_SIZE = 16
|
36
36
|
RESPONSE_HEADER_SIZE = 20
|
37
37
|
|
38
|
-
|
39
|
-
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
|
40
|
-
|
41
|
-
attr_reader :logger, :size, :nodes, :auths, :primary, :secondaries, :arbiters,
|
42
|
-
:safe, :primary_pool, :read_pool, :secondary_pools
|
38
|
+
attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try
|
43
39
|
|
44
40
|
# Counter for generating unique request ids.
|
45
41
|
@@current_request_id = 0
|
@@ -49,8 +45,7 @@ module Mongo
|
|
49
45
|
# You may specify whether connection to slave is permitted.
|
50
46
|
# In all cases, the default host is "localhost" and the default port is 27017.
|
51
47
|
#
|
52
|
-
#
|
53
|
-
# use Connection.multi.
|
48
|
+
# If you're connecting to a replica set, you'll need to use ReplSetConnection.new instead.
|
54
49
|
#
|
55
50
|
# Once connected to a replica set, you can find out which nodes are primary, secondary, and
|
56
51
|
# arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
|
@@ -92,62 +87,19 @@ module Mongo
|
|
92
87
|
#
|
93
88
|
# @core connections
|
94
89
|
def initialize(host=nil, port=nil, options={})
|
95
|
-
@
|
96
|
-
|
97
|
-
if block_given?
|
98
|
-
@nodes = yield self
|
99
|
-
else
|
100
|
-
@nodes = format_pair(host, port)
|
101
|
-
end
|
90
|
+
@host_to_try = format_pair(host, port)
|
102
91
|
|
103
92
|
# Host and port of current master.
|
104
93
|
@host = @port = nil
|
105
94
|
|
106
|
-
# Replica set name
|
107
|
-
@replica_set_name = options[:rs_name]
|
108
|
-
|
109
|
-
# Lock for request ids.
|
110
|
-
@id_lock = Mutex.new
|
111
|
-
|
112
|
-
# Pool size and timeout.
|
113
|
-
@pool_size = options[:pool_size] || 1
|
114
|
-
@timeout = options[:timeout] || 5.0
|
115
|
-
|
116
|
-
# Mutex for synchronizing pool access
|
117
|
-
@connection_mutex = Mutex.new
|
118
|
-
|
119
|
-
# Global safe option. This is false by default.
|
120
|
-
@safe = options[:safe] || false
|
121
|
-
|
122
|
-
# Create a mutex when a new key, in this case a socket,
|
123
|
-
# is added to the hash.
|
124
|
-
@safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
|
125
|
-
|
126
|
-
# Condition variable for signal and wait
|
127
|
-
@queue = ConditionVariable.new
|
128
|
-
|
129
95
|
# slave_ok can be true only if one node is specified
|
130
96
|
@slave_ok = options[:slave_ok]
|
131
97
|
|
132
|
-
|
133
|
-
# when connecting to a replica set.
|
134
|
-
@primary = nil
|
135
|
-
@secondaries = []
|
136
|
-
@arbiters = []
|
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
|
-
|
145
|
-
@logger = options[:logger] || nil
|
146
|
-
|
147
|
-
should_connect = options.fetch(:connect, true)
|
148
|
-
connect if should_connect
|
98
|
+
setup(options)
|
149
99
|
end
|
150
100
|
|
101
|
+
# DEPRECATED
|
102
|
+
#
|
151
103
|
# Initialize a connection to a MongoDB replica set using an array of seed nodes.
|
152
104
|
#
|
153
105
|
# The seed nodes specified will be used on the initial connection to the replica set, but note
|
@@ -170,20 +122,13 @@ module Mongo
|
|
170
122
|
# :read_secondary => true)
|
171
123
|
#
|
172
124
|
# @return [Mongo::Connection]
|
125
|
+
#
|
126
|
+
# @deprecated
|
173
127
|
def self.multi(nodes, opts={})
|
174
|
-
|
175
|
-
raise MongoArgumentError, "Connection.multi requires at least one node to be specified."
|
176
|
-
end
|
128
|
+
warn "Connection.multi is now deprecated. Please use ReplSetConnection.new instead."
|
177
129
|
|
178
|
-
|
179
|
-
|
180
|
-
new(nil, nil, opts) do |con|
|
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]
|
184
|
-
con.pair_val_to_connection(node)
|
185
|
-
end
|
186
|
-
end
|
130
|
+
nodes << opts
|
131
|
+
ReplSetConnection.new(*nodes)
|
187
132
|
end
|
188
133
|
|
189
134
|
# Initialize a connection to MongoDB using the MongoDB URI spec:
|
@@ -195,8 +140,15 @@ module Mongo
|
|
195
140
|
#
|
196
141
|
# @return [Mongo::Connection]
|
197
142
|
def self.from_uri(uri, opts={})
|
198
|
-
|
199
|
-
|
143
|
+
nodes, auths = Mongo::URIParser.parse(uri)
|
144
|
+
opts.merge!({:auths => auths})
|
145
|
+
if nodes.length == 1
|
146
|
+
Connection.new(nodes[0][0], nodes[0][1], opts)
|
147
|
+
elsif nodes.length > 1
|
148
|
+
nodes << opts
|
149
|
+
ReplSetConnection.new(*nodes)
|
150
|
+
else
|
151
|
+
raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
|
200
152
|
end
|
201
153
|
end
|
202
154
|
|
@@ -362,18 +314,7 @@ module Mongo
|
|
362
314
|
self["admin"].command(oh)
|
363
315
|
end
|
364
316
|
|
365
|
-
|
366
|
-
#
|
367
|
-
# return [Integer]
|
368
|
-
def get_request_id
|
369
|
-
request_id = ''
|
370
|
-
@id_lock.synchronize do
|
371
|
-
request_id = @@current_request_id += 1
|
372
|
-
end
|
373
|
-
request_id
|
374
|
-
end
|
375
|
-
|
376
|
-
# Get the build information for the current connection.
|
317
|
+
# Get the build information for the current connection.
|
377
318
|
#
|
378
319
|
# @return [Hash]
|
379
320
|
def server_info
|
@@ -392,7 +333,7 @@ module Mongo
|
|
392
333
|
#
|
393
334
|
# @return [Boolean]
|
394
335
|
def slave_ok?
|
395
|
-
@
|
336
|
+
@slave_ok
|
396
337
|
end
|
397
338
|
|
398
339
|
# Send a message to MongoDB, adding the necessary headers.
|
@@ -403,7 +344,8 @@ module Mongo
|
|
403
344
|
# @return [Integer] number of bytes sent
|
404
345
|
def send_message(operation, message, log_message=nil)
|
405
346
|
begin
|
406
|
-
|
347
|
+
add_message_headers(message, operation)
|
348
|
+
packed_message = message.to_s
|
407
349
|
socket = checkout_writer
|
408
350
|
send_message_on_socket(packed_message, socket)
|
409
351
|
ensure
|
@@ -424,15 +366,19 @@ module Mongo
|
|
424
366
|
#
|
425
367
|
# @return [Hash] The document returned by the call to getlasterror.
|
426
368
|
def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false)
|
427
|
-
|
428
|
-
|
369
|
+
docs = num_received = cursor_id = ''
|
370
|
+
add_message_headers(message, operation)
|
371
|
+
|
372
|
+
last_error_message = BSON::ByteBuffer.new
|
373
|
+
build_last_error_message(last_error_message, db_name, last_error_params)
|
374
|
+
last_error_id = add_message_headers(last_error_message, Mongo::Constants::OP_QUERY)
|
375
|
+
|
376
|
+
packed_message = message.append!(last_error_message).to_s
|
429
377
|
begin
|
430
378
|
sock = checkout_writer
|
431
|
-
packed_message = message_with_headers.append!(message_with_check).to_s
|
432
|
-
docs = num_received = cursor_id = ''
|
433
379
|
@safe_mutexes[sock].synchronize do
|
434
380
|
send_message_on_socket(packed_message, sock)
|
435
|
-
docs, num_received, cursor_id = receive(sock)
|
381
|
+
docs, num_received, cursor_id = receive(sock, last_error_id)
|
436
382
|
end
|
437
383
|
ensure
|
438
384
|
checkin_writer(sock)
|
@@ -440,6 +386,7 @@ module Mongo
|
|
440
386
|
|
441
387
|
if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
|
442
388
|
close if error == "not master"
|
389
|
+
error = "wtimeout" if error == "timeout"
|
443
390
|
raise Mongo::OperationFailure, docs[0]['code'].to_s + ': ' + error
|
444
391
|
end
|
445
392
|
|
@@ -456,14 +403,15 @@ module Mongo
|
|
456
403
|
# An array whose indexes include [0] documents returned, [1] number of document received,
|
457
404
|
# and [3] a cursor_id.
|
458
405
|
def receive_message(operation, message, log_message=nil, socket=nil, command=false)
|
459
|
-
|
406
|
+
request_id = add_message_headers(message, operation)
|
407
|
+
packed_message = message.to_s
|
460
408
|
begin
|
461
409
|
sock = socket || (command ? checkout_writer : checkout_reader)
|
462
410
|
|
463
411
|
result = ''
|
464
412
|
@safe_mutexes[sock].synchronize do
|
465
413
|
send_message_on_socket(packed_message, sock)
|
466
|
-
result = receive(sock)
|
414
|
+
result = receive(sock, request_id)
|
467
415
|
end
|
468
416
|
ensure
|
469
417
|
command ? checkin_writer(sock) : checkin_reader(sock)
|
@@ -480,22 +428,15 @@ module Mongo
|
|
480
428
|
# @raise [ConnectionFailure] if unable to connect to any host or port.
|
481
429
|
def connect
|
482
430
|
reset_connection
|
483
|
-
@nodes_to_try = @nodes.clone
|
484
|
-
|
485
|
-
while connecting?
|
486
|
-
node = @nodes_to_try.shift
|
487
|
-
config = check_is_master(node)
|
488
431
|
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
set_auxillary(node, config)
|
493
|
-
end
|
432
|
+
config = check_is_master(@host_to_try)
|
433
|
+
if is_primary?(config)
|
434
|
+
set_primary(@host_to_try)
|
494
435
|
end
|
495
436
|
|
496
|
-
|
497
|
-
|
498
|
-
|
437
|
+
if !connected?
|
438
|
+
raise ConnectionFailure, "Failed to connect to a master node at #{@host_to_try[0]}:#{@host_to_try[1]}"
|
439
|
+
end
|
499
440
|
end
|
500
441
|
|
501
442
|
def connecting?
|
@@ -513,139 +454,102 @@ module Mongo
|
|
513
454
|
def close
|
514
455
|
@primary_pool.close if @primary_pool
|
515
456
|
@primary_pool = nil
|
516
|
-
@read_pool = nil
|
517
|
-
@secondary_pools.each do |pool|
|
518
|
-
pool.close
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
## Configuration helper methods
|
523
|
-
|
524
|
-
# Returns an array of host-port pairs.
|
525
|
-
#
|
526
|
-
# @private
|
527
|
-
def format_pair(pair_or_host, port)
|
528
|
-
case pair_or_host
|
529
|
-
when String
|
530
|
-
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
|
531
|
-
when nil
|
532
|
-
[['localhost', DEFAULT_PORT]]
|
533
|
-
end
|
534
|
-
end
|
535
|
-
|
536
|
-
# Convert an argument containing a host name string and a
|
537
|
-
# port number integer into a [host, port] pair array.
|
538
|
-
#
|
539
|
-
# @private
|
540
|
-
def pair_val_to_connection(a)
|
541
|
-
case a
|
542
|
-
when nil
|
543
|
-
['localhost', DEFAULT_PORT]
|
544
|
-
when String
|
545
|
-
[a, DEFAULT_PORT]
|
546
|
-
when Integer
|
547
|
-
['localhost', a]
|
548
|
-
when Array
|
549
|
-
a
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
554
|
-
# Returns an array of nodes and an array of db authorizations, if applicable.
|
555
|
-
#
|
556
|
-
# @private
|
557
|
-
def parse_uri(string)
|
558
|
-
if string =~ /^mongodb:\/\//
|
559
|
-
string = string[10..-1]
|
560
|
-
else
|
561
|
-
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
562
|
-
end
|
563
|
-
|
564
|
-
nodes = []
|
565
|
-
auths = []
|
566
|
-
specs = string.split(',')
|
567
|
-
specs.each do |spec|
|
568
|
-
matches = MONGODB_URI_MATCHER.match(spec)
|
569
|
-
if !matches
|
570
|
-
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
571
|
-
end
|
572
|
-
|
573
|
-
uname = matches[2]
|
574
|
-
pwd = matches[3]
|
575
|
-
host = matches[4]
|
576
|
-
port = matches[6] || DEFAULT_PORT
|
577
|
-
if !(port.to_s =~ /^\d+$/)
|
578
|
-
raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
|
579
|
-
end
|
580
|
-
port = port.to_i
|
581
|
-
db = matches[8]
|
582
|
-
|
583
|
-
if uname && pwd && db
|
584
|
-
add_auth(db, uname, pwd)
|
585
|
-
elsif uname || pwd || db
|
586
|
-
raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
|
587
|
-
"and db if any one of these is specified."
|
588
|
-
end
|
589
|
-
|
590
|
-
nodes << [host, port]
|
591
|
-
end
|
592
|
-
|
593
|
-
nodes
|
594
457
|
end
|
595
458
|
|
596
459
|
# Checkout a socket for reading (i.e., a secondary node).
|
460
|
+
# Note: this is overridden in ReplSetConnection.
|
597
461
|
def checkout_reader
|
598
462
|
connect unless connected?
|
599
|
-
|
600
|
-
if @read_pool
|
601
|
-
@read_pool.checkout
|
602
|
-
else
|
603
|
-
checkout_writer
|
604
|
-
end
|
463
|
+
@primary_pool.checkout
|
605
464
|
end
|
606
465
|
|
607
466
|
# Checkout a socket for writing (i.e., a primary node).
|
467
|
+
# Note: this is overridden in ReplSetConnection.
|
608
468
|
def checkout_writer
|
609
469
|
connect unless connected?
|
610
|
-
|
611
470
|
@primary_pool.checkout
|
612
471
|
end
|
613
472
|
|
614
473
|
# Checkin a socket used for reading.
|
474
|
+
# Note: this is overridden in ReplSetConnection.
|
615
475
|
def checkin_reader(socket)
|
616
|
-
if @
|
617
|
-
@
|
618
|
-
else
|
619
|
-
checkin_writer(socket)
|
476
|
+
if @primary_pool
|
477
|
+
@primary_pool.checkin(socket)
|
620
478
|
end
|
621
479
|
end
|
622
480
|
|
623
481
|
# Checkin a socket used for writing.
|
482
|
+
# Note: this is overridden in ReplSetConnection.
|
624
483
|
def checkin_writer(socket)
|
625
484
|
if @primary_pool
|
626
485
|
@primary_pool.checkin(socket)
|
627
486
|
end
|
628
487
|
end
|
629
488
|
|
630
|
-
|
489
|
+
protected
|
631
490
|
|
632
|
-
#
|
633
|
-
|
634
|
-
|
635
|
-
|
491
|
+
# Generic initialization code.
|
492
|
+
# @protected
|
493
|
+
def setup(options)
|
494
|
+
# Authentication objects
|
495
|
+
@auths = options.fetch(:auths, [])
|
496
|
+
|
497
|
+
# Lock for request ids.
|
498
|
+
@id_lock = Mutex.new
|
499
|
+
|
500
|
+
# Pool size and timeout.
|
501
|
+
@pool_size = options[:pool_size] || 1
|
502
|
+
@timeout = options[:timeout] || 5.0
|
503
|
+
|
504
|
+
# Mutex for synchronizing pool access
|
505
|
+
@connection_mutex = Mutex.new
|
506
|
+
|
507
|
+
# Global safe option. This is false by default.
|
508
|
+
@safe = options[:safe] || false
|
509
|
+
|
510
|
+
# Create a mutex when a new key, in this case a socket,
|
511
|
+
# is added to the hash.
|
512
|
+
@safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
|
513
|
+
|
514
|
+
# Condition variable for signal and wait
|
515
|
+
@queue = ConditionVariable.new
|
516
|
+
|
517
|
+
# Connection pool for primay node
|
518
|
+
@primary = nil
|
519
|
+
@primary_pool = nil
|
520
|
+
|
521
|
+
@logger = options[:logger] || nil
|
522
|
+
|
523
|
+
should_connect = options.fetch(:connect, true)
|
524
|
+
connect if should_connect
|
525
|
+
end
|
526
|
+
|
527
|
+
## Configuration helper methods
|
528
|
+
|
529
|
+
# Returns a host-port pair.
|
530
|
+
#
|
531
|
+
# @return [Array]
|
532
|
+
#
|
533
|
+
# @private
|
534
|
+
def format_pair(host, port)
|
535
|
+
case host
|
536
|
+
when String
|
537
|
+
[host, port ? port.to_i : DEFAULT_PORT]
|
538
|
+
when nil
|
539
|
+
['localhost', DEFAULT_PORT]
|
636
540
|
end
|
637
541
|
end
|
638
542
|
|
543
|
+
private
|
544
|
+
|
545
|
+
## Methods for establishing a connection:
|
546
|
+
|
639
547
|
# If a ConnectionFailure is raised, this method will be called
|
640
548
|
# to close the connection and reset connection values.
|
549
|
+
# TODO: evaluate whether this method is actually necessary
|
641
550
|
def reset_connection
|
642
551
|
close
|
643
552
|
@primary = nil
|
644
|
-
@secondaries = []
|
645
|
-
@secondary_pools = []
|
646
|
-
@arbiters = []
|
647
|
-
@nodes_tried = []
|
648
|
-
@nodes_to_try = []
|
649
553
|
end
|
650
554
|
|
651
555
|
# Primary is defined as either a master node or a slave if
|
@@ -653,8 +557,9 @@ module Mongo
|
|
653
557
|
#
|
654
558
|
# If a primary node is discovered, we set the the @host and @port and
|
655
559
|
# apply any saved authentication.
|
560
|
+
# TODO: simplify
|
656
561
|
def is_primary?(config)
|
657
|
-
config && (config['ismaster'] == 1 || config['ismaster'] == true) ||
|
562
|
+
config && (config['ismaster'] == 1 || config['ismaster'] == true) || @slave_ok
|
658
563
|
end
|
659
564
|
|
660
565
|
def check_is_master(node)
|
@@ -664,41 +569,15 @@ module Mongo
|
|
664
569
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
665
570
|
|
666
571
|
config = self['admin'].command({:ismaster => 1}, :sock => socket)
|
667
|
-
|
668
|
-
check_set_name(config, socket)
|
669
572
|
rescue OperationFailure, SocketError, SystemCallError, IOError => ex
|
670
|
-
close
|
573
|
+
close
|
671
574
|
ensure
|
672
|
-
@nodes_tried << node
|
673
|
-
if config
|
674
|
-
update_node_list(config['hosts']) if config['hosts']
|
675
|
-
|
676
|
-
if config['msg'] && @logger
|
677
|
-
@logger.warn("MONGODB #{config['msg']}")
|
678
|
-
end
|
679
|
-
end
|
680
|
-
|
681
575
|
socket.close if socket
|
682
576
|
end
|
683
577
|
|
684
578
|
config
|
685
579
|
end
|
686
580
|
|
687
|
-
# Make sure that we're connected to the expected replica set.
|
688
|
-
def check_set_name(config, socket)
|
689
|
-
if @replica_set_name
|
690
|
-
config = self['admin'].command({:replSetGetStatus => 1},
|
691
|
-
:sock => socket, :check_response => false)
|
692
|
-
|
693
|
-
if !Mongo::Support.ok?(config)
|
694
|
-
raise ReplicaSetConnectionError, config['errmsg']
|
695
|
-
elsif config['set'] != @replica_set_name
|
696
|
-
raise ReplicaSetConnectionError,
|
697
|
-
"Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set_name}'"
|
698
|
-
end
|
699
|
-
end
|
700
|
-
end
|
701
|
-
|
702
581
|
# Set the specified node as primary, and
|
703
582
|
# apply any saved authentication credentials.
|
704
583
|
def set_primary(node)
|
@@ -708,72 +587,30 @@ module Mongo
|
|
708
587
|
apply_saved_authentication
|
709
588
|
end
|
710
589
|
|
711
|
-
# Determines what kind of node we have and caches its host
|
712
|
-
# and port so that users can easily connect manually.
|
713
|
-
def set_auxillary(node, config)
|
714
|
-
if config
|
715
|
-
if config['secondary']
|
716
|
-
host, port = *node
|
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
|
721
|
-
elsif config['arbiterOnly']
|
722
|
-
@arbiters << node unless @arbiters.include?(node)
|
723
|
-
end
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
# Update the list of known nodes. Only applies to replica sets,
|
728
|
-
# where the response to the ismaster command will return a list
|
729
|
-
# of known hosts.
|
730
|
-
#
|
731
|
-
# @param hosts [Array] a list of hosts, specified as string-encoded
|
732
|
-
# host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"]
|
733
|
-
#
|
734
|
-
# @return [Array] the updated list of nodes
|
735
|
-
def update_node_list(hosts)
|
736
|
-
new_nodes = hosts.map do |host|
|
737
|
-
if !host.respond_to?(:split)
|
738
|
-
warn "Could not parse host #{host.inspect}."
|
739
|
-
next
|
740
|
-
end
|
741
|
-
|
742
|
-
host, port = host.split(':')
|
743
|
-
[host, port.to_i]
|
744
|
-
end
|
745
|
-
|
746
|
-
# Replace the list of seed nodes with the canonical list.
|
747
|
-
@nodes = new_nodes.clone
|
748
590
|
|
749
|
-
|
750
|
-
end
|
591
|
+
## Low-level connection methods.
|
751
592
|
|
752
|
-
def receive(sock)
|
753
|
-
|
593
|
+
def receive(sock, expected_response)
|
594
|
+
begin
|
595
|
+
receive_header(sock, expected_response)
|
754
596
|
number_received, cursor_id = receive_response_header(sock)
|
755
597
|
read_documents(number_received, cursor_id, sock)
|
598
|
+
rescue Mongo::ConnectionFailure => ex
|
599
|
+
close
|
600
|
+
raise ex
|
601
|
+
end
|
756
602
|
end
|
757
603
|
|
758
|
-
def receive_header(sock)
|
759
|
-
header =
|
760
|
-
header.
|
761
|
-
|
762
|
-
raise "
|
763
|
-
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
604
|
+
def receive_header(sock, expected_response)
|
605
|
+
header = receive_message_on_socket(16, sock)
|
606
|
+
size, request_id, response_to = header.unpack('VVV')
|
607
|
+
if expected_response != response_to
|
608
|
+
raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
|
764
609
|
end
|
765
|
-
header.rewind
|
766
|
-
size = header.get_int
|
767
|
-
request_id = header.get_int
|
768
|
-
response_to = header.get_int
|
769
|
-
op = header.get_int
|
770
|
-
end
|
771
610
|
|
772
|
-
|
773
|
-
bytes_read = receive_and_discard_message_on_socket(16, sock)
|
774
|
-
unless bytes_read == STANDARD_HEADER_SIZE
|
611
|
+
unless header.size == STANDARD_HEADER_SIZE
|
775
612
|
raise "Short read for DB response header: " +
|
776
|
-
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{
|
613
|
+
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
777
614
|
end
|
778
615
|
nil
|
779
616
|
end
|
@@ -814,8 +651,9 @@ module Mongo
|
|
814
651
|
|
815
652
|
# Constructs a getlasterror message. This method is used exclusively by
|
816
653
|
# Connection#send_message_with_safe_check.
|
817
|
-
|
818
|
-
|
654
|
+
#
|
655
|
+
# Because it modifies message by reference, we don't need to return it.
|
656
|
+
def build_last_error_message(message, db_name, opts)
|
819
657
|
message.put_int(0)
|
820
658
|
BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
|
821
659
|
message.put_int(0)
|
@@ -827,18 +665,22 @@ module Mongo
|
|
827
665
|
cmd.merge!(opts)
|
828
666
|
end
|
829
667
|
message.put_binary(BSON::BSON_CODER.serialize(cmd, false).to_s)
|
830
|
-
|
668
|
+
nil
|
831
669
|
end
|
832
670
|
|
833
671
|
# Prepares a message for transmission to MongoDB by
|
834
672
|
# constructing a valid message header.
|
835
|
-
|
673
|
+
#
|
674
|
+
# Note: this method modifies message by reference.
|
675
|
+
#
|
676
|
+
# @returns [Integer] the request id used in the header
|
677
|
+
def add_message_headers(message, operation)
|
836
678
|
headers = [
|
837
679
|
# Message size.
|
838
680
|
16 + message.size,
|
839
681
|
|
840
682
|
# Unique request id.
|
841
|
-
get_request_id,
|
683
|
+
request_id = get_request_id,
|
842
684
|
|
843
685
|
# Response id.
|
844
686
|
0,
|
@@ -848,6 +690,19 @@ module Mongo
|
|
848
690
|
].pack('VVVV')
|
849
691
|
|
850
692
|
message.prepend!(headers)
|
693
|
+
|
694
|
+
request_id
|
695
|
+
end
|
696
|
+
|
697
|
+
# Increment and return the next available request id.
|
698
|
+
#
|
699
|
+
# return [Integer]
|
700
|
+
def get_request_id
|
701
|
+
request_id = ''
|
702
|
+
@id_lock.synchronize do
|
703
|
+
request_id = @@current_request_id += 1
|
704
|
+
end
|
705
|
+
request_id
|
851
706
|
end
|
852
707
|
|
853
708
|
# Low-level method for sending a message on a socket.
|
@@ -893,29 +748,6 @@ module Mongo
|
|
893
748
|
message
|
894
749
|
end
|
895
750
|
|
896
|
-
# Low-level data for receiving data from socket.
|
897
|
-
# Unlike #receive_message_on_socket, this method immediately discards the data
|
898
|
-
# and only returns the number of bytes read.
|
899
|
-
def receive_and_discard_message_on_socket(length, socket)
|
900
|
-
bytes_read = 0
|
901
|
-
begin
|
902
|
-
chunk = socket.read(length)
|
903
|
-
bytes_read = chunk.length
|
904
|
-
raise ConnectionFailure, "connection closed" unless bytes_read > 0
|
905
|
-
if bytes_read < length
|
906
|
-
while bytes_read < length
|
907
|
-
socket.read(length - bytes_read, chunk)
|
908
|
-
raise ConnectionFailure, "connection closed" unless chunk.length > 0
|
909
|
-
bytes_read += chunk.length
|
910
|
-
end
|
911
|
-
end
|
912
|
-
rescue => ex
|
913
|
-
close
|
914
|
-
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
915
|
-
end
|
916
|
-
bytes_read
|
917
|
-
end
|
918
|
-
|
919
751
|
if defined?(Encoding)
|
920
752
|
BINARY_ENCODING = Encoding.find("binary")
|
921
753
|
|