mongo 1.5.2 → 1.6.0

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 (51) hide show
  1. data/Rakefile +24 -8
  2. data/docs/HISTORY.md +15 -0
  3. data/docs/READ_PREFERENCE.md +1 -1
  4. data/docs/RELEASES.md +18 -4
  5. data/docs/REPLICA_SETS.md +5 -5
  6. data/docs/WRITE_CONCERN.md +1 -1
  7. data/lib/mongo/collection.rb +49 -10
  8. data/lib/mongo/connection.rb +7 -24
  9. data/lib/mongo/cursor.rb +3 -1
  10. data/lib/mongo/db.rb +5 -1
  11. data/lib/mongo/exceptions.rb +0 -3
  12. data/lib/mongo/gridfs/grid.rb +1 -1
  13. data/lib/mongo/gridfs/grid_file_system.rb +11 -3
  14. data/lib/mongo/networking.rb +7 -3
  15. data/lib/mongo/repl_set_connection.rb +58 -82
  16. data/lib/mongo/util/logging.rb +26 -18
  17. data/lib/mongo/util/node.rb +11 -2
  18. data/lib/mongo/util/pool_manager.rb +7 -5
  19. data/lib/mongo/util/support.rb +2 -2
  20. data/lib/mongo/util/uri_parser.rb +74 -36
  21. data/lib/mongo/version.rb +1 -1
  22. data/mongo.gemspec +2 -2
  23. data/test/auxillary/authentication_test.rb +8 -0
  24. data/test/auxillary/repl_set_auth_test.rb +1 -2
  25. data/test/bson/ordered_hash_test.rb +1 -1
  26. data/test/bson/test_helper.rb +2 -1
  27. data/test/collection_test.rb +71 -0
  28. data/test/connection_test.rb +9 -0
  29. data/test/db_test.rb +7 -0
  30. data/test/grid_file_system_test.rb +12 -0
  31. data/test/load/thin/load.rb +1 -1
  32. data/test/replica_sets/basic_test.rb +36 -26
  33. data/test/replica_sets/complex_connect_test.rb +44 -0
  34. data/test/replica_sets/connect_test.rb +48 -22
  35. data/test/replica_sets/count_test.rb +4 -6
  36. data/test/replica_sets/insert_test.rb +13 -14
  37. data/test/replica_sets/pooled_insert_test.rb +9 -10
  38. data/test/replica_sets/query_test.rb +4 -4
  39. data/test/replica_sets/read_preference_test.rb +48 -14
  40. data/test/replica_sets/refresh_test.rb +43 -42
  41. data/test/replica_sets/refresh_with_threads_test.rb +10 -9
  42. data/test/replica_sets/replication_ack_test.rb +3 -3
  43. data/test/replica_sets/rs_test_helper.rb +17 -11
  44. data/test/test_helper.rb +2 -1
  45. data/test/tools/repl_set_manager.rb +63 -39
  46. data/test/unit/collection_test.rb +2 -1
  47. data/test/unit/connection_test.rb +22 -0
  48. data/test/unit/cursor_test.rb +6 -3
  49. data/test/unit/read_test.rb +1 -1
  50. data/test/uri_test.rb +17 -2
  51. metadata +151 -167
@@ -37,9 +37,6 @@ module Mongo
37
37
  end
38
38
  end
39
39
 
40
- # Raised when configuration options cause connections, queries, etc., to fail.
41
- class ConfigurationError < MongoRubyError; end
42
-
43
40
  # Raised on fatal errors to GridFS.
44
41
  class GridError < MongoRubyError; end
45
42
 
@@ -70,7 +70,7 @@ module Mongo
70
70
  opts = opts.dup
71
71
  filename = opts[:filename]
72
72
  opts.merge!(default_grid_io_opts)
73
- file = GridIO.new(@files, @chunks, filename, 'w', opts=opts)
73
+ file = GridIO.new(@files, @chunks, filename, 'w', opts)
74
74
  file.write(data)
75
75
  file.close
76
76
  file.files_id
@@ -71,6 +71,9 @@ module Mongo
71
71
  # GridFileSystem#delete.
72
72
  # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
73
73
  # will be validated using an md5 hash. If validation fails, an exception will be raised.
74
+ # @option opts [Integer] :versions (false) deletes all versions which exceed the number specified to
75
+ # retain ordered by uploadDate. This option only works in 'w' mode. Certain precautions must be taken when
76
+ # deleting GridFS files. See the notes under GridFileSystem#delete.
74
77
  #
75
78
  # @example
76
79
  #
@@ -97,7 +100,12 @@ module Mongo
97
100
  def open(filename, mode, opts={})
98
101
  opts = opts.dup
99
102
  opts.merge!(default_grid_io_opts(filename))
100
- del = opts.delete(:delete_old) && mode == 'w'
103
+ if mode == 'w'
104
+ versions = opts.delete(:versions)
105
+ if opts.delete(:delete_old) || (versions && versions < 1)
106
+ versions = 1
107
+ end
108
+ end
101
109
  file = GridIO.new(@files, @chunks, filename, mode, opts)
102
110
  return file unless block_given?
103
111
  result = nil
@@ -105,9 +113,9 @@ module Mongo
105
113
  result = yield file
106
114
  ensure
107
115
  id = file.close
108
- if del
116
+ if versions
109
117
  self.delete do
110
- @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'])
118
+ @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'], :sort => ['uploadDate', -1], :skip => (versions -1))
111
119
  end
112
120
  end
113
121
  end
@@ -87,9 +87,13 @@ module Mongo
87
87
  end
88
88
 
89
89
  if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
90
- close if error == "not master"
91
- error = "wtimeout" if error == "timeout"
92
- raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
90
+ if error.include?("not master")
91
+ close
92
+ raise ConnectionFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
93
+ else
94
+ error = "wtimeout" if error == "timeout"
95
+ raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
96
+ end
93
97
  end
94
98
 
95
99
  docs[0]
@@ -20,7 +20,6 @@ module Mongo
20
20
 
21
21
  # Instantiates and manages connections to a MongoDB replica set.
22
22
  class ReplSetConnection < Connection
23
- CLEANUP_INTERVAL = 300
24
23
 
25
24
  attr_reader :replica_set_name, :seeds, :refresh_interval, :refresh_mode,
26
25
  :refresh_version
@@ -32,26 +31,25 @@ module Mongo
32
31
  # Connection#arbiters. This is useful if your application needs to connect manually to nodes other
33
32
  # than the primary.
34
33
  #
35
- # @param [Array] args A list of host-port pairs to be used as seed nodes followed by a
36
- # hash containing any options. See the examples below for exactly how to use the constructor.
34
+ # @param [Array] seeds "host:port" strings
37
35
  #
38
- # @option options [String] :rs_name (nil) The name of the replica set to connect to. You
36
+ # @option opts [String] :rs_name (nil) The name of the replica set to connect to. You
39
37
  # can use this option to verify that you're connecting to the right replica set.
40
- # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
38
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
41
39
  # propogated to DB objects instantiated off of this Connection. This
42
40
  # default can be overridden upon instantiation of any DB by explicity setting a :safe value
43
41
  # on initialization.
44
- # @option options [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
42
+ # @option opts [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
45
43
  # objects created from this connection object. If +:secondary+ is chosen, reads will be sent
46
44
  # to one of the closest available secondary nodes. If a secondary node cannot be located, the
47
45
  # read will be sent to the primary.
48
- # @option options [Logger] :logger (nil) Logger instance to receive driver operation log.
49
- # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
46
+ # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
47
+ # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
50
48
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
51
- # @option options [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
49
+ # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
52
50
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
53
51
  # Note: this setting is relevant only for multi-threaded applications.
54
- # @option opts [Float] :op_timeout (30) The number of seconds to wait for a read operation to time out.
52
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
55
53
  # @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
56
54
  # connection attempt.
57
55
  # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
@@ -63,17 +61,17 @@ module Mongo
63
61
  # between calls to check the replica set's state.
64
62
  # @option opts [Boolean] :require_primary (true) If true, require a primary node for the connection
65
63
  # to succeed. Otherwise, connection will succeed as long as there's at least one secondary node.
64
+ # Note: that the number of seed nodes does not have to be equal to the number of replica set members.
65
+ # The purpose of seed nodes is to permit the driver to find at least one replica set member even if a member is down.
66
66
  #
67
- # @example Connect to a replica set and provide two seed nodes. Note that the number of seed nodes does
68
- # not have to be equal to the number of replica set members. The purpose of seed nodes is to permit
69
- # the driver to find at least one replica set member even if a member is down.
70
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
67
+ # @example Connect to a replica set and provide two seed nodes.
68
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
71
69
  #
72
70
  # @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
73
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :rs_name => 'prod')
71
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :name => 'prod')
74
72
  #
75
73
  # @example Connect to a replica set providing two seed nodes and allowing reads from a secondary node:
76
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
74
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :read => :secondary)
77
75
  #
78
76
  # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
79
77
  #
@@ -85,23 +83,33 @@ module Mongo
85
83
  else
86
84
  opts = {}
87
85
  end
88
-
86
+
89
87
  unless args.length > 0
90
88
  raise MongoArgumentError, "A ReplSetConnection requires at least one seed node."
91
89
  end
90
+
91
+ # This is temporary until support for the old format is dropped
92
+ @seeds = []
93
+ if args.first.last.is_a?(Integer)
94
+ warn "Initiating a ReplSetConnection with seeds passed as individual [host, port] array arguments is deprecated."
95
+ warn "Please specify hosts as an array of 'host:port' strings; the old format will be removed in v2.0"
96
+ @seeds = args
97
+ else
98
+ args.first.map do |host_port|
99
+ seed = host_port.split(":")
100
+ seed[1] = seed[1].to_i
101
+ seeds << seed
102
+ end
103
+ end
92
104
 
93
- # The original, immutable list of seed node.
94
105
  # TODO: add a method for replacing this list of node.
95
- @seeds = args
96
106
  @seeds.freeze
97
107
 
98
- # TODO: get rid of this
99
- @nodes = @seeds.dup
100
-
101
108
  # Refresh
102
109
  @refresh_mode = opts.fetch(:refresh_mode, false)
103
110
  @refresh_interval = opts[:refresh_interval] || 90
104
111
  @last_refresh = Time.now
112
+ @refresh_version = 0
105
113
 
106
114
  # No connection manager by default.
107
115
  @manager = nil
@@ -127,7 +135,6 @@ module Mongo
127
135
  end
128
136
 
129
137
  @connected = false
130
- @refresh_version = 0
131
138
 
132
139
  # Replica set name
133
140
  if opts[:rs_name]
@@ -212,7 +219,6 @@ module Mongo
212
219
  @manager = background_manager
213
220
  old_manager.close(:soft => true)
214
221
  @refresh_version += 1
215
-
216
222
  return true
217
223
  end
218
224
 
@@ -312,10 +318,8 @@ module Mongo
312
318
  else
313
319
  connect
314
320
  end
315
-
316
321
  begin
317
322
  socket = get_socket_from_pool(self.read_pool)
318
-
319
323
  if !socket
320
324
  connect
321
325
  socket = get_socket_from_pool(self.primary_pool)
@@ -331,6 +335,26 @@ module Mongo
331
335
  raise ConnectionFailure.new("Could not connect to a node for reading.")
332
336
  end
333
337
  end
338
+
339
+ def checkout_secondary
340
+ if connected?
341
+ sync_refresh
342
+ else
343
+ connect
344
+ end
345
+ begin
346
+ socket = get_socket_from_pool(self.secondary_pool)
347
+ rescue => ex
348
+ checkin(socket) if socket
349
+ raise ex
350
+ end
351
+
352
+ if socket
353
+ socket
354
+ else
355
+ raise ConnectionFailure.new("Could not connect to a secondary for reading.")
356
+ end
357
+ end
334
358
 
335
359
  # Checkout a socket for writing (i.e., a primary node).
336
360
  def checkout_writer
@@ -419,6 +443,10 @@ module Mongo
419
443
  def read_pool
420
444
  @manager ? @manager.read_pool : nil
421
445
  end
446
+
447
+ def secondary_pool
448
+ @manager ? @manager.secondary_pool : nil
449
+ end
422
450
 
423
451
  def secondary_pools
424
452
  @manager ? @manager.secondary_pools : []
@@ -440,64 +468,12 @@ module Mongo
440
468
 
441
469
  # Generic initialization code.
442
470
  def setup(opts)
443
- # Default maximum BSON object size
444
- @max_bson_size = Mongo::DEFAULT_MAX_BSON_SIZE
445
-
446
471
  @safe_mutex_lock = Mutex.new
447
472
  @safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
448
-
449
- # Determine whether to use SSL.
450
- @ssl = opts.fetch(:ssl, false)
451
- if @ssl
452
- @socket_class = Mongo::SSLSocket
453
- else
454
- @socket_class = ::TCPSocket
455
- end
456
-
457
- # Authentication objects
458
- @auths = opts.fetch(:auths, [])
459
-
460
- # Lock for request ids.
461
- @id_lock = Mutex.new
462
-
463
- # Pool size and timeout.
464
- @pool_size = opts[:pool_size] || 1
465
- if opts[:timeout]
466
- warn "The :timeout option has been deprecated " +
467
- "and will be removed in the 2.0 release. Use :pool_timeout instead."
468
- end
469
- @pool_timeout = opts[:pool_timeout] || opts[:timeout] || 5.0
470
-
471
- # Timeout on socket read operation.
472
- @op_timeout = opts[:op_timeout] || 30
473
-
474
- # Timeout on socket connect.
475
- @connect_timeout = opts[:connect_timeout] || 30
476
-
477
- # Mutex for synchronizing pool access
478
- # TODO: remove this.
479
- @connection_mutex = Mutex.new
480
-
481
- # Global safe option. This is false by default.
482
- @safe = opts[:safe] || false
483
-
484
- # Condition variable for signal and wait
485
- @queue = ConditionVariable.new
486
-
487
- @logger = opts[:logger] || nil
488
-
489
- # Clean up connections to dead threads.
490
- @last_cleanup = Time.now
491
- @cleanup_lock = Mutex.new
492
-
493
- if @logger
494
- write_logging_startup_message
495
- end
496
-
497
- @last_refresh = Time.now
498
-
499
- should_connect = opts.fetch(:connect, true)
500
- connect if should_connect
473
+
474
+ opts[:connect_timeout] = opts[:connect_timeout] || 30
475
+
476
+ super opts
501
477
  end
502
478
 
503
479
  # Checkout a socket connected to a node with one of
@@ -1,8 +1,10 @@
1
1
  module Mongo
2
2
  module Logging
3
3
 
4
+ DEBUG_LEVEL = defined?(Logger) ? Logger::DEBUG : 0
5
+
4
6
  def write_logging_startup_message
5
- log(:warn, "Please note that logging negatively impacts client-side performance. " +
7
+ log(:debug, "Please note that logging negatively impacts client-side performance. " +
6
8
  "You should set your logging level no lower than :info in production.")
7
9
  end
8
10
 
@@ -10,37 +12,43 @@ module Mongo
10
12
  def log(level, msg)
11
13
  return unless @logger
12
14
  case level
13
- when :debug then
14
- @logger.debug "MONGODB [DEBUG] #{msg}"
15
- when :warn then
16
- @logger.warn "MONGODB [WARNING] #{msg}"
17
- when :error then
18
- @logger.error "MONGODB [ERROR] #{msg}"
19
15
  when :fatal then
20
16
  @logger.fatal "MONGODB [FATAL] #{msg}"
21
- else
17
+ when :error then
18
+ @logger.error "MONGODB [ERROR] #{msg}"
19
+ when :warn then
20
+ @logger.warn "MONGODB [WARNING] #{msg}"
21
+ when :info then
22
22
  @logger.info "MONGODB [INFO] #{msg}"
23
+ when :debug then
24
+ @logger.debug "MONGODB [DEBUG] #{msg}"
25
+ else
26
+ @logger.debug "MONGODB [DEBUG] #{msg}"
23
27
  end
24
28
  end
25
29
 
26
30
  # Execute the block and log the operation described by name and payload.
27
31
  def instrument(name, payload = {}, &blk)
32
+ start_time = Time.now
28
33
  res = yield
29
- log_operation(name, payload)
34
+ if @logger && (@logger.level == DEBUG_LEVEL)
35
+ log_operation(name, payload, start_time)
36
+ end
30
37
  res
31
38
  end
32
39
 
33
40
  protected
34
41
 
35
- def log_operation(name, payload)
36
- @logger ||= nil
37
- return unless @logger
38
- msg = "#{payload[:database]}['#{payload[:collection]}'].#{name}("
39
- msg += payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
40
- msg += ".skip(#{payload[:skip]})" if payload[:skip]
41
- msg += ".limit(#{payload[:limit]})" if payload[:limit]
42
- msg += ".sort(#{payload[:order]})" if payload[:order]
43
- @logger.debug "MONGODB #{msg}"
42
+ def log_operation(name, payload, start_time)
43
+ msg = "MONGODB "
44
+ msg << "(#{((Time.now - start_time) * 1000).to_i}ms) "
45
+ msg << "#{payload[:database]}['#{payload[:collection]}'].#{name}("
46
+ msg << payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
47
+ msg << ".skip(#{payload[:skip]})" if payload[:skip]
48
+ msg << ".limit(#{payload[:limit]})" if payload[:limit]
49
+ msg << ".sort(#{payload[:order]})" if payload[:order]
50
+
51
+ @logger.debug(msg)
44
52
  end
45
53
 
46
54
  end
@@ -101,7 +101,12 @@ module Mongo
101
101
  rescue ConnectionFailure, OperationFailure, OperationTimeout, SocketError, SystemCallError, IOError => ex
102
102
  @connection.log(:warn, "Attempted connection to node #{host_string} raised " +
103
103
  "#{ex.class}: #{ex.message}")
104
- @socket.close unless @socket.closed?
104
+
105
+ # Socket may already be nil from issuing command
106
+ if @socket && !@socket.closed?
107
+ @socket.close
108
+ end
109
+
105
110
  return nil
106
111
  end
107
112
 
@@ -166,12 +171,16 @@ module Mongo
166
171
  [host, port]
167
172
  end
168
173
 
169
- # Ensure that this node is a member of a replica set.
174
+ # Ensure that this node is a healty member of a replica set.
170
175
  def check_set_membership(config)
171
176
  if !config['hosts']
172
177
  message = "Will not connect to #{host_string} because it's not a member " +
173
178
  "of a replica set."
174
179
  raise ConnectionFailure, message
180
+ elsif config['hosts'].length == 1 && !config['ismaster'] &&
181
+ !config['secondary']
182
+ message = "Attempting to connect to an unhealthy, single-node replica set."
183
+ raise ConnectionFailure, message
175
184
  end
176
185
  end
177
186
 
@@ -2,8 +2,8 @@ module Mongo
2
2
  class PoolManager
3
3
 
4
4
  attr_reader :connection, :arbiters, :primary, :secondaries, :primary_pool,
5
- :read_pool, :secondary_pools, :hosts, :nodes, :max_bson_size,
6
- :tags_to_pools, :tag_map, :members
5
+ :read_pool, :secondary_pool, :secondary_pools, :hosts, :nodes,
6
+ :max_bson_size, :tags_to_pools, :tag_map, :members
7
7
 
8
8
  # Create a new set of connection pools.
9
9
  #
@@ -135,12 +135,12 @@ module Mongo
135
135
  end
136
136
 
137
137
  def initialize_data
138
- @seeds = []
139
138
  @primary = nil
140
139
  @primary_pool = nil
141
140
  @read_pool = nil
142
141
  @arbiters = []
143
142
  @secondaries = []
143
+ @secondary_pool = nil
144
144
  @secondary_pools = []
145
145
  @hosts = Set.new
146
146
  @members = Set.new
@@ -234,11 +234,13 @@ module Mongo
234
234
  # time to figure out which nodes to choose from.
235
235
  def set_read_pool
236
236
  if @secondary_pools.empty?
237
- @read_pool = @primary_pool
237
+ @read_pool = @primary_pool
238
238
  elsif @secondary_pools.size == 1
239
- @read_pool = @secondary_pools[0]
239
+ @read_pool = @secondary_pools[0]
240
+ @secondary_pool = @read_pool
240
241
  else
241
242
  @read_pool = nearby_pool_from_set(@secondary_pools)
243
+ @secondary_pool = @read_pool
242
244
  end
243
245
  end
244
246