mongo 1.0 → 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/LICENSE.txt +1 -13
- data/{README.rdoc → README.md} +129 -149
- data/Rakefile +94 -58
- data/bin/mongo_console +21 -0
- data/docs/1.0_UPGRADE.md +21 -0
- data/docs/CREDITS.md +123 -0
- data/docs/FAQ.md +112 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +185 -0
- data/docs/REPLICA_SETS.md +75 -0
- data/docs/TUTORIAL.md +247 -0
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo/collection.rb +225 -105
- data/lib/mongo/connection.rb +374 -315
- data/lib/mongo/cursor.rb +122 -77
- data/lib/mongo/db.rb +109 -85
- data/lib/mongo/exceptions.rb +6 -0
- data/lib/mongo/gridfs/grid.rb +19 -11
- data/lib/mongo/gridfs/grid_ext.rb +36 -9
- data/lib/mongo/gridfs/grid_file_system.rb +15 -9
- data/lib/mongo/gridfs/grid_io.rb +49 -16
- data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
- data/lib/mongo/repl_set_connection.rb +290 -0
- data/lib/mongo/util/conversions.rb +3 -1
- data/lib/mongo/util/core_ext.rb +17 -4
- data/lib/mongo/util/pool.rb +125 -0
- data/lib/mongo/util/server_version.rb +2 -0
- data/lib/mongo/util/support.rb +12 -0
- data/lib/mongo/util/uri_parser.rb +71 -0
- data/lib/mongo.rb +23 -7
- data/{mongo-ruby-driver.gemspec → mongo.gemspec} +9 -7
- data/test/auxillary/1.4_features.rb +2 -2
- data/test/auxillary/authentication_test.rb +1 -1
- data/test/auxillary/autoreconnect_test.rb +1 -1
- data/test/{slave_connection_test.rb → auxillary/slave_connection_test.rb} +6 -6
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +537 -0
- data/test/bson/byte_buffer_test.rb +190 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +141 -0
- data/test/bson/ordered_hash_test.rb +197 -0
- data/test/collection_test.rb +195 -15
- data/test/connection_test.rb +93 -56
- data/test/conversions_test.rb +1 -1
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +93 -32
- data/test/db_api_test.rb +28 -55
- data/test/db_connection_test.rb +2 -3
- data/test/db_test.rb +45 -40
- data/test/grid_file_system_test.rb +14 -6
- data/test/grid_io_test.rb +36 -7
- data/test/grid_test.rb +54 -10
- data/test/replica_sets/connect_test.rb +84 -0
- data/test/replica_sets/count_test.rb +35 -0
- data/test/{replica → replica_sets}/insert_test.rb +17 -14
- data/test/replica_sets/pooled_insert_test.rb +55 -0
- data/test/replica_sets/query_secondaries.rb +80 -0
- data/test/replica_sets/query_test.rb +41 -0
- data/test/replica_sets/replication_ack_test.rb +64 -0
- data/test/replica_sets/rs_test_helper.rb +29 -0
- data/test/safe_test.rb +68 -0
- data/test/support/hash_with_indifferent_access.rb +199 -0
- data/test/support/keys.rb +45 -0
- data/test/support_test.rb +19 -0
- data/test/test_helper.rb +53 -15
- data/test/threading/{test_threading_large_pool.rb → threading_with_large_pool_test.rb} +2 -2
- data/test/threading_test.rb +2 -2
- data/test/tools/repl_set_manager.rb +241 -0
- data/test/tools/test.rb +13 -0
- data/test/unit/collection_test.rb +70 -7
- data/test/unit/connection_test.rb +18 -39
- data/test/unit/cursor_test.rb +7 -8
- data/test/unit/db_test.rb +14 -17
- data/test/unit/grid_test.rb +49 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/repl_set_connection_test.rb +82 -0
- data/test/unit/safe_test.rb +125 -0
- metadata +132 -51
- data/bin/bson_benchmark.rb +0 -59
- data/bin/fail_if_no_c.rb +0 -11
- data/examples/admin.rb +0 -43
- data/examples/capped.rb +0 -22
- data/examples/cursor.rb +0 -48
- data/examples/gridfs.rb +0 -44
- data/examples/index_test.rb +0 -126
- data/examples/info.rb +0 -31
- data/examples/queries.rb +0 -70
- data/examples/simple.rb +0 -24
- data/examples/strict.rb +0 -35
- data/examples/types.rb +0 -36
- data/test/replica/count_test.rb +0 -34
- data/test/replica/pooled_insert_test.rb +0 -54
- data/test/replica/query_test.rb +0 -39
data/lib/mongo/connection.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
# --
|
|
2
4
|
# Copyright (C) 2008-2010 10gen Inc.
|
|
3
5
|
#
|
|
@@ -22,6 +24,9 @@ module Mongo
|
|
|
22
24
|
|
|
23
25
|
# Instantiates and manages connections to MongoDB.
|
|
24
26
|
class Connection
|
|
27
|
+
TCPSocket = ::TCPSocket
|
|
28
|
+
Mutex = ::Mutex
|
|
29
|
+
ConditionVariable = ::ConditionVariable
|
|
25
30
|
|
|
26
31
|
# Abort connections if a ConnectionError is raised.
|
|
27
32
|
Thread.abort_on_exception = true
|
|
@@ -30,34 +35,38 @@ module Mongo
|
|
|
30
35
|
STANDARD_HEADER_SIZE = 16
|
|
31
36
|
RESPONSE_HEADER_SIZE = 20
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
|
|
35
|
-
|
|
36
|
-
attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out
|
|
38
|
+
attr_reader :logger, :size, :auths, :primary, :safe, :primary_pool, :host_to_try
|
|
37
39
|
|
|
38
40
|
# Counter for generating unique request ids.
|
|
39
41
|
@@current_request_id = 0
|
|
40
42
|
|
|
41
|
-
# Create a connection to MongoDB.
|
|
42
|
-
# along with a maximum connection pool size and timeout.
|
|
43
|
+
# Create a connection to single MongoDB instance.
|
|
43
44
|
#
|
|
44
|
-
#
|
|
45
|
+
# You may specify whether connection to slave is permitted.
|
|
45
46
|
# In all cases, the default host is "localhost" and the default port is 27017.
|
|
46
47
|
#
|
|
47
|
-
#
|
|
48
|
+
# If you're connecting to a replica set, you'll need to use ReplSetConnection.new instead.
|
|
48
49
|
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
50
|
+
# Once connected to a replica set, you can find out which nodes are primary, secondary, and
|
|
51
|
+
# arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
|
|
52
|
+
# Connection#arbiters. This is useful if your application needs to connect manually to nodes other
|
|
53
|
+
# than the primary.
|
|
51
54
|
#
|
|
52
55
|
# @param [String, Hash] host.
|
|
53
56
|
# @param [Integer] port specify a port number here if only one host is being specified.
|
|
54
57
|
#
|
|
58
|
+
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
|
59
|
+
# propogated to DB objects instantiated off of this Connection. This
|
|
60
|
+
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
|
|
61
|
+
# on initialization.
|
|
55
62
|
# @option options [Boolean] :slave_ok (false) Must be set to +true+ when connecting
|
|
56
63
|
# to a single, slave node.
|
|
57
64
|
# @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
|
|
58
|
-
# @option options [Integer] :pool_size (1) The maximum number of socket connections
|
|
59
|
-
#
|
|
65
|
+
# @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
|
|
66
|
+
# connection pool. Note: this setting is relevant only for multi-threaded applications.
|
|
67
|
+
# @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
|
|
60
68
|
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
|
|
69
|
+
# Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
|
|
61
70
|
#
|
|
62
71
|
# @example localhost, 27017
|
|
63
72
|
# Connection.new
|
|
@@ -71,71 +80,55 @@ module Mongo
|
|
|
71
80
|
# @example localhost, 3000, where this node may be a slave
|
|
72
81
|
# Connection.new("localhost", 3000, :slave_ok => true)
|
|
73
82
|
#
|
|
74
|
-
# @see http://
|
|
83
|
+
# @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
|
|
84
|
+
#
|
|
85
|
+
# @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
|
|
86
|
+
# driver fails to connect to a replica set with that name.
|
|
75
87
|
#
|
|
76
88
|
# @core connections
|
|
77
89
|
def initialize(host=nil, port=nil, options={})
|
|
78
|
-
@
|
|
79
|
-
|
|
80
|
-
if block_given?
|
|
81
|
-
@nodes = yield self
|
|
82
|
-
else
|
|
83
|
-
@nodes = format_pair(host, port)
|
|
84
|
-
end
|
|
90
|
+
@host_to_try = format_pair(host, port)
|
|
85
91
|
|
|
86
92
|
# Host and port of current master.
|
|
87
93
|
@host = @port = nil
|
|
88
94
|
|
|
89
|
-
# Lock for request ids.
|
|
90
|
-
@id_lock = Mutex.new
|
|
91
|
-
|
|
92
|
-
# Pool size and timeout.
|
|
93
|
-
@size = options[:pool_size] || 1
|
|
94
|
-
@timeout = options[:timeout] || 5.0
|
|
95
|
-
|
|
96
|
-
# Mutex for synchronizing pool access
|
|
97
|
-
@connection_mutex = Mutex.new
|
|
98
|
-
@safe_mutex = Mutex.new
|
|
99
|
-
|
|
100
|
-
# Condition variable for signal and wait
|
|
101
|
-
@queue = ConditionVariable.new
|
|
102
|
-
|
|
103
|
-
@sockets = []
|
|
104
|
-
@checked_out = []
|
|
105
|
-
|
|
106
95
|
# slave_ok can be true only if one node is specified
|
|
107
|
-
@slave_ok = options[:slave_ok]
|
|
108
|
-
@logger = options[:logger] || nil
|
|
109
|
-
@options = options
|
|
96
|
+
@slave_ok = options[:slave_ok]
|
|
110
97
|
|
|
111
|
-
|
|
112
|
-
connect_to_master if should_connect
|
|
98
|
+
setup(options)
|
|
113
99
|
end
|
|
114
100
|
|
|
115
|
-
#
|
|
101
|
+
# DEPRECATED
|
|
116
102
|
#
|
|
117
|
-
#
|
|
118
|
-
# @param opts Takes the same options as Connection.new
|
|
103
|
+
# Initialize a connection to a MongoDB replica set using an array of seed nodes.
|
|
119
104
|
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
105
|
+
# The seed nodes specified will be used on the initial connection to the replica set, but note
|
|
106
|
+
# that this list of nodes will be replced by the list of canonical nodes returned by running the
|
|
107
|
+
# is_master command on the replica set.
|
|
108
|
+
#
|
|
109
|
+
# @param nodes [Array] An array of arrays, each of which specifies a host and port.
|
|
110
|
+
# @param opts [Hash] Any of the available options that can be passed to Connection.new.
|
|
111
|
+
#
|
|
112
|
+
# @option options [String] :rs_name (nil) The name of the replica set to connect to. An exception will be
|
|
113
|
+
# raised if unable to connect to a replica set with this name.
|
|
114
|
+
# @option options [Boolean] :read_secondary (false) When true, this connection object will pick a random slave
|
|
115
|
+
# to send reads to.
|
|
123
116
|
#
|
|
124
117
|
# @example
|
|
125
|
-
# Connection.
|
|
126
|
-
#
|
|
127
|
-
#
|
|
118
|
+
# Connection.multi([["db1.example.com", 27017], ["db2.example.com", 27017]])
|
|
119
|
+
#
|
|
120
|
+
# @example This connection will read from a random secondary node.
|
|
121
|
+
# Connection.multi([["db1.example.com", 27017], ["db2.example.com", 27017], ["db3.example.com", 27017]],
|
|
122
|
+
# :read_secondary => true)
|
|
128
123
|
#
|
|
129
124
|
# @return [Mongo::Connection]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
new(
|
|
137
|
-
[con.pair_val_to_connection(nodes[0]), con.pair_val_to_connection(nodes[1])]
|
|
138
|
-
end
|
|
125
|
+
#
|
|
126
|
+
# @deprecated
|
|
127
|
+
def self.multi(nodes, opts={})
|
|
128
|
+
warn "Connection.multi is now deprecated. Please use ReplSetConnection.new instead."
|
|
129
|
+
|
|
130
|
+
nodes << opts
|
|
131
|
+
ReplSetConnection.new(*nodes)
|
|
139
132
|
end
|
|
140
133
|
|
|
141
134
|
# Initialize a connection to MongoDB using the MongoDB URI spec:
|
|
@@ -147,11 +140,43 @@ module Mongo
|
|
|
147
140
|
#
|
|
148
141
|
# @return [Mongo::Connection]
|
|
149
142
|
def self.from_uri(uri, opts={})
|
|
150
|
-
|
|
151
|
-
|
|
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."
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
# Fsync, then lock the mongod process against writes. Use this to get
|
|
156
|
+
# the datafiles in a state safe for snapshotting, backing up, etc.
|
|
157
|
+
#
|
|
158
|
+
# @return [BSON::OrderedHash] the command response
|
|
159
|
+
def lock!
|
|
160
|
+
cmd = BSON::OrderedHash.new
|
|
161
|
+
cmd[:fsync] = 1
|
|
162
|
+
cmd[:lock] = true
|
|
163
|
+
self['admin'].command(cmd)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Is this database locked against writes?
|
|
167
|
+
#
|
|
168
|
+
# @return [Boolean]
|
|
169
|
+
def locked?
|
|
170
|
+
self['admin']['$cmd.sys.inprog'].find_one['fsyncLock'] == 1
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Unlock a previously fsync-locked mongod process.
|
|
174
|
+
#
|
|
175
|
+
# @return [BSON::OrderedHash] command response
|
|
176
|
+
def unlock!
|
|
177
|
+
self['admin']['$cmd.sys.unlock'].find_one
|
|
178
|
+
end
|
|
179
|
+
|
|
155
180
|
# Apply each of the saved database authentications.
|
|
156
181
|
#
|
|
157
182
|
# @return [Boolean] returns true if authentications exist and succeeed, false
|
|
@@ -172,6 +197,9 @@ module Mongo
|
|
|
172
197
|
# specificed in the list of auths. This method is called automatically
|
|
173
198
|
# by DB#authenticate.
|
|
174
199
|
#
|
|
200
|
+
# Note: this method will not actually issue an authentication command. To do that,
|
|
201
|
+
# either run Connection#apply_saved_authentication or DB#authenticate.
|
|
202
|
+
#
|
|
175
203
|
# @param [String] db_name
|
|
176
204
|
# @param [String] username
|
|
177
205
|
# @param [String] password
|
|
@@ -214,9 +242,9 @@ module Mongo
|
|
|
214
242
|
#
|
|
215
243
|
# @return [Hash]
|
|
216
244
|
def database_info
|
|
217
|
-
doc = self['admin'].command({:listDatabases => 1}
|
|
218
|
-
|
|
219
|
-
|
|
245
|
+
doc = self['admin'].command({:listDatabases => 1})
|
|
246
|
+
doc['databases'].each_with_object({}) do |db, info|
|
|
247
|
+
info[db['name']] = db['sizeOnDisk'].to_i
|
|
220
248
|
end
|
|
221
249
|
end
|
|
222
250
|
|
|
@@ -236,7 +264,7 @@ module Mongo
|
|
|
236
264
|
#
|
|
237
265
|
# @core databases db-instance_method
|
|
238
266
|
def db(db_name, options={})
|
|
239
|
-
DB.new(db_name, self, options
|
|
267
|
+
DB.new(db_name, self, options)
|
|
240
268
|
end
|
|
241
269
|
|
|
242
270
|
# Shortcut for returning a database. Use DB#db to accept options.
|
|
@@ -247,7 +275,7 @@ module Mongo
|
|
|
247
275
|
#
|
|
248
276
|
# @core databases []-instance_method
|
|
249
277
|
def [](db_name)
|
|
250
|
-
DB.new(db_name, self, :
|
|
278
|
+
DB.new(db_name, self, :safe => @safe)
|
|
251
279
|
end
|
|
252
280
|
|
|
253
281
|
# Drop a database.
|
|
@@ -266,7 +294,7 @@ module Mongo
|
|
|
266
294
|
# @param [String] username username for authentication against from_db (>=1.3.x).
|
|
267
295
|
# @param [String] password password for authentication against from_db (>=1.3.x).
|
|
268
296
|
def copy_database(from, to, from_host="localhost", username=nil, password=nil)
|
|
269
|
-
oh = OrderedHash.new
|
|
297
|
+
oh = BSON::OrderedHash.new
|
|
270
298
|
oh[:copydb] = 1
|
|
271
299
|
oh[:fromhost] = from_host
|
|
272
300
|
oh[:fromdb] = from
|
|
@@ -275,33 +303,22 @@ module Mongo
|
|
|
275
303
|
unless username && password
|
|
276
304
|
raise MongoArgumentError, "Both username and password must be supplied for authentication."
|
|
277
305
|
end
|
|
278
|
-
nonce_cmd = OrderedHash.new
|
|
306
|
+
nonce_cmd = BSON::OrderedHash.new
|
|
279
307
|
nonce_cmd[:copydbgetnonce] = 1
|
|
280
308
|
nonce_cmd[:fromhost] = from_host
|
|
281
|
-
result = self["admin"].command(nonce_cmd
|
|
309
|
+
result = self["admin"].command(nonce_cmd)
|
|
282
310
|
oh[:nonce] = result["nonce"]
|
|
283
311
|
oh[:username] = username
|
|
284
312
|
oh[:key] = Mongo::Support.auth_key(username, password, oh[:nonce])
|
|
285
313
|
end
|
|
286
|
-
self["admin"].command(oh
|
|
314
|
+
self["admin"].command(oh)
|
|
287
315
|
end
|
|
288
316
|
|
|
289
|
-
|
|
290
|
-
#
|
|
291
|
-
# return [Integer]
|
|
292
|
-
def get_request_id
|
|
293
|
-
request_id = ''
|
|
294
|
-
@id_lock.synchronize do
|
|
295
|
-
request_id = @@current_request_id += 1
|
|
296
|
-
end
|
|
297
|
-
request_id
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
# Get the build information for the current connection.
|
|
317
|
+
# Get the build information for the current connection.
|
|
301
318
|
#
|
|
302
319
|
# @return [Hash]
|
|
303
320
|
def server_info
|
|
304
|
-
self["admin"].command({:buildinfo => 1}
|
|
321
|
+
self["admin"].command({:buildinfo => 1})
|
|
305
322
|
end
|
|
306
323
|
|
|
307
324
|
# Get the build version of the current server.
|
|
@@ -319,24 +336,20 @@ module Mongo
|
|
|
319
336
|
@slave_ok
|
|
320
337
|
end
|
|
321
338
|
|
|
322
|
-
|
|
323
|
-
## Connections and pooling ##
|
|
324
|
-
|
|
325
339
|
# Send a message to MongoDB, adding the necessary headers.
|
|
326
340
|
#
|
|
327
341
|
# @param [Integer] operation a MongoDB opcode.
|
|
328
342
|
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
329
|
-
# @param [String] log_message text version of +message+ for logging.
|
|
330
343
|
#
|
|
331
|
-
# @return [
|
|
344
|
+
# @return [Integer] number of bytes sent
|
|
332
345
|
def send_message(operation, message, log_message=nil)
|
|
333
|
-
@logger.debug(" MONGODB #{log_message || message}") if @logger
|
|
334
346
|
begin
|
|
335
|
-
|
|
336
|
-
|
|
347
|
+
add_message_headers(message, operation)
|
|
348
|
+
packed_message = message.to_s
|
|
349
|
+
socket = checkout_writer
|
|
337
350
|
send_message_on_socket(packed_message, socket)
|
|
338
351
|
ensure
|
|
339
|
-
|
|
352
|
+
checkin_writer(socket)
|
|
340
353
|
end
|
|
341
354
|
end
|
|
342
355
|
|
|
@@ -346,55 +359,62 @@ module Mongo
|
|
|
346
359
|
# @param [Integer] operation a MongoDB opcode.
|
|
347
360
|
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
348
361
|
# @param [String] db_name the name of the database. used on call to get_last_error.
|
|
349
|
-
# @param [
|
|
362
|
+
# @param [Hash] last_error_params parameters to be sent to getLastError. See DB#error for
|
|
363
|
+
# available options.
|
|
350
364
|
#
|
|
351
|
-
# @
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
def send_message_with_safe_check(operation, message, db_name, log_message=nil)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
365
|
+
# @see DB#get_last_error for valid last error params.
|
|
366
|
+
#
|
|
367
|
+
# @return [Hash] The document returned by the call to getlasterror.
|
|
368
|
+
def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false)
|
|
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
|
|
358
377
|
begin
|
|
359
|
-
sock =
|
|
360
|
-
|
|
361
|
-
docs = num_received = cursor_id = ''
|
|
362
|
-
@safe_mutex.synchronize do
|
|
378
|
+
sock = checkout_writer
|
|
379
|
+
@safe_mutexes[sock].synchronize do
|
|
363
380
|
send_message_on_socket(packed_message, sock)
|
|
364
|
-
docs, num_received, cursor_id = receive(sock)
|
|
381
|
+
docs, num_received, cursor_id = receive(sock, last_error_id)
|
|
365
382
|
end
|
|
366
383
|
ensure
|
|
367
|
-
|
|
384
|
+
checkin_writer(sock)
|
|
368
385
|
end
|
|
369
|
-
|
|
370
|
-
|
|
386
|
+
|
|
387
|
+
if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
|
|
388
|
+
close if error == "not master"
|
|
389
|
+
error = "wtimeout" if error == "timeout"
|
|
390
|
+
raise Mongo::OperationFailure, docs[0]['code'].to_s + ': ' + error
|
|
371
391
|
end
|
|
372
|
-
|
|
392
|
+
|
|
393
|
+
docs[0]
|
|
373
394
|
end
|
|
374
395
|
|
|
375
396
|
# Sends a message to the database and waits for the response.
|
|
376
397
|
#
|
|
377
398
|
# @param [Integer] operation a MongoDB opcode.
|
|
378
399
|
# @param [BSON::ByteBuffer] message a message to send to the database.
|
|
379
|
-
# @param [String] log_message text version of +message+ for logging.
|
|
380
400
|
# @param [Socket] socket a socket to use in lieu of checking out a new one.
|
|
381
401
|
#
|
|
382
402
|
# @return [Array]
|
|
383
403
|
# An array whose indexes include [0] documents returned, [1] number of document received,
|
|
384
404
|
# and [3] a cursor_id.
|
|
385
|
-
def receive_message(operation, message, log_message=nil, socket=nil)
|
|
386
|
-
|
|
387
|
-
|
|
405
|
+
def receive_message(operation, message, log_message=nil, socket=nil, command=false)
|
|
406
|
+
request_id = add_message_headers(message, operation)
|
|
407
|
+
packed_message = message.to_s
|
|
388
408
|
begin
|
|
389
|
-
sock = socket ||
|
|
409
|
+
sock = socket || (command ? checkout_writer : checkout_reader)
|
|
390
410
|
|
|
391
411
|
result = ''
|
|
392
|
-
@
|
|
412
|
+
@safe_mutexes[sock].synchronize do
|
|
393
413
|
send_message_on_socket(packed_message, sock)
|
|
394
|
-
result = receive(sock)
|
|
414
|
+
result = receive(sock, request_id)
|
|
395
415
|
end
|
|
396
416
|
ensure
|
|
397
|
-
|
|
417
|
+
command ? checkin_writer(sock) : checkin_reader(sock)
|
|
398
418
|
end
|
|
399
419
|
result
|
|
400
420
|
end
|
|
@@ -402,282 +422,305 @@ module Mongo
|
|
|
402
422
|
# Create a new socket and attempt to connect to master.
|
|
403
423
|
# If successful, sets host and port to master and returns the socket.
|
|
404
424
|
#
|
|
425
|
+
# If connecting to a replica set, this method will replace the
|
|
426
|
+
# initially-provided seed list with any nodes known to the set.
|
|
427
|
+
#
|
|
405
428
|
# @raise [ConnectionFailure] if unable to connect to any host or port.
|
|
406
|
-
def
|
|
407
|
-
|
|
408
|
-
@host = @port = nil
|
|
409
|
-
for node_pair in @nodes
|
|
410
|
-
host, port = *node_pair
|
|
411
|
-
begin
|
|
412
|
-
socket = TCPSocket.new(host, port)
|
|
413
|
-
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
414
|
-
|
|
415
|
-
# If we're connected to master, set the @host and @port
|
|
416
|
-
result = self['admin'].command({:ismaster => 1}, false, false, socket)
|
|
417
|
-
if result['ok'] == 1 && ((is_master = result['ismaster'] == 1) || @slave_ok)
|
|
418
|
-
@host, @port = host, port
|
|
419
|
-
apply_saved_authentication
|
|
420
|
-
end
|
|
429
|
+
def connect
|
|
430
|
+
reset_connection
|
|
421
431
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
end
|
|
432
|
+
config = check_is_master(@host_to_try)
|
|
433
|
+
if is_primary?(config)
|
|
434
|
+
set_primary(@host_to_try)
|
|
435
|
+
end
|
|
427
436
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
socket.close if socket
|
|
431
|
-
close
|
|
432
|
-
false
|
|
433
|
-
end
|
|
437
|
+
if !connected?
|
|
438
|
+
raise ConnectionFailure, "Failed to connect to a master node at #{@host_to_try[0]}:#{@host_to_try[1]}"
|
|
434
439
|
end
|
|
435
|
-
raise ConnectionFailure, "failed to connect to any given host:port" unless socket
|
|
436
440
|
end
|
|
437
441
|
|
|
438
|
-
|
|
439
|
-
|
|
442
|
+
def connecting?
|
|
443
|
+
@nodes_to_try.length > 0
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# It's possible that we defined connected as all nodes being connected???
|
|
447
|
+
# NOTE: Do check if this needs to be more stringent.
|
|
448
|
+
# Probably not since if any node raises a connection failure, all nodes will be closed.
|
|
440
449
|
def connected?
|
|
441
|
-
@host && @port
|
|
450
|
+
@primary_pool && @primary_pool.host && @primary_pool.port
|
|
442
451
|
end
|
|
443
452
|
|
|
444
453
|
# Close the connection to the database.
|
|
445
454
|
def close
|
|
446
|
-
@
|
|
447
|
-
|
|
448
|
-
end
|
|
449
|
-
@host = @port = nil
|
|
450
|
-
@sockets.clear
|
|
451
|
-
@checked_out.clear
|
|
455
|
+
@primary_pool.close if @primary_pool
|
|
456
|
+
@primary_pool = nil
|
|
452
457
|
end
|
|
453
458
|
|
|
454
|
-
|
|
459
|
+
# Checkout a socket for reading (i.e., a secondary node).
|
|
460
|
+
# Note: this is overridden in ReplSetConnection.
|
|
461
|
+
def checkout_reader
|
|
462
|
+
connect unless connected?
|
|
463
|
+
@primary_pool.checkout
|
|
464
|
+
end
|
|
455
465
|
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
when String
|
|
462
|
-
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
|
|
463
|
-
when nil
|
|
464
|
-
[['localhost', DEFAULT_PORT]]
|
|
465
|
-
end
|
|
466
|
+
# Checkout a socket for writing (i.e., a primary node).
|
|
467
|
+
# Note: this is overridden in ReplSetConnection.
|
|
468
|
+
def checkout_writer
|
|
469
|
+
connect unless connected?
|
|
470
|
+
@primary_pool.checkout
|
|
466
471
|
end
|
|
467
472
|
|
|
468
|
-
#
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
case a
|
|
474
|
-
when nil
|
|
475
|
-
['localhost', DEFAULT_PORT]
|
|
476
|
-
when String
|
|
477
|
-
[a, DEFAULT_PORT]
|
|
478
|
-
when Integer
|
|
479
|
-
['localhost', a]
|
|
480
|
-
when Array
|
|
481
|
-
a
|
|
473
|
+
# Checkin a socket used for reading.
|
|
474
|
+
# Note: this is overridden in ReplSetConnection.
|
|
475
|
+
def checkin_reader(socket)
|
|
476
|
+
if @primary_pool
|
|
477
|
+
@primary_pool.checkin(socket)
|
|
482
478
|
end
|
|
483
479
|
end
|
|
484
480
|
|
|
485
|
-
#
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if string =~ /^mongodb:\/\//
|
|
491
|
-
string = string[10..-1]
|
|
492
|
-
else
|
|
493
|
-
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
|
481
|
+
# Checkin a socket used for writing.
|
|
482
|
+
# Note: this is overridden in ReplSetConnection.
|
|
483
|
+
def checkin_writer(socket)
|
|
484
|
+
if @primary_pool
|
|
485
|
+
@primary_pool.checkin(socket)
|
|
494
486
|
end
|
|
487
|
+
end
|
|
495
488
|
|
|
496
|
-
|
|
497
|
-
auths = []
|
|
498
|
-
specs = string.split(',')
|
|
499
|
-
specs.each do |spec|
|
|
500
|
-
matches = MONGODB_URI_MATCHER.match(spec)
|
|
501
|
-
if !matches
|
|
502
|
-
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
|
503
|
-
end
|
|
489
|
+
protected
|
|
504
490
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
|
|
511
|
-
end
|
|
512
|
-
port = port.to_i
|
|
513
|
-
db = matches[8]
|
|
514
|
-
|
|
515
|
-
if (uname || pwd || db) && !(uname && pwd && db)
|
|
516
|
-
raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
|
|
517
|
-
"and db if any one of these is specified."
|
|
518
|
-
else
|
|
519
|
-
add_auth(db, uname, pwd)
|
|
520
|
-
end
|
|
491
|
+
# Generic initialization code.
|
|
492
|
+
# @protected
|
|
493
|
+
def setup(options)
|
|
494
|
+
# Authentication objects
|
|
495
|
+
@auths = options.fetch(:auths, [])
|
|
521
496
|
|
|
522
|
-
|
|
523
|
-
|
|
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
|
|
524
522
|
|
|
525
|
-
|
|
523
|
+
should_connect = options.fetch(:connect, true)
|
|
524
|
+
connect if should_connect
|
|
526
525
|
end
|
|
527
526
|
|
|
528
|
-
|
|
527
|
+
## Configuration helper methods
|
|
529
528
|
|
|
530
|
-
#
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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]
|
|
535
540
|
end
|
|
536
|
-
true
|
|
537
541
|
end
|
|
538
542
|
|
|
539
|
-
|
|
543
|
+
private
|
|
544
|
+
|
|
545
|
+
## Methods for establishing a connection:
|
|
546
|
+
|
|
547
|
+
# If a ConnectionFailure is raised, this method will be called
|
|
548
|
+
# to close the connection and reset connection values.
|
|
549
|
+
# TODO: evaluate whether this method is actually necessary
|
|
550
|
+
def reset_connection
|
|
551
|
+
close
|
|
552
|
+
@primary = nil
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Primary is defined as either a master node or a slave if
|
|
556
|
+
# :slave_ok has been set to +true+.
|
|
540
557
|
#
|
|
541
|
-
#
|
|
542
|
-
#
|
|
543
|
-
|
|
558
|
+
# If a primary node is discovered, we set the the @host and @port and
|
|
559
|
+
# apply any saved authentication.
|
|
560
|
+
# TODO: simplify
|
|
561
|
+
def is_primary?(config)
|
|
562
|
+
config && (config['ismaster'] == 1 || config['ismaster'] == true) || @slave_ok
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def check_is_master(node)
|
|
544
566
|
begin
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
raise ConnectionFailure, "Failed to connect socket: #{ex}"
|
|
549
|
-
end
|
|
550
|
-
@sockets << socket
|
|
551
|
-
@checked_out << socket
|
|
552
|
-
socket
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
# Checks out the first available socket from the pool.
|
|
556
|
-
#
|
|
557
|
-
# This method is called exclusively from #checkout;
|
|
558
|
-
# therefore, it runs within a mutex.
|
|
559
|
-
def checkout_existing_socket
|
|
560
|
-
socket = (@sockets - @checked_out).first
|
|
561
|
-
@checked_out << socket
|
|
562
|
-
socket
|
|
563
|
-
end
|
|
564
|
-
|
|
565
|
-
# Check out an existing socket or create a new socket if the maximum
|
|
566
|
-
# pool size has not been exceeded. Otherwise, wait for the next
|
|
567
|
-
# available socket.
|
|
568
|
-
def checkout
|
|
569
|
-
connect_to_master if !connected?
|
|
570
|
-
start_time = Time.now
|
|
571
|
-
loop do
|
|
572
|
-
if (Time.now - start_time) > @timeout
|
|
573
|
-
raise ConnectionTimeoutError, "could not obtain connection within " +
|
|
574
|
-
"#{@timeout} seconds. The max pool size is currently #{@size}; " +
|
|
575
|
-
"consider increasing the pool size or timeout."
|
|
576
|
-
end
|
|
567
|
+
host, port = *node
|
|
568
|
+
socket = TCPSocket.new(host, port)
|
|
569
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
577
570
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
571
|
+
config = self['admin'].command({:ismaster => 1}, :sock => socket)
|
|
572
|
+
rescue OperationFailure, SocketError, SystemCallError, IOError => ex
|
|
573
|
+
close
|
|
574
|
+
ensure
|
|
575
|
+
socket.close if socket
|
|
576
|
+
end
|
|
584
577
|
|
|
585
|
-
|
|
578
|
+
config
|
|
579
|
+
end
|
|
586
580
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
581
|
+
# Set the specified node as primary, and
|
|
582
|
+
# apply any saved authentication credentials.
|
|
583
|
+
def set_primary(node)
|
|
584
|
+
host, port = *node
|
|
585
|
+
@primary = [host, port]
|
|
586
|
+
@primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
|
|
587
|
+
apply_saved_authentication
|
|
594
588
|
end
|
|
595
589
|
|
|
596
|
-
|
|
597
|
-
|
|
590
|
+
|
|
591
|
+
## Low-level connection methods.
|
|
592
|
+
|
|
593
|
+
def receive(sock, expected_response)
|
|
594
|
+
begin
|
|
595
|
+
receive_header(sock, expected_response)
|
|
598
596
|
number_received, cursor_id = receive_response_header(sock)
|
|
599
597
|
read_documents(number_received, cursor_id, sock)
|
|
598
|
+
rescue Mongo::ConnectionFailure => ex
|
|
599
|
+
close
|
|
600
|
+
raise ex
|
|
601
|
+
end
|
|
600
602
|
end
|
|
601
603
|
|
|
602
|
-
def receive_header(sock)
|
|
603
|
-
header =
|
|
604
|
-
|
|
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}"
|
|
609
|
+
end
|
|
610
|
+
|
|
605
611
|
unless header.size == STANDARD_HEADER_SIZE
|
|
606
612
|
raise "Short read for DB response header: " +
|
|
607
613
|
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
|
608
614
|
end
|
|
609
|
-
|
|
610
|
-
size = header.get_int
|
|
611
|
-
request_id = header.get_int
|
|
612
|
-
response_to = header.get_int
|
|
613
|
-
op = header.get_int
|
|
615
|
+
nil
|
|
614
616
|
end
|
|
615
617
|
|
|
616
618
|
def receive_response_header(sock)
|
|
617
|
-
header_buf =
|
|
618
|
-
header_buf.put_array(receive_message_on_socket(RESPONSE_HEADER_SIZE, sock).unpack("C*"))
|
|
619
|
+
header_buf = receive_message_on_socket(RESPONSE_HEADER_SIZE, sock)
|
|
619
620
|
if header_buf.length != RESPONSE_HEADER_SIZE
|
|
620
621
|
raise "Short read for DB response header; " +
|
|
621
622
|
"expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
|
|
622
623
|
end
|
|
623
|
-
header_buf.
|
|
624
|
-
|
|
625
|
-
cursor_id
|
|
626
|
-
starting_from = header_buf.get_int
|
|
627
|
-
number_remaining = header_buf.get_int
|
|
624
|
+
flags, cursor_id_a, cursor_id_b, starting_from, number_remaining = header_buf.unpack('VVVVV')
|
|
625
|
+
check_response_flags(flags)
|
|
626
|
+
cursor_id = (cursor_id_b << 32) + cursor_id_a
|
|
628
627
|
[number_remaining, cursor_id]
|
|
629
628
|
end
|
|
630
629
|
|
|
630
|
+
def check_response_flags(flags)
|
|
631
|
+
if flags & Mongo::Constants::REPLY_CURSOR_NOT_FOUND != 0
|
|
632
|
+
raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
|
|
633
|
+
"Either an invalid cursor was specified, or the cursor may have timed out on the server."
|
|
634
|
+
elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
|
|
635
|
+
# Getting odd failures when a exception is raised here.
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
631
639
|
def read_documents(number_received, cursor_id, sock)
|
|
632
640
|
docs = []
|
|
633
641
|
number_remaining = number_received
|
|
634
642
|
while number_remaining > 0 do
|
|
635
|
-
buf =
|
|
636
|
-
buf.
|
|
637
|
-
buf
|
|
638
|
-
size = buf.get_int
|
|
639
|
-
buf.put_array(receive_message_on_socket(size - 4, sock).unpack("C*"), 4)
|
|
643
|
+
buf = receive_message_on_socket(4, sock)
|
|
644
|
+
size = buf.unpack('V')[0]
|
|
645
|
+
buf << receive_message_on_socket(size - 4, sock)
|
|
640
646
|
number_remaining -= 1
|
|
641
|
-
buf.rewind
|
|
642
647
|
docs << BSON::BSON_CODER.deserialize(buf)
|
|
643
648
|
end
|
|
644
649
|
[docs, number_received, cursor_id]
|
|
645
650
|
end
|
|
646
651
|
|
|
647
|
-
|
|
648
|
-
|
|
652
|
+
# Constructs a getlasterror message. This method is used exclusively by
|
|
653
|
+
# Connection#send_message_with_safe_check.
|
|
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)
|
|
649
657
|
message.put_int(0)
|
|
650
658
|
BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
|
|
651
659
|
message.put_int(0)
|
|
652
660
|
message.put_int(-1)
|
|
653
|
-
|
|
654
|
-
|
|
661
|
+
cmd = BSON::OrderedHash.new
|
|
662
|
+
cmd[:getlasterror] = 1
|
|
663
|
+
if opts.is_a?(Hash)
|
|
664
|
+
opts.assert_valid_keys(:w, :wtimeout, :fsync)
|
|
665
|
+
cmd.merge!(opts)
|
|
666
|
+
end
|
|
667
|
+
message.put_binary(BSON::BSON_CODER.serialize(cmd, false).to_s)
|
|
668
|
+
nil
|
|
655
669
|
end
|
|
656
670
|
|
|
657
671
|
# Prepares a message for transmission to MongoDB by
|
|
658
672
|
# constructing a valid message header.
|
|
659
|
-
|
|
660
|
-
|
|
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)
|
|
678
|
+
headers = [
|
|
679
|
+
# Message size.
|
|
680
|
+
16 + message.size,
|
|
661
681
|
|
|
662
|
-
|
|
663
|
-
|
|
682
|
+
# Unique request id.
|
|
683
|
+
request_id = get_request_id,
|
|
664
684
|
|
|
665
|
-
|
|
666
|
-
|
|
685
|
+
# Response id.
|
|
686
|
+
0,
|
|
667
687
|
|
|
668
|
-
|
|
669
|
-
|
|
688
|
+
# Opcode.
|
|
689
|
+
operation
|
|
690
|
+
].pack('VVVV')
|
|
670
691
|
|
|
671
|
-
# Opcode.
|
|
672
|
-
headers.put_int(operation)
|
|
673
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
|
|
674
706
|
end
|
|
675
707
|
|
|
676
708
|
# Low-level method for sending a message on a socket.
|
|
677
709
|
# Requires a packed message and an available socket,
|
|
710
|
+
#
|
|
711
|
+
# @return [Integer] number of bytes sent
|
|
678
712
|
def send_message_on_socket(packed_message, socket)
|
|
679
713
|
begin
|
|
680
|
-
socket.send(packed_message, 0)
|
|
714
|
+
total_bytes_sent = socket.send(packed_message, 0)
|
|
715
|
+
if total_bytes_sent != packed_message.size
|
|
716
|
+
packed_message.slice!(0, total_bytes_sent)
|
|
717
|
+
while packed_message.size > 0
|
|
718
|
+
byte_sent = socket.send(packed_message, 0)
|
|
719
|
+
total_bytes_sent += byte_sent
|
|
720
|
+
packed_message.slice!(0, byte_sent)
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
total_bytes_sent
|
|
681
724
|
rescue => ex
|
|
682
725
|
close
|
|
683
726
|
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
|
@@ -687,12 +730,16 @@ module Mongo
|
|
|
687
730
|
# Low-level method for receiving data from socket.
|
|
688
731
|
# Requires length and an available socket.
|
|
689
732
|
def receive_message_on_socket(length, socket)
|
|
690
|
-
message = ""
|
|
691
733
|
begin
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
734
|
+
message = socket.read(length)
|
|
735
|
+
raise ConnectionFailure, "connection closed" unless message.length > 0
|
|
736
|
+
if message.length < length
|
|
737
|
+
chunk = new_binary_string
|
|
738
|
+
while message.length < length
|
|
739
|
+
socket.read(length - message.length, chunk)
|
|
740
|
+
raise ConnectionFailure, "connection closed" unless chunk.length > 0
|
|
741
|
+
message << chunk
|
|
742
|
+
end
|
|
696
743
|
end
|
|
697
744
|
rescue => ex
|
|
698
745
|
close
|
|
@@ -700,5 +747,17 @@ module Mongo
|
|
|
700
747
|
end
|
|
701
748
|
message
|
|
702
749
|
end
|
|
750
|
+
|
|
751
|
+
if defined?(Encoding)
|
|
752
|
+
BINARY_ENCODING = Encoding.find("binary")
|
|
753
|
+
|
|
754
|
+
def new_binary_string
|
|
755
|
+
"".force_encoding(BINARY_ENCODING)
|
|
756
|
+
end
|
|
757
|
+
else
|
|
758
|
+
def new_binary_string
|
|
759
|
+
""
|
|
760
|
+
end
|
|
761
|
+
end
|
|
703
762
|
end
|
|
704
763
|
end
|