mongo 1.6.1 → 1.6.2

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.
Files changed (49) hide show
  1. data/README.md +17 -16
  2. data/Rakefile +30 -24
  3. data/docs/HISTORY.md +10 -0
  4. data/docs/RELEASES.md +7 -0
  5. data/docs/REPLICA_SETS.md +2 -2
  6. data/docs/TUTORIAL.md +181 -84
  7. data/lib/mongo.rb +0 -3
  8. data/lib/mongo/collection.rb +1 -1
  9. data/lib/mongo/connection.rb +28 -20
  10. data/lib/mongo/db.rb +5 -3
  11. data/lib/mongo/exceptions.rb +1 -1
  12. data/lib/mongo/gridfs/grid_file_system.rb +1 -1
  13. data/lib/mongo/networking.rb +18 -18
  14. data/lib/mongo/repl_set_connection.rb +24 -3
  15. data/lib/mongo/util/node.rb +6 -16
  16. data/lib/mongo/util/pool.rb +8 -11
  17. data/lib/mongo/util/ssl_socket.rb +34 -12
  18. data/lib/mongo/util/tcp_socket.rb +92 -3
  19. data/lib/mongo/util/uri_parser.rb +2 -2
  20. data/lib/mongo/version.rb +1 -1
  21. data/test/auxillary/repl_set_auth_test.rb +19 -5
  22. data/test/bson/bson_test.rb +23 -21
  23. data/test/bson/json_test.rb +1 -1
  24. data/test/collection_test.rb +14 -4
  25. data/test/connection_test.rb +14 -11
  26. data/test/cursor_test.rb +4 -4
  27. data/test/grid_file_system_test.rb +3 -1
  28. data/test/grid_io_test.rb +2 -2
  29. data/test/grid_test.rb +13 -6
  30. data/test/pool_test.rb +0 -2
  31. data/test/replica_sets/basic_test.rb +1 -1
  32. data/test/replica_sets/complex_connect_test.rb +5 -4
  33. data/test/replica_sets/connect_test.rb +14 -6
  34. data/test/replica_sets/pooled_insert_test.rb +1 -1
  35. data/test/replica_sets/query_test.rb +2 -2
  36. data/test/replica_sets/read_preference_test.rb +1 -2
  37. data/test/replica_sets/refresh_test.rb +4 -2
  38. data/test/replica_sets/refresh_with_threads_test.rb +7 -13
  39. data/test/replica_sets/rs_test_helper.rb +1 -1
  40. data/test/test_helper.rb +6 -4
  41. data/test/threading/threading_with_large_pool_test.rb +1 -1
  42. data/test/threading_test.rb +2 -2
  43. data/test/timeout_test.rb +40 -0
  44. data/test/tools/repl_set_manager.rb +9 -8
  45. data/test/unit/connection_test.rb +6 -7
  46. data/test/unit/node_test.rb +1 -0
  47. data/test/unit/pool_manager_test.rb +2 -1
  48. data/test/uri_test.rb +1 -2
  49. metadata +13 -6
@@ -78,6 +78,3 @@ if RUBY_PLATFORM =~ /java/
78
78
  require 'mongo/gridfs/grid_io_fix'
79
79
  end
80
80
  require 'mongo/gridfs/grid_file_system'
81
-
82
- require 'timeout'
83
- Mongo::TimeoutHandler = Timeout
@@ -961,7 +961,7 @@ module Mongo
961
961
  begin
962
962
  message.put_binary(BSON::BSON_CODER.serialize(doc, check_keys, true, @connection.max_bson_size).to_s)
963
963
  true
964
- rescue StandardError => e # StandardError will be replaced with BSONError
964
+ rescue StandardError # StandardError will be replaced with BSONError
965
965
  doc.delete(:_id)
966
966
  error_docs << doc
967
967
  false
@@ -26,7 +26,7 @@ module Mongo
26
26
  include Mongo::Logging
27
27
  include Mongo::Networking
28
28
 
29
- TCPSocket = ::TCPSocket
29
+ TCPSocket = Mongo::TCPSocket
30
30
  Mutex = ::Mutex
31
31
  ConditionVariable = ::ConditionVariable
32
32
 
@@ -67,7 +67,7 @@ module Mongo
67
67
  # logging negatively impacts performance; therefore, it should not be used for high-performance apps.
68
68
  # @option opts [Integer] :pool_size (1) The maximum number of socket self.connections allowed per
69
69
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
70
- # @option opts [Float] :pool_timeout (5.0) When all of the self.connections a pool are checked out,
70
+ # @option opts [Float] :timeout (5.0) When all of the self.connections a pool are checked out,
71
71
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
72
72
  # Note: this setting is relevant only for multi-threaded applications (which in Ruby are rare).
73
73
  # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
@@ -480,6 +480,12 @@ module Mongo
480
480
  @max_bson_size
481
481
  end
482
482
 
483
+ # Prefer primary pool but fall back to secondary
484
+ def checkout_best
485
+ connect unless connected?
486
+ @primary_pool.checkout
487
+ end
488
+
483
489
  # Checkout a socket for reading (i.e., a secondary node).
484
490
  # Note: this is overridden in ReplSetConnection.
485
491
  def checkout_reader
@@ -513,6 +519,19 @@ module Mongo
513
519
  end
514
520
  end
515
521
 
522
+ # Excecutes block with the best available socket
523
+ def best_available_socket
524
+ socket = nil
525
+ begin
526
+ socket = checkout_best
527
+ yield socket
528
+ ensure
529
+ if socket
530
+ socket.pool.checkin(socket)
531
+ end
532
+ end
533
+ end
534
+
516
535
  protected
517
536
 
518
537
  def valid_opts
@@ -603,27 +622,16 @@ module Mongo
603
622
  socket = nil
604
623
  config = nil
605
624
 
606
- if @connect_timeout
607
- Mongo::TimeoutHandler.timeout(@connect_timeout, OperationTimeout) do
608
- socket = @socket_class.new(host, port)
609
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
610
- end
611
- else
612
- socket = @socket_class.new(host, port)
613
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
614
- end
625
+ socket = @socket_class.new(host, port, @op_timeout, @connect_timeout)
626
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
615
627
 
616
- if @connect_timeout
617
- Mongo::TimeoutHandler.timeout(@connect_timeout, OperationTimeout) do
618
- config = self['admin'].command({:ismaster => 1}, :socket => socket)
619
- end
620
- else
621
- config = self['admin'].command({:ismaster => 1}, :socket => socket)
622
- end
623
- rescue OperationFailure, SocketError, SystemCallError, IOError => ex
628
+ config = self['admin'].command({:ismaster => 1}, :socket => socket)
629
+ rescue OperationFailure, SocketError, SystemCallError, IOError
624
630
  close
625
631
  ensure
626
- socket.close if socket
632
+ if socket
633
+ socket.close unless socket.closed?
634
+ end
627
635
  end
628
636
 
629
637
  config
@@ -17,7 +17,6 @@
17
17
  # ++
18
18
 
19
19
  require 'socket'
20
- require 'timeout'
21
20
  require 'thread'
22
21
 
23
22
  module Mongo
@@ -111,10 +110,13 @@ module Mongo
111
110
  if !save_auth
112
111
  raise MongoArgumentError, "If using connection pooling, :save_auth must be set to true."
113
112
  end
114
- @connection.authenticate_pools
115
113
  end
116
114
 
117
- issue_authentication(username, password, save_auth)
115
+ @connection.best_available_socket do |socket|
116
+ issue_authentication(username, password, save_auth, :socket => socket)
117
+ end
118
+
119
+ @connection.authenticate_pools
118
120
  end
119
121
 
120
122
  def issue_authentication(username, password, save_auth=true, opts={})
@@ -71,7 +71,7 @@ module Mongo
71
71
  class OperationFailure < MongoDBError; end
72
72
 
73
73
  # Raised when a socket read operation times out.
74
- class OperationTimeout < ::Timeout::Error; end
74
+ class OperationTimeout < SocketError; end
75
75
 
76
76
  # Raised when a client attempts to perform an invalid operation.
77
77
  class InvalidOperation < MongoDBError; end
@@ -115,7 +115,7 @@ module Mongo
115
115
  id = file.close
116
116
  if versions
117
117
  self.delete do
118
- @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'], :sort => ['uploadDate', -1], :skip => (versions -1))
118
+ @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'], :sort => ['uploadDate', -1], :skip => (versions - 1))
119
119
  end
120
120
  end
121
121
  end
@@ -186,7 +186,10 @@ module Mongo
186
186
 
187
187
  def receive_header(sock, expected_response, exhaust=false)
188
188
  header = receive_message_on_socket(16, sock)
189
- size, request_id, response_to = header.unpack('VVV')
189
+
190
+ # unpacks to size, request_id, response_to
191
+ response_to = header.unpack('VVV')[2]
192
+
190
193
  if !exhaust && expected_response != response_to
191
194
  raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
192
195
  end
@@ -204,7 +207,10 @@ module Mongo
204
207
  raise "Short read for DB response header; " +
205
208
  "expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}"
206
209
  end
207
- flags, cursor_id_a, cursor_id_b, starting_from, number_remaining = header_buf.unpack('VVVVV')
210
+
211
+ # unpacks to flags, cursor_id_a, cursor_id_b, starting_from, number_remaining
212
+ flags, cursor_id_a, cursor_id_b, _, number_remaining = header_buf.unpack('VVVVV')
213
+
208
214
  check_response_flags(flags)
209
215
  cursor_id = (cursor_id_b << 32) + cursor_id_a
210
216
  [number_remaining, cursor_id]
@@ -294,11 +300,11 @@ module Mongo
294
300
  # @return [Integer] number of bytes sent
295
301
  def send_message_on_socket(packed_message, socket)
296
302
  begin
297
- total_bytes_sent = socket.send(packed_message, 0)
303
+ total_bytes_sent = socket.send(packed_message)
298
304
  if total_bytes_sent != packed_message.size
299
305
  packed_message.slice!(0, total_bytes_sent)
300
306
  while packed_message.size > 0
301
- byte_sent = socket.send(packed_message, 0)
307
+ byte_sent = socket.send(packed_message)
302
308
  total_bytes_sent += byte_sent
303
309
  packed_message.slice!(0, byte_sent)
304
310
  end
@@ -314,22 +320,15 @@ module Mongo
314
320
  # Requires length and an available socket.
315
321
  def receive_message_on_socket(length, socket)
316
322
  begin
317
- if @op_timeout
318
- message = nil
319
- Mongo::TimeoutHandler.timeout(@op_timeout, OperationTimeout) do
320
- message = receive_data(length, socket)
321
- end
322
- else
323
323
  message = receive_data(length, socket)
324
- end
325
- rescue => ex
326
- close
324
+ rescue OperationTimeout, ConnectionFailure => ex
325
+ close
327
326
 
328
- if ex.class == OperationTimeout
329
- raise OperationTimeout, "Timed out waiting on socket read."
330
- else
331
- raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
332
- end
327
+ if ex.class == OperationTimeout
328
+ raise OperationTimeout, "Timed out waiting on socket read."
329
+ else
330
+ raise ConnectionFailure, "Operation failed with the following exception: #{ex}"
331
+ end
333
332
  end
334
333
  message
335
334
  end
@@ -337,6 +336,7 @@ module Mongo
337
336
  def receive_data(length, socket)
338
337
  message = new_binary_string
339
338
  socket.read(length, message)
339
+
340
340
  raise ConnectionFailure, "connection closed" unless message && message.length > 0
341
341
  if message.length < length
342
342
  chunk = new_binary_string
@@ -287,14 +287,18 @@ module Mongo
287
287
  end
288
288
 
289
289
  def authenticate_pools
290
- primary_pool.authenticate_existing
290
+ if primary_pool
291
+ primary_pool.authenticate_existing
292
+ end
291
293
  secondary_pools.each do |pool|
292
294
  pool.authenticate_existing
293
295
  end
294
296
  end
295
297
 
296
298
  def logout_pools(db)
297
- primary_pool.logout_existing(db)
299
+ if primary_pool
300
+ primary_pool.logout_existing(db)
301
+ end
298
302
  secondary_pools.each do |pool|
299
303
  pool.logout_existing(db)
300
304
  end
@@ -323,6 +327,19 @@ module Mongo
323
327
  raise ConnectionFailure.new("Could not checkout a socket.")
324
328
  end
325
329
  end
330
+
331
+ # Checkout best available socket by trying primary
332
+ # pool first and then falling back to secondary.
333
+ def checkout_best
334
+ checkout do
335
+ socket = get_socket_from_pool(:primary)
336
+ if !socket
337
+ connect
338
+ socket = get_socket_from_pool(:secondary)
339
+ end
340
+ socket
341
+ end
342
+ end
326
343
 
327
344
  # Checkout a socket for reading (i.e., a secondary node).
328
345
  # Note that @read_pool might point to the primary pool
@@ -459,7 +476,11 @@ module Mongo
459
476
 
460
477
  # Refresh
461
478
  @refresh_mode = opts.fetch(:refresh_mode, false)
462
- @refresh_interval = opts[:refresh_interval] || 90
479
+ @refresh_interval = opts.fetch(:refresh_interval, 90)
480
+
481
+ if @refresh_mode && @refresh_interval < 60
482
+ @refresh_interval = 60 unless ENV['TEST_MODE'] = 'TRUE'
483
+ end
463
484
 
464
485
  if @refresh_mode == :async
465
486
  warn ":async refresh mode has been deprecated. Refresh
@@ -36,20 +36,16 @@ module Mongo
36
36
  def connect
37
37
  begin
38
38
  socket = nil
39
- if @connection.connect_timeout
40
- Mongo::TimeoutHandler.timeout(@connection.connect_timeout, OperationTimeout) do
41
- socket = @connection.socket_class.new(@host, @port)
42
- end
43
- else
44
- socket = @connection.socket_class.new(@host, @port)
45
- end
39
+ socket = @connection.socket_class.new(@host, @port,
40
+ @connection.op_timeout, @connection.connect_timeout
41
+ )
46
42
 
47
43
  if socket.nil?
48
44
  return nil
49
45
  else
50
46
  socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
51
47
  end
52
- rescue OperationTimeout, OperationFailure, SocketError, SystemCallError, IOError => ex
48
+ rescue OperationTimeout, ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError => ex
53
49
  @connection.log(:debug, "Failed connection to #{host_string} with #{ex.class}, #{ex.message}.")
54
50
  socket.close if socket
55
51
  return nil
@@ -74,7 +70,7 @@ module Mongo
74
70
  begin
75
71
  result = @connection['admin'].command({:ping => 1}, :socket => @socket)
76
72
  return result['ok'] == 1
77
- rescue OperationFailure, SocketError, SystemCallError, IOError => ex
73
+ rescue OperationFailure, SocketError, SystemCallError, IOError
78
74
  return nil
79
75
  end
80
76
  end
@@ -84,13 +80,7 @@ module Mongo
84
80
  # matches with the name provided.
85
81
  def set_config
86
82
  begin
87
- if @connection.connect_timeout
88
- Mongo::TimeoutHandler.timeout(@connection.connect_timeout, OperationTimeout) do
89
- @config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
90
- end
91
- else
92
- @config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
93
- end
83
+ @config = @connection['admin'].command({:ismaster => 1}, :socket => @socket)
94
84
 
95
85
  if @config['msg'] && @logger
96
86
  @connection.log(:warn, "#{config['msg']}")
@@ -133,7 +133,7 @@ module Mongo
133
133
  def ping
134
134
  begin
135
135
  return self.connection['admin'].command({:ping => 1}, :socket => @node.socket)
136
- rescue OperationFailure, SocketError, SystemCallError, IOError => ex
136
+ rescue OperationFailure, SocketError, SystemCallError, IOError
137
137
  return false
138
138
  end
139
139
  end
@@ -156,7 +156,7 @@ module Mongo
156
156
  # therefore, it runs within a mutex.
157
157
  def checkout_new_socket
158
158
  begin
159
- socket = self.connection.socket_class.new(@host, @port)
159
+ socket = @connection.socket_class.new(@host, @port, @connection.op_timeout)
160
160
  socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
161
161
  socket.pool = self
162
162
  rescue => ex
@@ -217,7 +217,9 @@ module Mongo
217
217
  if @pids[socket] != Process.pid
218
218
  @pids[socket] = nil
219
219
  @sockets.delete(socket)
220
- socket.close if socket
220
+ if socket
221
+ socket.close unless socket.closed?
222
+ end
221
223
  checkout_new_socket
222
224
  else
223
225
  @checked_out << socket
@@ -227,15 +229,10 @@ module Mongo
227
229
  end
228
230
 
229
231
  def prune_thread_socket_hash
230
- map = {}
231
- Thread.list.each do |t|
232
- map[t] = 1
233
- end
232
+ current_threads = Set[*Thread.list]
234
233
 
235
- @threads_to_sockets.keys.each do |key|
236
- if !map[key]
237
- @threads_to_sockets.delete(key)
238
- end
234
+ @threads_to_sockets.delete_if do |thread, socket|
235
+ !current_threads.include?(thread)
239
236
  end
240
237
  end
241
238
 
@@ -1,4 +1,6 @@
1
+ require 'socket'
1
2
  require 'openssl'
3
+ require 'timeout'
2
4
 
3
5
  module Mongo
4
6
 
@@ -9,31 +11,51 @@ module Mongo
9
11
 
10
12
  attr_accessor :pool
11
13
 
12
- def initialize(host, port)
13
- @socket = ::TCPSocket.new(host, port)
14
+ def initialize(host, port, op_timeout=nil, connect_timeout=nil)
15
+ @op_timeout = op_timeout
16
+ @connect_timeout = connect_timeout
17
+
18
+ @socket = ::TCPSocket.new(host, port)
14
19
  @ssl = OpenSSL::SSL::SSLSocket.new(@socket)
15
20
  @ssl.sync_close = true
16
- @ssl.connect
21
+
22
+ connect
17
23
  end
18
24
 
19
- def setsockopt(key, value, n)
20
- @socket.setsockopt(key, value, n)
25
+ def connect
26
+ if @connect_timeout
27
+ Timeout::timeout(@connect_timeout, ConnectionTimeoutError) do
28
+ @ssl.connect
29
+ end
30
+ else
31
+ @ssl.connect
32
+ end
21
33
  end
22
34
 
23
- # Write to the SSL socket.
24
- #
25
- # @param buffer a buffer to send.
26
- # @param flags socket flags. Because Ruby's SSL
27
- def send(buffer, flags=0)
28
- @ssl.syswrite(buffer)
35
+ def send(data)
36
+ @ssl.syswrite(data)
29
37
  end
30
38
 
31
39
  def read(length, buffer)
32
- @ssl.sysread(length, buffer)
40
+ if @op_timeout
41
+ Timeout::timeout(@op_timeout, OperationTimeout) do
42
+ @ssl.sysread(length, buffer)
43
+ end
44
+ else
45
+ @ssl.sysread(length, buffer)
46
+ end
47
+ end
48
+
49
+ def setsockopt(key, value, n)
50
+ @ssl.setsockopt(key, value, n)
33
51
  end
34
52
 
35
53
  def close
36
54
  @ssl.close
37
55
  end
56
+
57
+ def closed?
58
+ @ssl.closed?
59
+ end
38
60
  end
39
61
  end
@@ -1,6 +1,95 @@
1
+ require 'socket'
2
+
1
3
  module Mongo
2
- class TCPSocket < ::TCPSocket
3
- attr_accessor :pool
4
+ # Wrapper class for Socket
5
+ #
6
+ # Emulates TCPSocket with operation and connection timeout
7
+ # sans Timeout::timeout
8
+ #
9
+ class TCPSocket
10
+ attr_accessor :pool
11
+
12
+ def initialize(host, port, op_timeout=nil, connect_timeout=nil)
13
+ @op_timeout = op_timeout
14
+ @connect_timeout = connect_timeout
15
+
16
+ # TODO: Prefer ipv6 if server is ipv6 enabled
17
+ @host = Socket.getaddrinfo(host, nil, Socket::AF_INET).first[3]
18
+ @port = port
19
+ @socket_address = Socket.pack_sockaddr_in(@port, @host)
20
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
21
+
22
+ connect
23
+ end
24
+
25
+ def connect
26
+ # Connect nonblock is broken in current versions of JRuby
27
+ if RUBY_PLATFORM == 'java'
28
+ require 'timeout'
29
+ if @connect_timeout
30
+ Timeout::timeout(@connect_timeout, OperationTimeout) do
31
+ @socket.connect(@socket_address)
32
+ end
33
+ else
34
+ @socket.connect(@socket_address)
35
+ end
36
+ else
37
+ # Try to connect for @connect_timeout seconds
38
+ begin
39
+ @socket.connect_nonblock(@socket_address)
40
+ rescue Errno::EINPROGRESS
41
+ # Block until there is a response or error
42
+ resp = IO.select([@socket], [@socket], [@socket], @connect_timeout)
43
+ if resp.nil?
44
+ raise ConnectionTimeoutError
45
+ end
46
+ end
47
+
48
+ # If there was a failure this will raise an Error
49
+ begin
50
+ @socket.connect_nonblock(@socket_address)
51
+ rescue Errno::EISCONN
52
+ # Successfully connected
53
+ end
54
+ end
55
+ end
56
+
57
+ def send(data)
58
+ @socket.write(data)
59
+ end
60
+
61
+ def read(maxlen, buffer)
62
+ # Block on data to read for @op_timeout seconds
63
+ begin
64
+ ready = IO.select([@socket], nil, [@socket], @op_timeout)
65
+ rescue IOError
66
+ raise OperationFailure
67
+ end
68
+ if ready
69
+ begin
70
+ @socket.readpartial(maxlen, buffer)
71
+ rescue EOFError
72
+ return ConnectionError
73
+ rescue Errno::ENOTCONN, Errno::EBADF, Errno::ECONNRESET, Errno::EPIPE
74
+ raise ConnectionFailure
75
+ rescue Errno::EINTR, Errno::EIO, IOError
76
+ raise OperationFailure
77
+ end
78
+ else
79
+ raise OperationTimeout
80
+ end
81
+ end
82
+
83
+ def setsockopt(key, value, n)
84
+ @socket.setsockopt(key, value, n)
85
+ end
86
+
87
+ def close
88
+ @socket.close
89
+ end
4
90
 
5
- end
91
+ def closed?
92
+ @socket.closed?
93
+ end
94
+ end
6
95
  end