mongo 1.4.0 → 1.5.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- data/docs/HISTORY.md +15 -0
- data/docs/REPLICA_SETS.md +19 -7
- data/lib/mongo.rb +1 -0
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/connection.rb +29 -351
- data/lib/mongo/cursor.rb +88 -6
- data/lib/mongo/gridfs/grid.rb +4 -2
- data/lib/mongo/gridfs/grid_file_system.rb +4 -2
- data/lib/mongo/networking.rb +345 -0
- data/lib/mongo/repl_set_connection.rb +236 -191
- data/lib/mongo/util/core_ext.rb +45 -0
- data/lib/mongo/util/logging.rb +5 -0
- data/lib/mongo/util/node.rb +6 -4
- data/lib/mongo/util/pool.rb +73 -26
- data/lib/mongo/util/pool_manager.rb +100 -30
- data/lib/mongo/util/uri_parser.rb +29 -21
- data/lib/mongo/version.rb +1 -1
- data/test/bson/binary_test.rb +6 -8
- data/test/bson/bson_test.rb +1 -0
- data/test/bson/ordered_hash_test.rb +2 -0
- data/test/bson/test_helper.rb +0 -17
- data/test/collection_test.rb +22 -0
- data/test/connection_test.rb +1 -1
- data/test/cursor_test.rb +3 -3
- data/test/load/thin/load.rb +4 -7
- data/test/replica_sets/basic_test.rb +46 -0
- data/test/replica_sets/connect_test.rb +35 -58
- data/test/replica_sets/count_test.rb +15 -6
- data/test/replica_sets/insert_test.rb +6 -7
- data/test/replica_sets/query_test.rb +4 -6
- data/test/replica_sets/read_preference_test.rb +112 -8
- data/test/replica_sets/refresh_test.rb +66 -36
- data/test/replica_sets/refresh_with_threads_test.rb +55 -0
- data/test/replica_sets/replication_ack_test.rb +3 -6
- data/test/replica_sets/rs_test_helper.rb +12 -6
- data/test/replica_sets/threading_test.rb +111 -0
- data/test/test_helper.rb +9 -2
- data/test/threading_test.rb +14 -6
- data/test/tools/repl_set_manager.rb +55 -40
- data/test/unit/collection_test.rb +2 -1
- data/test/unit/connection_test.rb +8 -8
- data/test/unit/grid_test.rb +4 -2
- data/test/unit/pool_manager_test.rb +1 -0
- data/test/unit/read_test.rb +17 -5
- data/test/uri_test.rb +9 -4
- metadata +13 -28
- data/test/replica_sets/connection_string_test.rb +0 -29
- data/test/replica_sets/pooled_insert_test.rb +0 -58
- data/test/replica_sets/query_secondaries.rb +0 -109
data/lib/mongo/cursor.rb
CHANGED
@@ -26,7 +26,7 @@ module Mongo
|
|
26
26
|
attr_reader :collection, :selector, :fields,
|
27
27
|
:order, :hint, :snapshot, :timeout,
|
28
28
|
:full_collection_name, :transformer,
|
29
|
-
:options, :cursor_id
|
29
|
+
:options, :cursor_id, :show_disk_loc
|
30
30
|
|
31
31
|
# Create a new cursor.
|
32
32
|
#
|
@@ -98,6 +98,10 @@ module Mongo
|
|
98
98
|
else
|
99
99
|
@command = false
|
100
100
|
end
|
101
|
+
|
102
|
+
@checkin_read_pool = false
|
103
|
+
@checkin_connection = false
|
104
|
+
@read_pool = nil
|
101
105
|
end
|
102
106
|
|
103
107
|
# Guess whether the cursor is alive on the server.
|
@@ -460,10 +464,17 @@ module Mongo
|
|
460
464
|
def send_initial_query
|
461
465
|
message = construct_query_message
|
462
466
|
payload = instrument_payload if @logger
|
467
|
+
sock = @socket || checkout_socket_from_connection
|
463
468
|
instrument(:find, payload) do
|
469
|
+
begin
|
464
470
|
results, @n_received, @cursor_id = @connection.receive_message(
|
465
|
-
Mongo::Constants::OP_QUERY, message, nil,
|
466
|
-
|
471
|
+
Mongo::Constants::OP_QUERY, message, nil, sock, @command,
|
472
|
+
nil, @options & OP_QUERY_EXHAUST != 0)
|
473
|
+
rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
|
474
|
+
force_checkin_socket(sock)
|
475
|
+
raise ex
|
476
|
+
end
|
477
|
+
checkin_socket(sock) unless @socket
|
467
478
|
@returned += @n_received
|
468
479
|
@cache += results
|
469
480
|
@query_run = true
|
@@ -491,13 +502,83 @@ module Mongo
|
|
491
502
|
# Cursor id.
|
492
503
|
message.put_long(@cursor_id)
|
493
504
|
log(:debug, "cursor.refresh() for cursor #{@cursor_id}") if @logger
|
505
|
+
sock = @socket || checkout_socket_for_op_get_more
|
506
|
+
|
507
|
+
begin
|
494
508
|
results, @n_received, @cursor_id = @connection.receive_message(
|
495
|
-
|
509
|
+
Mongo::Constants::OP_GET_MORE, message, nil, sock, @command, nil)
|
510
|
+
rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
|
511
|
+
force_checkin_socket(sock)
|
512
|
+
raise ex
|
513
|
+
end
|
514
|
+
checkin_socket(sock) unless @socket
|
496
515
|
@returned += @n_received
|
497
516
|
@cache += results
|
498
517
|
close_cursor_if_query_complete
|
499
518
|
end
|
500
519
|
|
520
|
+
def checkout_socket_from_connection
|
521
|
+
@checkin_connection = true
|
522
|
+
if @command || @read_preference == :primary
|
523
|
+
@connection.checkout_writer
|
524
|
+
else
|
525
|
+
@read_pool = @connection.read_pool
|
526
|
+
@connection.checkout_reader
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def checkout_socket_for_op_get_more
|
531
|
+
if @read_pool && (@read_pool != @connection.read_pool)
|
532
|
+
checkout_socket_from_read_pool
|
533
|
+
else
|
534
|
+
checkout_socket_from_connection
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
def checkout_socket_from_read_pool
|
539
|
+
new_pool = @connection.secondary_pools.detect do |pool|
|
540
|
+
pool.host == @read_pool.host && pool.port == @read_pool.port
|
541
|
+
end
|
542
|
+
if new_pool
|
543
|
+
@read_pool = new_pool
|
544
|
+
sock = new_pool.checkout
|
545
|
+
@checkin_read_pool = true
|
546
|
+
return sock
|
547
|
+
else
|
548
|
+
raise Mongo::OperationFailure, "Failure to continue iterating " +
|
549
|
+
"cursor because the the replica set member persisting this " +
|
550
|
+
"cursor at #{@read_pool.host_string} cannot be found."
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
def checkin_socket(sock)
|
555
|
+
if @checkin_read_pool
|
556
|
+
@read_pool.checkin(sock)
|
557
|
+
@checkin_read_pool = false
|
558
|
+
elsif @checkin_connection
|
559
|
+
if @command || @read_preference == :primary
|
560
|
+
@connection.checkin_writer(sock)
|
561
|
+
else
|
562
|
+
@connection.checkin_reader(sock)
|
563
|
+
end
|
564
|
+
@checkin_connection = false
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
def force_checkin_socket(sock)
|
569
|
+
if @checkin_read_pool
|
570
|
+
@read_pool.checkin(sock)
|
571
|
+
@checkin_read_pool = false
|
572
|
+
else
|
573
|
+
if @command || @read_preference == :primary
|
574
|
+
@connection.checkin_writer(sock)
|
575
|
+
else
|
576
|
+
@connection.checkin_reader(sock)
|
577
|
+
end
|
578
|
+
@checkin_connection = false
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
501
582
|
def construct_query_message
|
502
583
|
message = BSON::ByteBuffer.new
|
503
584
|
message.put_int(@options)
|
@@ -527,7 +608,7 @@ module Mongo
|
|
527
608
|
spec['$hint'] = @hint if @hint && @hint.length > 0
|
528
609
|
spec['$explain'] = true if @explain
|
529
610
|
spec['$snapshot'] = true if @snapshot
|
530
|
-
spec['$
|
611
|
+
spec['$maxScan'] = @max_scan if @max_scan
|
531
612
|
spec['$returnKey'] = true if @return_key
|
532
613
|
spec['$showDiskLoc'] = true if @show_disk_loc
|
533
614
|
spec
|
@@ -535,7 +616,8 @@ module Mongo
|
|
535
616
|
|
536
617
|
# Returns true if the query contains order, explain, hint, or snapshot.
|
537
618
|
def query_contains_special_fields?
|
538
|
-
@order || @explain || @hint || @snapshot
|
619
|
+
@order || @explain || @hint || @snapshot || @show_disk_loc ||
|
620
|
+
@max_scan || @return_key
|
539
621
|
end
|
540
622
|
|
541
623
|
def close_cursor_if_query_complete
|
data/lib/mongo/gridfs/grid.rb
CHANGED
@@ -38,8 +38,10 @@ module Mongo
|
|
38
38
|
@chunks = @db["#{fs_name}.chunks"]
|
39
39
|
@fs_name = fs_name
|
40
40
|
|
41
|
-
#
|
42
|
-
|
41
|
+
# Create indexes only if we're connected to a primary node.
|
42
|
+
connection = @db.connection
|
43
|
+
if (connection.class == Connection && connection.read_primary?) ||
|
44
|
+
(connection.class == ReplSetConnection && connection.primary)
|
43
45
|
@chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
|
44
46
|
end
|
45
47
|
end
|
@@ -39,8 +39,10 @@ module Mongo
|
|
39
39
|
|
40
40
|
@default_query_opts = {:sort => [['filename', 1], ['uploadDate', -1]], :limit => 1}
|
41
41
|
|
42
|
-
#
|
43
|
-
|
42
|
+
# Create indexes only if we're connected to a primary node.
|
43
|
+
connection = @db.connection
|
44
|
+
if (connection.class == Connection && connection.read_primary?) ||
|
45
|
+
(connection.class == ReplSetConnection && connection.primary)
|
44
46
|
@files.create_index([['filename', 1], ['uploadDate', -1]])
|
45
47
|
@chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
|
46
48
|
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
module Mongo
|
2
|
+
module Networking
|
3
|
+
|
4
|
+
STANDARD_HEADER_SIZE = 16
|
5
|
+
RESPONSE_HEADER_SIZE = 20
|
6
|
+
|
7
|
+
# Counter for generating unique request ids.
|
8
|
+
@@current_request_id = 0
|
9
|
+
|
10
|
+
# Send a message to MongoDB, adding the necessary headers.
|
11
|
+
#
|
12
|
+
# @param [Integer] operation a MongoDB opcode.
|
13
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
14
|
+
#
|
15
|
+
# @option opts [Symbol] :connection (:writer) The connection to which
|
16
|
+
# this message should be sent. Valid options are :writer and :reader.
|
17
|
+
#
|
18
|
+
# @return [Integer] number of bytes sent
|
19
|
+
def send_message(operation, message, opts={})
|
20
|
+
if opts.is_a?(String)
|
21
|
+
warn "Connection#send_message no longer takes a string log message. " +
|
22
|
+
"Logging is now handled within the Collection and Cursor classes."
|
23
|
+
opts = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
connection = opts.fetch(:connection, :writer)
|
27
|
+
|
28
|
+
add_message_headers(message, operation)
|
29
|
+
packed_message = message.to_s
|
30
|
+
|
31
|
+
if connection == :writer
|
32
|
+
sock = checkout_writer
|
33
|
+
else
|
34
|
+
sock = checkout_reader
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
send_message_on_socket(packed_message, sock)
|
39
|
+
ensure
|
40
|
+
if connection == :writer
|
41
|
+
checkin_writer(sock)
|
42
|
+
else
|
43
|
+
checkin_reader(sock)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sends a message to the database, waits for a response, and raises
|
49
|
+
# an exception if the operation has failed.
|
50
|
+
#
|
51
|
+
# @param [Integer] operation a MongoDB opcode.
|
52
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
53
|
+
# @param [String] db_name the name of the database. used on call to get_last_error.
|
54
|
+
# @param [Hash] last_error_params parameters to be sent to getLastError. See DB#error for
|
55
|
+
# available options.
|
56
|
+
#
|
57
|
+
# @see DB#get_last_error for valid last error params.
|
58
|
+
#
|
59
|
+
# @return [Hash] The document returned by the call to getlasterror.
|
60
|
+
def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false)
|
61
|
+
docs = num_received = cursor_id = ''
|
62
|
+
add_message_headers(message, operation)
|
63
|
+
|
64
|
+
last_error_message = BSON::ByteBuffer.new
|
65
|
+
build_last_error_message(last_error_message, db_name, last_error_params)
|
66
|
+
last_error_id = add_message_headers(last_error_message, Mongo::Constants::OP_QUERY)
|
67
|
+
|
68
|
+
packed_message = message.append!(last_error_message).to_s
|
69
|
+
sock = checkout_writer
|
70
|
+
begin
|
71
|
+
send_message_on_socket(packed_message, sock)
|
72
|
+
docs, num_received, cursor_id = receive(sock, last_error_id)
|
73
|
+
checkin_writer(sock)
|
74
|
+
rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
|
75
|
+
checkin_writer(sock)
|
76
|
+
raise ex
|
77
|
+
end
|
78
|
+
|
79
|
+
if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
|
80
|
+
close if error == "not master"
|
81
|
+
error = "wtimeout" if error == "timeout"
|
82
|
+
raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
|
83
|
+
end
|
84
|
+
|
85
|
+
docs[0]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sends a message to the database and waits for the response.
|
89
|
+
#
|
90
|
+
# @param [Integer] operation a MongoDB opcode.
|
91
|
+
# @param [BSON::ByteBuffer] message a message to send to the database.
|
92
|
+
# @param [String] log_message this is currently a no-op and will be removed.
|
93
|
+
# @param [Socket] socket a socket to use in lieu of checking out a new one.
|
94
|
+
# @param [Boolean] command (false) indicate whether this is a command. If this is a command,
|
95
|
+
# the message will be sent to the primary node.
|
96
|
+
# @param [Boolean] command (false) indicate whether the cursor should be exhausted. Set
|
97
|
+
# this to true only when the OP_QUERY_EXHAUST flag is set.
|
98
|
+
#
|
99
|
+
# @return [Array]
|
100
|
+
# An array whose indexes include [0] documents returned, [1] number of document received,
|
101
|
+
# and [3] a cursor_id.
|
102
|
+
def receive_message(operation, message, log_message=nil, socket=nil, command=false,
|
103
|
+
read=:primary, exhaust=false)
|
104
|
+
request_id = add_message_headers(message, operation)
|
105
|
+
packed_message = message.to_s
|
106
|
+
if socket
|
107
|
+
sock = socket
|
108
|
+
should_checkin = false
|
109
|
+
else
|
110
|
+
if command || read == :primary
|
111
|
+
sock = checkout_writer
|
112
|
+
elsif read == :secondary
|
113
|
+
sock = checkout_reader
|
114
|
+
else
|
115
|
+
sock = checkout_tagged(read)
|
116
|
+
end
|
117
|
+
should_checkin = true
|
118
|
+
end
|
119
|
+
|
120
|
+
result = ''
|
121
|
+
begin
|
122
|
+
send_message_on_socket(packed_message, sock)
|
123
|
+
result = receive(sock, request_id, exhaust)
|
124
|
+
ensure
|
125
|
+
if should_checkin
|
126
|
+
if command || read == :primary
|
127
|
+
checkin_writer(sock)
|
128
|
+
elsif read == :secondary
|
129
|
+
checkin_reader(sock)
|
130
|
+
else
|
131
|
+
# TODO: sock = checkout_tagged(read)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def receive(sock, cursor_id, exhaust=false)
|
141
|
+
begin
|
142
|
+
if exhaust
|
143
|
+
docs = []
|
144
|
+
num_received = 0
|
145
|
+
|
146
|
+
while(cursor_id != 0) do
|
147
|
+
receive_header(sock, cursor_id, exhaust)
|
148
|
+
number_received, cursor_id = receive_response_header(sock)
|
149
|
+
new_docs, n = read_documents(number_received, sock)
|
150
|
+
docs += new_docs
|
151
|
+
num_received += n
|
152
|
+
end
|
153
|
+
|
154
|
+
return [docs, num_received, cursor_id]
|
155
|
+
else
|
156
|
+
receive_header(sock, cursor_id, exhaust)
|
157
|
+
number_received, cursor_id = receive_response_header(sock)
|
158
|
+
docs, num_received = read_documents(number_received, sock)
|
159
|
+
|
160
|
+
return [docs, num_received, cursor_id]
|
161
|
+
end
|
162
|
+
rescue Mongo::ConnectionFailure => ex
|
163
|
+
close
|
164
|
+
raise ex
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def receive_header(sock, expected_response, exhaust=false)
|
169
|
+
header = receive_message_on_socket(16, sock)
|
170
|
+
size, request_id, response_to = header.unpack('VVV')
|
171
|
+
if !exhaust && expected_response != response_to
|
172
|
+
raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
|
173
|
+
end
|
174
|
+
|
175
|
+
unless header.size == STANDARD_HEADER_SIZE
|
176
|
+
raise "Short read for DB response header: " +
|
177
|
+
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
178
|
+
end
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def receive_response_header(sock)
|
183
|
+
header_buf = receive_message_on_socket(RESPONSE_HEADER_SIZE, sock)
|
184
|
+
if header_buf.length != RESPONSE_HEADER_SIZE
|
185
|
+
raise "Short read for DB response header; " +
|
186
|
+
"expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
|
187
|
+
end
|
188
|
+
flags, cursor_id_a, cursor_id_b, starting_from, number_remaining = header_buf.unpack('VVVVV')
|
189
|
+
check_response_flags(flags)
|
190
|
+
cursor_id = (cursor_id_b << 32) + cursor_id_a
|
191
|
+
[number_remaining, cursor_id]
|
192
|
+
end
|
193
|
+
|
194
|
+
def check_response_flags(flags)
|
195
|
+
if flags & Mongo::Constants::REPLY_CURSOR_NOT_FOUND != 0
|
196
|
+
raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
|
197
|
+
"Either an invalid cursor was specified, or the cursor may have timed out on the server."
|
198
|
+
elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
|
199
|
+
# Getting odd failures when a exception is raised here.
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def read_documents(number_received, sock)
|
204
|
+
docs = []
|
205
|
+
number_remaining = number_received
|
206
|
+
while number_remaining > 0 do
|
207
|
+
buf = receive_message_on_socket(4, sock)
|
208
|
+
size = buf.unpack('V')[0]
|
209
|
+
buf << receive_message_on_socket(size - 4, sock)
|
210
|
+
number_remaining -= 1
|
211
|
+
docs << BSON::BSON_CODER.deserialize(buf)
|
212
|
+
end
|
213
|
+
[docs, number_received]
|
214
|
+
end
|
215
|
+
|
216
|
+
# Constructs a getlasterror message. This method is used exclusively by
|
217
|
+
# Connection#send_message_with_safe_check.
|
218
|
+
#
|
219
|
+
# Because it modifies message by reference, we don't need to return it.
|
220
|
+
def build_last_error_message(message, db_name, opts)
|
221
|
+
message.put_int(0)
|
222
|
+
BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
|
223
|
+
message.put_int(0)
|
224
|
+
message.put_int(-1)
|
225
|
+
cmd = BSON::OrderedHash.new
|
226
|
+
cmd[:getlasterror] = 1
|
227
|
+
if opts.is_a?(Hash)
|
228
|
+
opts.assert_valid_keys(:w, :wtimeout, :fsync)
|
229
|
+
cmd.merge!(opts)
|
230
|
+
end
|
231
|
+
message.put_binary(BSON::BSON_CODER.serialize(cmd, false).to_s)
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
# Prepares a message for transmission to MongoDB by
|
236
|
+
# constructing a valid message header.
|
237
|
+
#
|
238
|
+
# Note: this method modifies message by reference.
|
239
|
+
#
|
240
|
+
# @return [Integer] the request id used in the header
|
241
|
+
def add_message_headers(message, operation)
|
242
|
+
headers = [
|
243
|
+
# Message size.
|
244
|
+
16 + message.size,
|
245
|
+
|
246
|
+
# Unique request id.
|
247
|
+
request_id = get_request_id,
|
248
|
+
|
249
|
+
# Response id.
|
250
|
+
0,
|
251
|
+
|
252
|
+
# Opcode.
|
253
|
+
operation
|
254
|
+
].pack('VVVV')
|
255
|
+
|
256
|
+
message.prepend!(headers)
|
257
|
+
|
258
|
+
request_id
|
259
|
+
end
|
260
|
+
|
261
|
+
# Increment and return the next available request id.
|
262
|
+
#
|
263
|
+
# return [Integer]
|
264
|
+
def get_request_id
|
265
|
+
request_id = ''
|
266
|
+
@id_lock.synchronize do
|
267
|
+
request_id = @@current_request_id += 1
|
268
|
+
end
|
269
|
+
request_id
|
270
|
+
end
|
271
|
+
|
272
|
+
# Low-level method for sending a message on a socket.
|
273
|
+
# Requires a packed message and an available socket,
|
274
|
+
#
|
275
|
+
# @return [Integer] number of bytes sent
|
276
|
+
def send_message_on_socket(packed_message, socket)
|
277
|
+
begin
|
278
|
+
total_bytes_sent = socket.send(packed_message, 0)
|
279
|
+
if total_bytes_sent != packed_message.size
|
280
|
+
packed_message.slice!(0, total_bytes_sent)
|
281
|
+
while packed_message.size > 0
|
282
|
+
byte_sent = socket.send(packed_message, 0)
|
283
|
+
total_bytes_sent += byte_sent
|
284
|
+
packed_message.slice!(0, byte_sent)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
total_bytes_sent
|
288
|
+
rescue => ex
|
289
|
+
close
|
290
|
+
raise ConnectionFailure, "Operation failed with the following exception: #{ex}:#{ex.message}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Low-level method for receiving data from socket.
|
295
|
+
# Requires length and an available socket.
|
296
|
+
def receive_message_on_socket(length, socket)
|
297
|
+
begin
|
298
|
+
if @op_timeout
|
299
|
+
message = nil
|
300
|
+
Mongo::TimeoutHandler.timeout(@op_timeout, OperationTimeout) do
|
301
|
+
message = receive_data(length, socket)
|
302
|
+
end
|
303
|
+
else
|
304
|
+
message = receive_data(length, socket)
|
305
|
+
end
|
306
|
+
rescue => ex
|
307
|
+
close
|
308
|
+
|
309
|
+
if ex.class == OperationTimeout
|
310
|
+
raise OperationTimeout, "Timed out waiting on socket read."
|
311
|
+
else
|
312
|
+
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
message
|
316
|
+
end
|
317
|
+
|
318
|
+
def receive_data(length, socket)
|
319
|
+
message = new_binary_string
|
320
|
+
socket.read(length, message)
|
321
|
+
raise ConnectionFailure, "connection closed" unless message && message.length > 0
|
322
|
+
if message.length < length
|
323
|
+
chunk = new_binary_string
|
324
|
+
while message.length < length
|
325
|
+
socket.read(length - message.length, chunk)
|
326
|
+
raise ConnectionFailure, "connection closed" unless chunk.length > 0
|
327
|
+
message << chunk
|
328
|
+
end
|
329
|
+
end
|
330
|
+
message
|
331
|
+
end
|
332
|
+
|
333
|
+
if defined?(Encoding)
|
334
|
+
BINARY_ENCODING = Encoding.find("binary")
|
335
|
+
|
336
|
+
def new_binary_string
|
337
|
+
"".force_encoding(BINARY_ENCODING)
|
338
|
+
end
|
339
|
+
else
|
340
|
+
def new_binary_string
|
341
|
+
""
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|