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/docs/HISTORY.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# MongoDB Ruby Driver History
|
2
2
|
|
3
|
+
### 1.5.0.rc0
|
4
|
+
2011-11-18
|
5
|
+
|
6
|
+
Fix bugs associated with replica set refresh.
|
7
|
+
|
8
|
+
### 1.4.1
|
9
|
+
2011-10-17
|
10
|
+
|
11
|
+
If you're using 1.4.0, this is a necessary upgrade.
|
12
|
+
|
13
|
+
* Simplified replica set refresh.
|
14
|
+
* Fix bugs associated with replica set refresh.
|
15
|
+
* Make cursor smart enough to continue functioning
|
16
|
+
even if a refresh is triggered.
|
17
|
+
|
3
18
|
### 1.4.0
|
4
19
|
2011-9-19
|
5
20
|
|
data/docs/REPLICA_SETS.md
CHANGED
@@ -28,31 +28,43 @@ Imagine that either the master node or one of the read nodes goes offline. How w
|
|
28
28
|
|
29
29
|
If any read operation fails, the driver will raise a *ConnectionFailure* exception. It then becomes the client's responsibility to decide how to handle this.
|
30
30
|
|
31
|
-
If the client decides to retry, it's not guaranteed that another member of the replica set will have been promoted to master right away, so it's still possible that the driver will raise another *ConnectionFailure*. However, once a member has been promoted to master, typically within a few seconds, subsequent operations will succeed.
|
31
|
+
If the client decides to retry, it's not guaranteed that another member of the replica set will have been promoted to master right away, so it's still possible that the driver will raise another *ConnectionFailure*. However, once a member has been promoted to master, typically within a few seconds, subsequent operations will succeed. *Note that this does not prevent
|
32
|
+
exception in the event of a primary failover.*
|
32
33
|
|
33
34
|
The driver will essentially cycle through all known seed addresses until a node identifies itself as master.
|
34
35
|
|
35
36
|
### Refresh mode
|
36
37
|
|
37
38
|
You can now specify a refresh mode and refresh interval for a replica set connection. This will help to ensure that
|
38
|
-
changes to a replica set's configuration are quickly reflected on the driver side.
|
39
|
-
|
39
|
+
changes to a replica set's configuration are quickly reflected on the driver side. In particular, if you change
|
40
|
+
the state of any secondary node, the automated refresh will ensure that this state is recorded on the client side.
|
41
|
+
If you add a secondary that responds to pings much faster than the existing nodes, then the new secondary will
|
42
|
+
be used for reads.
|
40
43
|
|
41
|
-
|
44
|
+
Refresh mode is disabled by default.
|
42
45
|
|
43
|
-
|
46
|
+
However, if you expect to make live changes to your secondaries, and you want this to be reflected without
|
47
|
+
having to manually restart your app server, then you should enable it. You can enable this mode
|
48
|
+
synchronously, which will refresh the replica set data in a synchronous fashion (which may
|
49
|
+
ocassionally slow down your queries):
|
44
50
|
|
45
|
-
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], :refresh_mode => :
|
51
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], :refresh_mode => :sync)
|
46
52
|
|
47
53
|
If you want to change the default refresh interval of 90 seconds, you can do so like this:
|
48
54
|
|
49
|
-
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], :refresh_mode => :
|
55
|
+
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], :refresh_mode => :sync,
|
50
56
|
:refresh_interval => 60)
|
51
57
|
|
58
|
+
Do not set this value to anything lower than 30, or you may start to experience performance issues.
|
59
|
+
|
52
60
|
You can also disable refresh mode altogether:
|
53
61
|
|
54
62
|
@connection = ReplSetConnection.new(['n1.mydb.net', 27017], :refresh_mode => false)
|
55
63
|
|
64
|
+
And you can call `refresh` manually on any replica set connection:
|
65
|
+
|
66
|
+
@connection.refresh
|
67
|
+
|
56
68
|
### Recovery
|
57
69
|
|
58
70
|
Driver users may wish to wrap their database calls with failure recovery code. Here's one possibility, which will attempt to connection
|
data/lib/mongo.rb
CHANGED
data/lib/mongo/collection.rb
CHANGED
@@ -217,7 +217,7 @@ module Mongo
|
|
217
217
|
max_scan = opts.delete(:max_scan)
|
218
218
|
return_key = opts.delete(:return_key)
|
219
219
|
transformer = opts.delete(:transformer)
|
220
|
-
show_disk_loc = opts.delete(:
|
220
|
+
show_disk_loc = opts.delete(:show_disk_loc)
|
221
221
|
read = opts.delete(:read) || @read_preference
|
222
222
|
|
223
223
|
if timeout == false && !block_given?
|
data/lib/mongo/connection.rb
CHANGED
@@ -19,26 +19,26 @@
|
|
19
19
|
require 'set'
|
20
20
|
require 'socket'
|
21
21
|
require 'thread'
|
22
|
-
|
23
22
|
module Mongo
|
24
23
|
|
25
|
-
# Instantiates and manages connections to MongoDB.
|
24
|
+
# Instantiates and manages self.connections to MongoDB.
|
26
25
|
class Connection
|
27
26
|
include Mongo::Logging
|
27
|
+
include Mongo::Networking
|
28
28
|
|
29
29
|
TCPSocket = ::TCPSocket
|
30
30
|
Mutex = ::Mutex
|
31
31
|
ConditionVariable = ::ConditionVariable
|
32
32
|
|
33
|
+
Thread.abort_on_exception = true
|
34
|
+
|
33
35
|
DEFAULT_PORT = 27017
|
34
|
-
STANDARD_HEADER_SIZE = 16
|
35
|
-
RESPONSE_HEADER_SIZE = 20
|
36
36
|
|
37
|
-
|
38
|
-
:pool_size, :connect_timeout, :primary_pool, :socket_class
|
37
|
+
mongo_thread_local_accessor :connections
|
39
38
|
|
40
|
-
|
41
|
-
|
39
|
+
attr_reader :logger, :size, :auths, :primary, :safe, :host_to_try,
|
40
|
+
:pool_size, :connect_timeout, :pool_timeout,
|
41
|
+
:primary_pool, :socket_class
|
42
42
|
|
43
43
|
# Create a connection to single MongoDB instance.
|
44
44
|
#
|
@@ -63,9 +63,9 @@ module Mongo
|
|
63
63
|
# to a single, slave node.
|
64
64
|
# @option opts [Logger, #debug] :logger (nil) A Logger instance for debugging driver ops. Note that
|
65
65
|
# logging negatively impacts performance; therefore, it should not be used for high-performance apps.
|
66
|
-
# @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
|
66
|
+
# @option opts [Integer] :pool_size (1) The maximum number of socket self.connections allowed per
|
67
67
|
# connection pool. Note: this setting is relevant only for multi-threaded applications.
|
68
|
-
# @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
|
68
|
+
# @option opts [Float] :pool_timeout (5.0) When all of the self.connections a pool are checked out,
|
69
69
|
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
|
70
70
|
# Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
|
71
71
|
# @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
|
@@ -80,7 +80,7 @@ module Mongo
|
|
80
80
|
# @example localhost, 27017
|
81
81
|
# Connection.new("localhost")
|
82
82
|
#
|
83
|
-
# @example localhost, 3000, max 5 connections, with max 5 seconds of wait time.
|
83
|
+
# @example localhost, 3000, max 5 self.connections, with max 5 seconds of wait time.
|
84
84
|
# Connection.new("localhost", 3000, :pool_size => 5, :timeout => 5)
|
85
85
|
#
|
86
86
|
# @example localhost, 3000, where this node may be a slave
|
@@ -91,7 +91,7 @@ module Mongo
|
|
91
91
|
# @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
|
92
92
|
# driver fails to connect to a replica set with that name.
|
93
93
|
#
|
94
|
-
# @core connections
|
94
|
+
# @core self.connections
|
95
95
|
def initialize(host=nil, port=nil, opts={})
|
96
96
|
@host_to_try = format_pair(host, port)
|
97
97
|
|
@@ -379,125 +379,12 @@ module Mongo
|
|
379
379
|
@slave_ok
|
380
380
|
end
|
381
381
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
# this message should be sent. Valid options are :writer and :reader.
|
389
|
-
#
|
390
|
-
# @return [Integer] number of bytes sent
|
391
|
-
def send_message(operation, message, opts={})
|
392
|
-
if opts.is_a?(String)
|
393
|
-
warn "Connection#send_message no longer takes a string log message. " +
|
394
|
-
"Logging is now handled within the Collection and Cursor classes."
|
395
|
-
opts = {}
|
396
|
-
end
|
397
|
-
|
398
|
-
connection = opts.fetch(:connection, :writer)
|
399
|
-
|
400
|
-
begin
|
401
|
-
add_message_headers(message, operation)
|
402
|
-
packed_message = message.to_s
|
403
|
-
|
404
|
-
if connection == :writer
|
405
|
-
socket = checkout_writer
|
406
|
-
else
|
407
|
-
socket = checkout_reader
|
408
|
-
end
|
409
|
-
|
410
|
-
send_message_on_socket(packed_message, socket)
|
411
|
-
ensure
|
412
|
-
checkin(socket)
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
# Sends a message to the database, waits for a response, and raises
|
417
|
-
# an exception if the operation has failed.
|
418
|
-
#
|
419
|
-
# @param [Integer] operation a MongoDB opcode.
|
420
|
-
# @param [BSON::ByteBuffer] message a message to send to the database.
|
421
|
-
# @param [String] db_name the name of the database. used on call to get_last_error.
|
422
|
-
# @param [Hash] last_error_params parameters to be sent to getLastError. See DB#error for
|
423
|
-
# available options.
|
424
|
-
#
|
425
|
-
# @see DB#get_last_error for valid last error params.
|
426
|
-
#
|
427
|
-
# @return [Hash] The document returned by the call to getlasterror.
|
428
|
-
def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false)
|
429
|
-
docs = num_received = cursor_id = ''
|
430
|
-
add_message_headers(message, operation)
|
431
|
-
|
432
|
-
last_error_message = BSON::ByteBuffer.new
|
433
|
-
build_last_error_message(last_error_message, db_name, last_error_params)
|
434
|
-
last_error_id = add_message_headers(last_error_message, Mongo::Constants::OP_QUERY)
|
435
|
-
|
436
|
-
packed_message = message.append!(last_error_message).to_s
|
437
|
-
begin
|
438
|
-
sock = checkout_writer
|
439
|
-
@safe_mutexes[sock].synchronize do
|
440
|
-
send_message_on_socket(packed_message, sock)
|
441
|
-
docs, num_received, cursor_id = receive(sock, last_error_id)
|
442
|
-
end
|
443
|
-
ensure
|
444
|
-
checkin(sock)
|
445
|
-
end
|
446
|
-
|
447
|
-
if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
|
448
|
-
close if error == "not master"
|
449
|
-
error = "wtimeout" if error == "timeout"
|
450
|
-
raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
|
451
|
-
end
|
452
|
-
|
453
|
-
docs[0]
|
454
|
-
end
|
455
|
-
|
456
|
-
# Sends a message to the database and waits for the response.
|
457
|
-
#
|
458
|
-
# @param [Integer] operation a MongoDB opcode.
|
459
|
-
# @param [BSON::ByteBuffer] message a message to send to the database.
|
460
|
-
# @param [String] log_message this is currently a no-op and will be removed.
|
461
|
-
# @param [Socket] socket a socket to use in lieu of checking out a new one.
|
462
|
-
# @param [Boolean] command (false) indicate whether this is a command. If this is a command,
|
463
|
-
# the message will be sent to the primary node.
|
464
|
-
# @param [Boolean] command (false) indicate whether the cursor should be exhausted. Set
|
465
|
-
# this to true only when the OP_QUERY_EXHAUST flag is set.
|
466
|
-
#
|
467
|
-
# @return [Array]
|
468
|
-
# An array whose indexes include [0] documents returned, [1] number of document received,
|
469
|
-
# and [3] a cursor_id.
|
470
|
-
def receive_message(operation, message, log_message=nil, socket=nil, command=false, read=:primary, exhaust=false)
|
471
|
-
request_id = add_message_headers(message, operation)
|
472
|
-
packed_message = message.to_s
|
473
|
-
begin
|
474
|
-
if socket
|
475
|
-
sock = socket
|
476
|
-
should_checkin = false
|
477
|
-
else
|
478
|
-
if command
|
479
|
-
sock = checkout_writer
|
480
|
-
elsif read == :primary
|
481
|
-
sock = checkout_writer
|
482
|
-
elsif read == :secondary
|
483
|
-
sock = checkout_reader
|
484
|
-
else
|
485
|
-
sock = checkout_tagged(read)
|
486
|
-
end
|
487
|
-
should_checkin = true
|
488
|
-
end
|
489
|
-
|
490
|
-
result = ''
|
491
|
-
@safe_mutexes[sock].synchronize do
|
492
|
-
send_message_on_socket(packed_message, sock)
|
493
|
-
result = receive(sock, request_id, exhaust)
|
494
|
-
end
|
495
|
-
ensure
|
496
|
-
if should_checkin
|
497
|
-
checkin(sock)
|
498
|
-
end
|
499
|
-
end
|
500
|
-
result
|
382
|
+
def get_socket_from_thread_local
|
383
|
+
Thread.current[:socket_map] ||= {}
|
384
|
+
Thread.current[:socket_map][self] ||= {}
|
385
|
+
Thread.current[:socket_map][self][:writer] ||= checkout_writer
|
386
|
+
Thread.current[:socket_map][self][:reader] =
|
387
|
+
Thread.current[:socket_map][self][:writer]
|
501
388
|
end
|
502
389
|
|
503
390
|
# Create a new socket and attempt to connect to master.
|
@@ -536,7 +423,7 @@ module Mongo
|
|
536
423
|
# NOTE: Do check if this needs to be more stringent.
|
537
424
|
# Probably not since if any node raises a connection failure, all nodes will be closed.
|
538
425
|
def connected?
|
539
|
-
@primary_pool &&
|
426
|
+
@primary_pool && !@primary_pool.closed?
|
540
427
|
end
|
541
428
|
|
542
429
|
# Determine if the connection is active. In a normal case the *server_info* operation
|
@@ -564,6 +451,13 @@ module Mongo
|
|
564
451
|
end
|
565
452
|
alias :primary? :read_primary?
|
566
453
|
|
454
|
+
# The socket pool that this connection reads from.
|
455
|
+
#
|
456
|
+
# @return [Mongo::Pool]
|
457
|
+
def read_pool
|
458
|
+
@primary_pool
|
459
|
+
end
|
460
|
+
|
567
461
|
# The value of the read preference. Because
|
568
462
|
# this is a single-node connection, the value
|
569
463
|
# is +:primary+, and the connection will read
|
@@ -604,16 +498,12 @@ module Mongo
|
|
604
498
|
# Checkin a socket used for reading.
|
605
499
|
# Note: this is overridden in ReplSetConnection.
|
606
500
|
def checkin_reader(socket)
|
607
|
-
warn "Connection#checkin_writer is not deprecated and will be removed " +
|
608
|
-
"in driver v2.0. Use Connection#checkin instead."
|
609
501
|
checkin(socket)
|
610
502
|
end
|
611
503
|
|
612
504
|
# Checkin a socket used for writing.
|
613
505
|
# Note: this is overridden in ReplSetConnection.
|
614
506
|
def checkin_writer(socket)
|
615
|
-
warn "Connection#checkin_writer is not deprecated and will be removed " +
|
616
|
-
"in driver v2.0. Use Connection#checkin instead."
|
617
507
|
checkin(socket)
|
618
508
|
end
|
619
509
|
|
@@ -651,7 +541,7 @@ module Mongo
|
|
651
541
|
warn "The :timeout option has been deprecated " +
|
652
542
|
"and will be removed in the 2.0 release. Use :pool_timeout instead."
|
653
543
|
end
|
654
|
-
@
|
544
|
+
@pool_timeout = opts[:pool_timeout] || opts[:timeout] || 5.0
|
655
545
|
|
656
546
|
# Timeout on socket read operation.
|
657
547
|
@op_timeout = opts[:op_timeout] || nil
|
@@ -666,10 +556,6 @@ module Mongo
|
|
666
556
|
# Global safe option. This is false by default.
|
667
557
|
@safe = opts[:safe] || false
|
668
558
|
|
669
|
-
# Create a mutex when a new key, in this case a socket,
|
670
|
-
# is added to the hash.
|
671
|
-
@safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
|
672
|
-
|
673
559
|
# Condition variable for signal and wait
|
674
560
|
@queue = ConditionVariable.new
|
675
561
|
|
@@ -680,8 +566,7 @@ module Mongo
|
|
680
566
|
@logger = opts[:logger] || nil
|
681
567
|
|
682
568
|
if @logger
|
683
|
-
|
684
|
-
"and should be disabled for high-performance production apps.")
|
569
|
+
write_logging_startup_message
|
685
570
|
end
|
686
571
|
|
687
572
|
should_connect = opts.fetch(:connect, true)
|
@@ -743,214 +628,7 @@ module Mongo
|
|
743
628
|
def set_primary(node)
|
744
629
|
host, port = *node
|
745
630
|
@primary = [host, port]
|
746
|
-
@primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @
|
747
|
-
end
|
748
|
-
|
749
|
-
## Low-level connection methods.
|
750
|
-
|
751
|
-
def receive(sock, cursor_id, exhaust=false)
|
752
|
-
begin
|
753
|
-
if exhaust
|
754
|
-
docs = []
|
755
|
-
num_received = 0
|
756
|
-
|
757
|
-
while(cursor_id != 0) do
|
758
|
-
receive_header(sock, cursor_id, exhaust)
|
759
|
-
number_received, cursor_id = receive_response_header(sock)
|
760
|
-
new_docs, n = read_documents(number_received, sock)
|
761
|
-
docs += new_docs
|
762
|
-
num_received += n
|
763
|
-
end
|
764
|
-
|
765
|
-
return [docs, num_received, cursor_id]
|
766
|
-
else
|
767
|
-
receive_header(sock, cursor_id, exhaust)
|
768
|
-
number_received, cursor_id = receive_response_header(sock)
|
769
|
-
docs, num_received = read_documents(number_received, sock)
|
770
|
-
|
771
|
-
return [docs, num_received, cursor_id]
|
772
|
-
end
|
773
|
-
rescue Mongo::ConnectionFailure => ex
|
774
|
-
close
|
775
|
-
raise ex
|
776
|
-
end
|
777
|
-
end
|
778
|
-
|
779
|
-
def receive_header(sock, expected_response, exhaust=false)
|
780
|
-
header = receive_message_on_socket(16, sock)
|
781
|
-
size, request_id, response_to = header.unpack('VVV')
|
782
|
-
if !exhaust && expected_response != response_to
|
783
|
-
raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
|
784
|
-
end
|
785
|
-
|
786
|
-
unless header.size == STANDARD_HEADER_SIZE
|
787
|
-
raise "Short read for DB response header: " +
|
788
|
-
"expected #{STANDARD_HEADER_SIZE} bytes, saw #{header.size}"
|
789
|
-
end
|
790
|
-
nil
|
791
|
-
end
|
792
|
-
|
793
|
-
def receive_response_header(sock)
|
794
|
-
header_buf = receive_message_on_socket(RESPONSE_HEADER_SIZE, sock)
|
795
|
-
if header_buf.length != RESPONSE_HEADER_SIZE
|
796
|
-
raise "Short read for DB response header; " +
|
797
|
-
"expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
|
798
|
-
end
|
799
|
-
flags, cursor_id_a, cursor_id_b, starting_from, number_remaining = header_buf.unpack('VVVVV')
|
800
|
-
check_response_flags(flags)
|
801
|
-
cursor_id = (cursor_id_b << 32) + cursor_id_a
|
802
|
-
[number_remaining, cursor_id]
|
803
|
-
end
|
804
|
-
|
805
|
-
def check_response_flags(flags)
|
806
|
-
if flags & Mongo::Constants::REPLY_CURSOR_NOT_FOUND != 0
|
807
|
-
raise Mongo::OperationFailure, "Query response returned CURSOR_NOT_FOUND. " +
|
808
|
-
"Either an invalid cursor was specified, or the cursor may have timed out on the server."
|
809
|
-
elsif flags & Mongo::Constants::REPLY_QUERY_FAILURE != 0
|
810
|
-
# Getting odd failures when a exception is raised here.
|
811
|
-
end
|
812
|
-
end
|
813
|
-
|
814
|
-
def read_documents(number_received, sock)
|
815
|
-
docs = []
|
816
|
-
number_remaining = number_received
|
817
|
-
while number_remaining > 0 do
|
818
|
-
buf = receive_message_on_socket(4, sock)
|
819
|
-
size = buf.unpack('V')[0]
|
820
|
-
buf << receive_message_on_socket(size - 4, sock)
|
821
|
-
number_remaining -= 1
|
822
|
-
docs << BSON::BSON_CODER.deserialize(buf)
|
823
|
-
end
|
824
|
-
[docs, number_received]
|
825
|
-
end
|
826
|
-
|
827
|
-
# Constructs a getlasterror message. This method is used exclusively by
|
828
|
-
# Connection#send_message_with_safe_check.
|
829
|
-
#
|
830
|
-
# Because it modifies message by reference, we don't need to return it.
|
831
|
-
def build_last_error_message(message, db_name, opts)
|
832
|
-
message.put_int(0)
|
833
|
-
BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd")
|
834
|
-
message.put_int(0)
|
835
|
-
message.put_int(-1)
|
836
|
-
cmd = BSON::OrderedHash.new
|
837
|
-
cmd[:getlasterror] = 1
|
838
|
-
if opts.is_a?(Hash)
|
839
|
-
opts.assert_valid_keys(:w, :wtimeout, :fsync)
|
840
|
-
cmd.merge!(opts)
|
841
|
-
end
|
842
|
-
message.put_binary(BSON::BSON_CODER.serialize(cmd, false).to_s)
|
843
|
-
nil
|
844
|
-
end
|
845
|
-
|
846
|
-
# Prepares a message for transmission to MongoDB by
|
847
|
-
# constructing a valid message header.
|
848
|
-
#
|
849
|
-
# Note: this method modifies message by reference.
|
850
|
-
#
|
851
|
-
# @return [Integer] the request id used in the header
|
852
|
-
def add_message_headers(message, operation)
|
853
|
-
headers = [
|
854
|
-
# Message size.
|
855
|
-
16 + message.size,
|
856
|
-
|
857
|
-
# Unique request id.
|
858
|
-
request_id = get_request_id,
|
859
|
-
|
860
|
-
# Response id.
|
861
|
-
0,
|
862
|
-
|
863
|
-
# Opcode.
|
864
|
-
operation
|
865
|
-
].pack('VVVV')
|
866
|
-
|
867
|
-
message.prepend!(headers)
|
868
|
-
|
869
|
-
request_id
|
870
|
-
end
|
871
|
-
|
872
|
-
# Increment and return the next available request id.
|
873
|
-
#
|
874
|
-
# return [Integer]
|
875
|
-
def get_request_id
|
876
|
-
request_id = ''
|
877
|
-
@id_lock.synchronize do
|
878
|
-
request_id = @@current_request_id += 1
|
879
|
-
end
|
880
|
-
request_id
|
881
|
-
end
|
882
|
-
|
883
|
-
# Low-level method for sending a message on a socket.
|
884
|
-
# Requires a packed message and an available socket,
|
885
|
-
#
|
886
|
-
# @return [Integer] number of bytes sent
|
887
|
-
def send_message_on_socket(packed_message, socket)
|
888
|
-
begin
|
889
|
-
total_bytes_sent = socket.send(packed_message, 0)
|
890
|
-
if total_bytes_sent != packed_message.size
|
891
|
-
packed_message.slice!(0, total_bytes_sent)
|
892
|
-
while packed_message.size > 0
|
893
|
-
byte_sent = socket.send(packed_message, 0)
|
894
|
-
total_bytes_sent += byte_sent
|
895
|
-
packed_message.slice!(0, byte_sent)
|
896
|
-
end
|
897
|
-
end
|
898
|
-
total_bytes_sent
|
899
|
-
rescue => ex
|
900
|
-
close
|
901
|
-
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
902
|
-
end
|
903
|
-
end
|
904
|
-
|
905
|
-
# Low-level method for receiving data from socket.
|
906
|
-
# Requires length and an available socket.
|
907
|
-
def receive_message_on_socket(length, socket)
|
908
|
-
begin
|
909
|
-
if @op_timeout
|
910
|
-
message = nil
|
911
|
-
Mongo::TimeoutHandler.timeout(@op_timeout, OperationTimeout) do
|
912
|
-
message = receive_data(length, socket)
|
913
|
-
end
|
914
|
-
else
|
915
|
-
message = receive_data(length, socket)
|
916
|
-
end
|
917
|
-
rescue => ex
|
918
|
-
close
|
919
|
-
|
920
|
-
if ex.class == OperationTimeout
|
921
|
-
raise OperationTimeout, "Timed out waiting on socket read."
|
922
|
-
else
|
923
|
-
raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
|
924
|
-
end
|
925
|
-
end
|
926
|
-
message
|
927
|
-
end
|
928
|
-
|
929
|
-
def receive_data(length, socket)
|
930
|
-
message = new_binary_string
|
931
|
-
socket.read(length, message)
|
932
|
-
raise ConnectionFailure, "connection closed" unless message && message.length > 0
|
933
|
-
if message.length < length
|
934
|
-
chunk = new_binary_string
|
935
|
-
while message.length < length
|
936
|
-
socket.read(length - message.length, chunk)
|
937
|
-
raise ConnectionFailure, "connection closed" unless chunk.length > 0
|
938
|
-
message << chunk
|
939
|
-
end
|
940
|
-
end
|
941
|
-
message
|
942
|
-
end
|
943
|
-
|
944
|
-
if defined?(Encoding)
|
945
|
-
BINARY_ENCODING = Encoding.find("binary")
|
946
|
-
|
947
|
-
def new_binary_string
|
948
|
-
"".force_encoding(BINARY_ENCODING)
|
949
|
-
end
|
950
|
-
else
|
951
|
-
def new_binary_string
|
952
|
-
""
|
953
|
-
end
|
631
|
+
@primary_pool = Pool.new(self, host, port, :size => @pool_size, :timeout => @pool_timeout)
|
954
632
|
end
|
955
633
|
end
|
956
634
|
end
|