mongo 1.6.1 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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