mongo 1.1.4 → 1.1.5
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/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
|
|