mongo 1.4.0 → 1.5.0.rc0
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/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
|