mongo 1.6.4 → 1.7.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.md +13 -13
  2. data/Rakefile +7 -10
  3. data/docs/{GridFS.md → GRID_FS.md} +0 -0
  4. data/docs/HISTORY.md +16 -0
  5. data/docs/READ_PREFERENCE.md +70 -10
  6. data/docs/TUTORIAL.md +2 -2
  7. data/lib/mongo.rb +2 -0
  8. data/lib/mongo/collection.rb +62 -11
  9. data/lib/mongo/connection.rb +31 -41
  10. data/lib/mongo/cursor.rb +42 -86
  11. data/lib/mongo/db.rb +10 -8
  12. data/lib/mongo/networking.rb +30 -65
  13. data/lib/mongo/repl_set_connection.rb +91 -170
  14. data/lib/mongo/sharded_connection.rb +221 -0
  15. data/lib/mongo/util/node.rb +29 -36
  16. data/lib/mongo/util/pool.rb +10 -3
  17. data/lib/mongo/util/pool_manager.rb +77 -90
  18. data/lib/mongo/util/sharding_pool_manager.rb +143 -0
  19. data/lib/mongo/util/support.rb +22 -2
  20. data/lib/mongo/util/tcp_socket.rb +10 -15
  21. data/lib/mongo/util/uri_parser.rb +17 -10
  22. data/lib/mongo/version.rb +1 -1
  23. data/test/collection_test.rb +133 -1
  24. data/test/connection_test.rb +50 -4
  25. data/test/db_api_test.rb +3 -3
  26. data/test/db_test.rb +6 -1
  27. data/test/replica_sets/basic_test.rb +3 -6
  28. data/test/replica_sets/complex_connect_test.rb +14 -2
  29. data/test/replica_sets/complex_read_preference_test.rb +237 -0
  30. data/test/replica_sets/connect_test.rb +47 -67
  31. data/test/replica_sets/count_test.rb +1 -1
  32. data/test/replica_sets/cursor_test.rb +70 -0
  33. data/test/replica_sets/read_preference_test.rb +171 -118
  34. data/test/replica_sets/refresh_test.rb +3 -3
  35. data/test/replica_sets/refresh_with_threads_test.rb +2 -2
  36. data/test/replica_sets/rs_test_helper.rb +2 -2
  37. data/test/sharded_cluster/basic_test.rb +112 -0
  38. data/test/sharded_cluster/mongo_config_test.rb +126 -0
  39. data/test/sharded_cluster/sc_test_helper.rb +39 -0
  40. data/test/test_helper.rb +3 -3
  41. data/test/threading/threading_with_large_pool_test.rb +1 -1
  42. data/test/tools/mongo_config.rb +307 -0
  43. data/test/tools/repl_set_manager.rb +12 -12
  44. data/test/unit/collection_test.rb +1 -1
  45. data/test/unit/cursor_test.rb +11 -6
  46. data/test/unit/db_test.rb +4 -0
  47. data/test/unit/grid_test.rb +2 -0
  48. data/test/unit/read_test.rb +39 -8
  49. data/test/uri_test.rb +4 -8
  50. metadata +144 -127
@@ -77,6 +77,9 @@ module Mongo
77
77
  value = collection.read_preference
78
78
  end
79
79
  @read_preference = value.is_a?(Hash) ? value.dup : value
80
+ @tag_sets = opts.fetch(:tag_sets, @collection.tag_sets)
81
+ @acceptable_latency = opts.fetch(:acceptable_latency, @collection.acceptable_latency)
82
+
80
83
  batch_size(opts[:batch_size] || 0)
81
84
 
82
85
  @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
@@ -98,10 +101,6 @@ module Mongo
98
101
  else
99
102
  @command = false
100
103
  end
101
-
102
- @checkin_read_pool = false
103
- @checkin_connection = false
104
- @read_pool = nil
105
104
  end
106
105
 
107
106
  # Guess whether the cursor is alive on the server.
@@ -199,7 +198,7 @@ module Mongo
199
198
  # This method overrides any sort order specified in the Collection#find
200
199
  # method, and only the last sort applied has an effect.
201
200
  #
202
- # @param [Symbol, Array] key_or_list either 1) a key to sort by 2)
201
+ # @param [Symbol, Array, Hash, OrderedHash] order either 1) a key to sort by 2)
203
202
  # an array of [key, direction] pairs to sort by or 3) a hash of
204
203
  # field => direction pairs to sort by. Direction should be specified as
205
204
  # Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING
@@ -339,7 +338,7 @@ module Mongo
339
338
  message.put_int(1)
340
339
  message.put_long(@cursor_id)
341
340
  log(:debug, "Cursor#close #{@cursor_id}")
342
- @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :connection => :reader)
341
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :socket => @socket)
343
342
  end
344
343
  @cursor_id = 0
345
344
  @closed = true
@@ -462,19 +461,37 @@ module Mongo
462
461
  end
463
462
  end
464
463
 
464
+ # Sends initial query -- which is always a read unless it is a command
465
+ #
466
+ # Upon ConnectionFailure, tries query 3 times if socket was not provided
467
+ # and the query is either not a command or is a secondary_ok command.
468
+ #
469
+ # Pins pools upon successful read and unpins pool upon ConnectionFailure
470
+ #
465
471
  def send_initial_query
466
- message = construct_query_message
467
- sock = @socket || checkout_socket_from_connection
472
+ tries = 0
468
473
  instrument(:find, instrument_payload) do
469
474
  begin
470
- results, @n_received, @cursor_id = @connection.receive_message(
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) unless @socket
475
+ tries += 1
476
+ message = construct_query_message
477
+ sock = @socket || checkout_socket_from_connection
478
+ results, @n_received, @cursor_id = @connection.receive_message(
479
+ Mongo::Constants::OP_QUERY, message, nil, sock, @command,
480
+ nil, @options & OP_QUERY_EXHAUST != 0)
481
+ rescue ConnectionFailure => ex
482
+ if tries < 3 && !@socket && (!@command || Mongo::Support.secondary_ok?(@selector))
483
+ @connection.unpin_pool(sock.pool) if sock
484
+ @connection.refresh
485
+ retry
486
+ else
487
+ raise ex
488
+ end
489
+ rescue OperationFailure, OperationTimeout => ex
475
490
  raise ex
491
+ ensure
492
+ checkin_socket(sock) unless @socket
476
493
  end
477
- checkin_socket(sock) unless @socket
494
+ @connection.pin_pool(sock.pool) if !@command && !@socket
478
495
  @returned += @n_received
479
496
  @cache += results
480
497
  @query_run = true
@@ -502,97 +519,36 @@ module Mongo
502
519
  # Cursor id.
503
520
  message.put_long(@cursor_id)
504
521
  log(:debug, "cursor.refresh() for cursor #{@cursor_id}") if @logger
505
- sock = @socket || checkout_socket_for_op_get_more
522
+
523
+ sock = @socket || checkout_socket_from_connection
506
524
 
507
525
  begin
508
- results, @n_received, @cursor_id = @connection.receive_message(
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
526
+ results, @n_received, @cursor_id = @connection.receive_message(
527
+ Mongo::Constants::OP_GET_MORE, message, nil, sock, @command, nil)
528
+ ensure
529
+ checkin_socket(sock) unless @socket
513
530
  end
514
- checkin_socket(sock) unless @socket
531
+
515
532
  @returned += @n_received
516
533
  @cache += results
517
534
  close_cursor_if_query_complete
518
535
  end
519
536
 
520
537
  def checkout_socket_from_connection
521
- socket = nil
522
538
  begin
523
- @checkin_connection = true
524
- if @command || @read_preference == :primary
525
- socket = @connection.checkout_writer
526
- elsif @read_preference == :secondary_only
527
- socket = @connection.checkout_secondary
539
+ if @command && !Mongo::Support::secondary_ok?(@selector)
540
+ @connection.checkout_reader(:primary)
528
541
  else
529
- @read_pool = @connection.read_pool
530
- socket = @connection.checkout_reader
542
+ @connection.checkout_reader(@read_preference, @tag_sets, @acceptable_latency)
531
543
  end
532
544
  rescue SystemStackError, NoMemoryError, SystemCallError => ex
533
545
  @connection.close
534
546
  raise ex
535
547
  end
536
-
537
- socket
538
- end
539
-
540
- def checkout_socket_for_op_get_more
541
- if @read_pool && (@read_pool != @connection.read_pool)
542
- checkout_socket_from_read_pool
543
- else
544
- checkout_socket_from_connection
545
- end
546
- end
547
-
548
- def checkout_socket_from_read_pool
549
- new_pool = @connection.secondary_pools.detect do |pool|
550
- pool.host == @read_pool.host && pool.port == @read_pool.port
551
- end
552
- if new_pool
553
- sock = nil
554
- begin
555
- @read_pool = new_pool
556
- sock = new_pool.checkout
557
- @checkin_read_pool = true
558
- rescue SystemStackError, NoMemoryError, SystemCallError => ex
559
- @connection.close
560
- raise ex
561
- end
562
- return sock
563
- else
564
- raise Mongo::OperationFailure, "Failure to continue iterating " +
565
- "cursor because the the replica set member persisting this " +
566
- "cursor at #{@read_pool.host_string} cannot be found."
567
- end
568
548
  end
569
549
 
570
550
  def checkin_socket(sock)
571
- if @checkin_read_pool
572
- @read_pool.checkin(sock)
573
- @checkin_read_pool = false
574
- elsif @checkin_connection
575
- if @command || @read_preference == :primary
576
- @connection.checkin_writer(sock)
577
- else
578
- @connection.checkin_reader(sock)
579
- end
580
- @checkin_connection = false
581
- end
582
- end
583
-
584
- def force_checkin_socket(sock)
585
- if @checkin_read_pool
586
- @read_pool.checkin(sock)
587
- @checkin_read_pool = false
588
- else
589
- if @command || @read_preference == :primary
590
- @connection.checkin_writer(sock)
591
- else
592
- @connection.checkin_reader(sock)
593
- end
594
- @checkin_connection = false
595
- end
551
+ @connection.checkin(sock)
596
552
  end
597
553
 
598
554
  def construct_query_message
@@ -53,6 +53,9 @@ module Mongo
53
53
  # The length of time that Collection.ensure_index should cache index calls
54
54
  attr_accessor :cache_time
55
55
 
56
+ # Read Preference
57
+ attr_accessor :read_preference, :tag_sets, :acceptable_latency
58
+
56
59
  # Instances of DB are normally obtained by calling Mongo#db.
57
60
  #
58
61
  # @param [String] name the database name.
@@ -72,6 +75,7 @@ module Mongo
72
75
  # value is provided, the default value set on this instance's Connection object will be used. This
73
76
  # default can be overridden upon instantiation of any collection by explicity setting a :safe value
74
77
  # on initialization
78
+ #
75
79
  # @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
76
80
  #
77
81
  # @core databases constructor_details
@@ -87,6 +91,8 @@ module Mongo
87
91
  value = @connection.read_preference
88
92
  end
89
93
  @read_preference = value.is_a?(Hash) ? value.dup : value
94
+ @tag_sets = opts.fetch(:tag_sets, @connection.tag_sets)
95
+ @acceptable_latency = opts.fetch(:acceptable_latency, @connection.acceptable_latency)
90
96
  @cache_time = opts[:cache_time] || 300 #5 minutes.
91
97
  end
92
98
 
@@ -112,8 +118,11 @@ module Mongo
112
118
  end
113
119
  end
114
120
 
115
- @connection.best_available_socket do |socket|
121
+ begin
122
+ socket = @connection.checkout_reader(:primary_preferred)
116
123
  issue_authentication(username, password, save_auth, :socket => socket)
124
+ ensure
125
+ socket.pool.checkin(socket) if socket
117
126
  end
118
127
 
119
128
  @connection.authenticate_pools
@@ -628,13 +637,6 @@ module Mongo
628
637
  doc
629
638
  end
630
639
 
631
- # The value of the read preference. This will be
632
- # either +:primary+, +:secondary+, or an object
633
- # representing the tags to be read from.
634
- def read_preference
635
- @read_preference
636
- end
637
-
638
640
  private
639
641
 
640
642
  def system_command_collection
@@ -23,30 +23,23 @@ module Mongo
23
23
  opts = {}
24
24
  end
25
25
 
26
- connection = opts.fetch(:connection, :writer)
27
-
28
26
  add_message_headers(message, operation)
29
27
  packed_message = message.to_s
30
28
 
31
- sock = nil
29
+ sock = opts.fetch(:socket, nil)
32
30
  begin
33
- if connection == :writer
34
- sock = checkout_writer
31
+ if operation == Mongo::Constants::OP_KILL_CURSORS && read_preference != :primary
32
+ sock ||= checkout_reader
35
33
  else
36
- sock = checkout_reader
34
+ sock ||= checkout_writer
37
35
  end
38
-
39
36
  send_message_on_socket(packed_message, sock)
40
37
  rescue SystemStackError, NoMemoryError, SystemCallError => ex
41
38
  close
42
39
  raise ex
43
40
  ensure
44
41
  if sock
45
- if connection == :writer
46
- checkin_writer(sock)
47
- else
48
- checkin_reader(sock)
49
- end
42
+ sock.pool.checkin(sock)
50
43
  end
51
44
  end
52
45
  end
@@ -77,9 +70,9 @@ module Mongo
77
70
  sock = checkout_writer
78
71
  send_message_on_socket(packed_message, sock)
79
72
  docs, num_received, cursor_id = receive(sock, last_error_id)
80
- checkin_writer(sock)
73
+ checkin(sock)
81
74
  rescue ConnectionFailure, OperationFailure, OperationTimeout => ex
82
- checkin_writer(sock)
75
+ checkin(sock)
83
76
  raise ex
84
77
  rescue SystemStackError, NoMemoryError, SystemCallError => ex
85
78
  close
@@ -119,24 +112,13 @@ module Mongo
119
112
  packed_message = message.to_s
120
113
 
121
114
  result = ''
122
- sock = nil
123
- begin
124
- if socket
125
- sock = socket
126
- should_checkin = false
127
- else
128
- if command || read == :primary
129
- sock = checkout_writer
130
- elsif read == :secondary
131
- sock = checkout_reader
132
- else
133
- sock = checkout_tagged(read)
134
- end
135
- should_checkin = true
136
- end
137
115
 
138
- send_message_on_socket(packed_message, sock)
139
- result = receive(sock, request_id, exhaust)
116
+ begin
117
+ send_message_on_socket(packed_message, socket)
118
+ result = receive(socket, request_id, exhaust)
119
+ rescue ConnectionFailure => ex
120
+ checkin(socket)
121
+ raise ex
140
122
  rescue SystemStackError, NoMemoryError, SystemCallError => ex
141
123
  close
142
124
  raise ex
@@ -145,16 +127,6 @@ module Mongo
145
127
  close if ex.class == IRB::Abort
146
128
  end
147
129
  raise ex
148
- ensure
149
- if should_checkin
150
- if command || read == :primary
151
- checkin_writer(sock)
152
- elsif read == :secondary
153
- checkin_reader(sock)
154
- else
155
- # TODO: sock = checkout_tagged(read)
156
- end
157
- end
158
130
  end
159
131
  result
160
132
  end
@@ -162,30 +134,25 @@ module Mongo
162
134
  private
163
135
 
164
136
  def receive(sock, cursor_id, exhaust=false)
165
- begin
166
- if exhaust
167
- docs = []
168
- num_received = 0
169
-
170
- while(cursor_id != 0) do
171
- receive_header(sock, cursor_id, exhaust)
172
- number_received, cursor_id = receive_response_header(sock)
173
- new_docs, n = read_documents(number_received, sock)
174
- docs += new_docs
175
- num_received += n
176
- end
177
-
178
- return [docs, num_received, cursor_id]
179
- else
137
+ if exhaust
138
+ docs = []
139
+ num_received = 0
140
+
141
+ while(cursor_id != 0) do
180
142
  receive_header(sock, cursor_id, exhaust)
181
143
  number_received, cursor_id = receive_response_header(sock)
182
- docs, num_received = read_documents(number_received, sock)
183
-
184
- return [docs, num_received, cursor_id]
144
+ new_docs, n = read_documents(number_received, sock)
145
+ docs += new_docs
146
+ num_received += n
185
147
  end
186
- rescue Mongo::ConnectionFailure => ex
187
- close
188
- raise ex
148
+
149
+ return [docs, num_received, cursor_id]
150
+ else
151
+ receive_header(sock, cursor_id, exhaust)
152
+ number_received, cursor_id = receive_response_header(sock)
153
+ docs, num_received = read_documents(number_received, sock)
154
+
155
+ return [docs, num_received, cursor_id]
189
156
  end
190
157
  end
191
158
 
@@ -194,7 +161,6 @@ module Mongo
194
161
 
195
162
  # unpacks to size, request_id, response_to
196
163
  response_to = header.unpack('VVV')[2]
197
-
198
164
  if !exhaust && expected_response != response_to
199
165
  raise Mongo::ConnectionFailure, "Expected response #{expected_response} but got #{response_to}"
200
166
  end
@@ -316,7 +282,6 @@ module Mongo
316
282
  end
317
283
  total_bytes_sent
318
284
  rescue => ex
319
- close
320
285
  raise ConnectionFailure, "Operation failed with the following exception: #{ex}:#{ex.message}"
321
286
  end
322
287
  end
@@ -327,7 +292,7 @@ module Mongo
327
292
  begin
328
293
  message = receive_data(length, socket)
329
294
  rescue OperationTimeout, ConnectionFailure => ex
330
- close
295
+ socket.close
331
296
 
332
297
  if ex.class == OperationTimeout
333
298
  raise OperationTimeout, "Timed out waiting on socket read."
@@ -21,11 +21,11 @@ module Mongo
21
21
  # Instantiates and manages connections to a MongoDB replica set.
22
22
  class ReplSetConnection < Connection
23
23
 
24
- REPL_SET_OPTS = [:read, :refresh_mode, :refresh_interval, :require_primary,
25
- :read_secondary, :rs_name, :name]
24
+ REPL_SET_OPTS = [:read, :refresh_mode, :refresh_interval, :read_secondary,
25
+ :rs_name, :name, :tag_sets, :secondary_acceptable_latency_ms]
26
26
 
27
27
  attr_reader :replica_set_name, :seeds, :refresh_interval, :refresh_mode,
28
- :refresh_version, :manager
28
+ :refresh_version, :manager, :tag_sets, :acceptable_latency
29
29
 
30
30
  # Create a connection to a MongoDB replica set.
31
31
  #
@@ -36,38 +36,48 @@ module Mongo
36
36
  # Connection#arbiters. This is useful if your application needs to connect manually to nodes other
37
37
  # than the primary.
38
38
  #
39
- # @param [Array] seeds "host:port" strings
39
+ # @overload initialize(seeds=ENV["MONGODB_URI"], opts={})
40
+ # @param [Array<String>, Array<Array(String, Integer)>] seeds
40
41
  #
41
- # @option opts [String] :name (nil) The name of the replica set to connect to. You
42
- # can use this option to verify that you're connecting to the right replica set.
43
- # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
44
- # propogated to DB objects instantiated off of this Connection. This
45
- # default can be overridden upon instantiation of any DB by explicity setting a :safe value
46
- # on initialization.
47
- # @option opts [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
48
- # objects created from this connection object. If +:secondary+ is chosen, reads will be sent
49
- # to one of the closest available secondary nodes. If a secondary node cannot be located, the
50
- # read will be sent to the primary.
51
- # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
52
- # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
53
- # connection pool. Note: this setting is relevant only for multi-threaded applications.
54
- # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
55
- # this is the number of seconds to wait for a new connection to be released before throwing an exception.
56
- # Note: this setting is relevant only for multi-threaded applications.
57
- # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
58
- # @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
59
- # connection attempt.
60
- # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
61
- # @option opts [Boolean] :refresh_mode (false) Set this to :sync to periodically update the
62
- # state of the connection every :refresh_interval seconds. Replica set connection failures
63
- # will always trigger a complete refresh. This option is useful when you want to add new nodes
64
- # or remove replica set nodes not currently in use by the driver.
65
- # @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
66
- # between calls to check the replica set's state.
67
- # @option opts [Boolean] :require_primary (true) If true, require a primary node for the connection
68
- # to succeed. Otherwise, connection will succeed as long as there's at least one secondary node.
69
- # Note: that the number of seed nodes does not have to be equal to the number of replica set members.
70
- # The purpose of seed nodes is to permit the driver to find at least one replica set member even if a member is down.
42
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
43
+ # propagated to DB objects instantiated off of this Connection. This
44
+ # default can be overridden upon instantiation of any DB by explicitly setting a :safe value
45
+ # on initialization.
46
+ # @option opts [:primary, :primary_preferred, :secondary, :secondary_preferred, :nearest] :read_preference (:primary)
47
+ # A "read preference" determines the candidate replica set members to which a query or command can be sent.
48
+ # [:primary]
49
+ # * Read from primary only.
50
+ # * Cannot be combined with tags.
51
+ # [:primary_preferred]
52
+ # * Read from primary if available, otherwise read from a secondary.
53
+ # [:secondary]
54
+ # * Read from secondary if available.
55
+ # [:secondary_preferred]
56
+ # * Read from a secondary if available, otherwise read from the primary.
57
+ # [:nearest]
58
+ # * Read from any member.
59
+ # @option opts [Array<Hash{ String, Symbol => Tag Value }>] :tag_sets ([])
60
+ # Read from replica-set members with these tags.
61
+ # @option opts [Integer] :secondary_acceptable_latency_ms (15) The acceptable
62
+ # nearest available member for a member to be considered "near".
63
+ # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
64
+ # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
65
+ # connection pool. Note: this setting is relevant only for multi-threaded applications.
66
+ # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
67
+ # this is the number of seconds to wait for a new connection to be released before throwing an exception.
68
+ # Note: this setting is relevant only for multi-threaded applications.
69
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
70
+ # @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
71
+ # connection attempt.
72
+ # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
73
+ # @option opts [Boolean] :refresh_mode (false) Set this to :sync to periodically update the
74
+ # state of the connection every :refresh_interval seconds. Replica set connection failures
75
+ # will always trigger a complete refresh. This option is useful when you want to add new nodes
76
+ # or remove replica set nodes not currently in use by the driver.
77
+ # @option opts [Integer] :refresh_interval (90) If :refresh_mode is enabled, this is the number of seconds
78
+ # between calls to check the replica set's state.
79
+ # @note the number of seed nodes does not have to be equal to the number of replica set members.
80
+ # The purpose of seed nodes is to permit the driver to find at least one replica set member even if a member is down.
71
81
  #
72
82
  # @example Connect to a replica set and provide two seed nodes.
73
83
  # Mongo::ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
@@ -80,26 +90,20 @@ module Mongo
80
90
  #
81
91
  # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
82
92
  #
83
- # @raise [MongoArgumentError] If called with no arguments and <code>ENV["MONGODB_URI"]</code> implies a direct connection.
93
+ # @raise [MongoArgumentError] This is raised for usage errors.
84
94
  #
85
- # @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
86
- # driver fails to connect to a replica set with that name.
95
+ # @raise [ConnectionFailure] This is raised for the various connection failures.
87
96
  def initialize(*args)
88
- if args.last.is_a?(Hash)
89
- opts = args.pop
90
- else
91
- opts = {}
92
- end
93
-
97
+ opts = args.last.is_a?(Hash) ? args.pop : {}
94
98
  nodes = args
95
99
 
96
100
  if nodes.empty? and ENV.has_key?('MONGODB_URI')
97
- parser = URIParser.new ENV['MONGODB_URI'], opts
101
+ parser = URIParser.new ENV['MONGODB_URI']
98
102
  if parser.direct?
99
103
  raise MongoArgumentError, "Mongo::ReplSetConnection.new called with no arguments, but ENV['MONGODB_URI'] implies a direct connection."
100
104
  end
101
- opts = parser.connection_options
102
- nodes = parser.nodes
105
+ opts = parser.connection_options.merge! opts
106
+ nodes = [parser.nodes]
103
107
  end
104
108
 
105
109
  unless nodes.length > 0
@@ -118,7 +122,6 @@ module Mongo
118
122
  end
119
123
  end
120
124
 
121
- # TODO: add a method for replacing this list of node.
122
125
  @seeds.freeze
123
126
 
124
127
  # Refresh
@@ -160,8 +163,8 @@ module Mongo
160
163
  @connect_mutex.synchronize do
161
164
  return if @connected
162
165
 
163
- discovered_seeds = @manager ? @manager.seeds : []
164
- @manager = PoolManager.new(self, discovered_seeds)
166
+ seeds = @manager.nil? ? @seeds : @manager.seeds
167
+ @manager = PoolManager.new(self, seeds)
165
168
 
166
169
  Thread.current[:managers] ||= Hash.new
167
170
  Thread.current[:managers][self] = @manager
@@ -169,10 +172,7 @@ module Mongo
169
172
  @manager.connect
170
173
  @refresh_version += 1
171
174
 
172
- if @require_primary && @manager.primary.nil? #TODO: in v2.0, we'll let this be optional and do a lazy connect.
173
- close
174
- raise ConnectionFailure, "Failed to connect to primary node."
175
- elsif @manager.read_pool.nil?
175
+ if @manager.pools.empty?
176
176
  close
177
177
  raise ConnectionFailure, "Failed to connect to any node."
178
178
  else
@@ -213,7 +213,7 @@ module Mongo
213
213
  # to get the refresh lock.
214
214
  def hard_refresh!
215
215
  log(:info, "Initiating hard refresh...")
216
- discovered_seeds = @manager ? @manager.seeds : []
216
+ discovered_seeds = @manager.seeds
217
217
  new_manager = PoolManager.new(self, discovered_seeds | @seeds)
218
218
  new_manager.connect
219
219
 
@@ -228,7 +228,7 @@ module Mongo
228
228
  end
229
229
 
230
230
  def connected?
231
- @connected && (@manager.primary_pool || @manager.read_pool)
231
+ @connected && !@manager.pools.empty?
232
232
  end
233
233
 
234
234
  # @deprecated
@@ -296,124 +296,68 @@ module Mongo
296
296
  end
297
297
 
298
298
  # Returns +true+ if it's okay to read from a secondary node.
299
- # Since this is a replica set, this must always be true.
300
299
  #
301
300
  # This method exist primarily so that Cursor objects will
302
301
  # generate query messages with a slaveOkay value of +true+.
303
302
  #
304
303
  # @return [Boolean] +true+
305
304
  def slave_ok?
306
- true
305
+ @read != :primary
307
306
  end
308
307
 
309
308
  def authenticate_pools
310
- if primary_pool
311
- primary_pool.authenticate_existing
312
- end
313
- secondary_pools.each do |pool|
314
- pool.authenticate_existing
315
- end
309
+ pools.each { |pool| pool.authenticate_existing }
316
310
  end
317
311
 
318
312
  def logout_pools(db)
319
- if primary_pool
320
- primary_pool.logout_existing(db)
321
- end
322
- secondary_pools.each do |pool|
323
- pool.logout_existing(db)
324
- end
313
+ pools.each { |pool| pool.logout_existing(db) }
325
314
  end
326
315
 
327
316
  # Generic socket checkout
328
317
  # Takes a block that returns a socket from pool
329
- def checkout(&block)
330
- if connected?
331
- sync_refresh
332
- else
333
- connect
334
- end
335
-
318
+ def checkout
319
+ ensure_manager
320
+
321
+ connected? ? sync_refresh : connect
322
+
336
323
  begin
337
- socket = block.call
324
+ socket = yield
338
325
  rescue => ex
339
326
  checkin(socket) if socket
340
327
  raise ex
341
328
  end
342
-
329
+
343
330
  if socket
344
331
  socket
345
332
  else
346
333
  @connected = false
347
334
  raise ConnectionFailure.new("Could not checkout a socket.")
348
335
  end
349
- end
350
-
351
- # Checkout best available socket by trying primary
352
- # pool first and then falling back to secondary.
353
- def checkout_best
354
- checkout do
355
- socket = get_socket_from_pool(:primary)
356
- if !socket
357
- connect
358
- socket = get_socket_from_pool(:secondary)
359
- end
360
- socket
361
- end
336
+ socket
362
337
  end
363
338
 
364
- # Checkout a socket for reading (i.e., a secondary node).
365
- # Note that @read_pool might point to the primary pool
366
- # if no read pool has been defined.
367
- def checkout_reader
368
- checkout do
369
- socket = get_socket_from_pool(:read)
370
- if !socket
371
- connect
372
- socket = get_socket_from_pool(:primary)
373
- end
374
- socket
375
- end
376
- end
377
-
378
- # Checkout a socket from a secondary
379
- # For :read_preference => :secondary_only
380
- def checkout_secondary
339
+ def checkout_reader(mode=@read, tag_sets=@tag_sets, acceptable_latency=@acceptable_latency)
381
340
  checkout do
382
- get_socket_from_pool(:secondary)
341
+ pool = read_pool(mode, tag_sets, acceptable_latency)
342
+ get_socket_from_pool(pool)
383
343
  end
384
344
  end
385
345
 
386
346
  # Checkout a socket for writing (i.e., a primary node).
387
347
  def checkout_writer
388
348
  checkout do
389
- get_socket_from_pool(:primary)
349
+ get_socket_from_pool(primary_pool)
390
350
  end
391
351
  end
392
352
 
393
353
  # Checkin a socket used for reading.
394
- def checkin_reader(socket)
395
- if socket
396
- socket.pool.checkin(socket)
397
- end
398
- sync_refresh
399
- end
400
-
401
- # Checkin a socket used for writing.
402
- def checkin_writer(socket)
403
- if socket
354
+ def checkin(socket)
355
+ if socket && socket.pool
404
356
  socket.pool.checkin(socket)
405
357
  end
406
358
  sync_refresh
407
359
  end
408
360
 
409
- def close_socket(socket)
410
- begin
411
- socket.close if socket
412
- rescue IOError
413
- log(:info, "Tried to close socket #{socket} but already closed.")
414
- end
415
- end
416
-
417
361
  def ensure_manager
418
362
  Thread.current[:managers] ||= Hash.new
419
363
 
@@ -422,25 +366,19 @@ module Mongo
422
366
  end
423
367
  end
424
368
 
425
- def get_socket_from_pool(pool_type)
426
- ensure_manager
369
+ def pin_pool(pool)
370
+ @manager.pinned_pools[Thread.current] = pool if @manager
371
+ end
427
372
 
428
- pool = case pool_type
429
- when :primary
430
- primary_pool
431
- when :secondary
432
- secondary_pool
433
- when :read
434
- read_pool
435
- end
373
+ def unpin_pool(pool)
374
+ @manager.pinned_pools[Thread.current] = nil if @manager
375
+ end
436
376
 
377
+ def get_socket_from_pool(pool)
437
378
  begin
438
- if pool
439
- pool.checkout
440
- end
441
- rescue ConnectionFailure => ex
442
- log(:info, "Failed to checkout from #{pool} with #{ex.class}; #{ex.message}")
443
- return nil
379
+ pool.checkout if pool
380
+ rescue ConnectionFailure
381
+ nil
444
382
  end
445
383
  end
446
384
 
@@ -469,8 +407,8 @@ module Mongo
469
407
  local_manager ? local_manager.primary_pool : nil
470
408
  end
471
409
 
472
- def read_pool
473
- local_manager ? local_manager.read_pool : nil
410
+ def read_pool(mode=@read, tags=@tag_sets, acceptable_latency=@acceptable_latency)
411
+ local_manager ? local_manager.read_pool(mode, tags, acceptable_latency) : nil
474
412
  end
475
413
 
476
414
  def secondary_pool
@@ -497,9 +435,6 @@ module Mongo
497
435
 
498
436
  # Parse option hash
499
437
  def setup(opts)
500
- # Require a primary node to connect?
501
- @require_primary = opts.fetch(:require_primary, true)
502
-
503
438
  # Refresh
504
439
  @refresh_mode = opts.fetch(:refresh_mode, false)
505
440
  @refresh_interval = opts.fetch(:refresh_interval, 90)
@@ -516,17 +451,20 @@ module Mongo
516
451
  "Refresh mode must be either :sync or false."
517
452
  end
518
453
 
519
- # Are we allowing reads from secondaries?
454
+ # Determine read preference
520
455
  if opts[:read_secondary]
521
456
  warn ":read_secondary options has now been deprecated and will " +
522
457
  "be removed in driver v2.0. Use the :read option instead."
523
458
  @read_secondary = opts.fetch(:read_secondary, false)
524
- @read = :secondary
459
+ @read = :secondary_preferred
525
460
  else
526
461
  @read = opts.fetch(:read, :primary)
527
462
  Mongo::Support.validate_read_preference(@read)
528
463
  end
529
464
 
465
+ @tag_sets = opts.fetch(:tag_sets, [])
466
+ @acceptable_latency = opts.fetch(:secondary_acceptable_latency_ms, 15)
467
+
530
468
  # Replica set name
531
469
  if opts[:rs_name]
532
470
  warn ":rs_name option has been deprecated and will be removed in v2.0. " +
@@ -540,24 +478,6 @@ module Mongo
540
478
 
541
479
  super opts
542
480
  end
543
-
544
- # Checkout a socket connected to a node with one of
545
- # the provided tags. If no such node exists, raise
546
- # an exception.
547
- #
548
- # NOTE: will be available in driver release v2.0.
549
- def checkout_tagged(tags)
550
- tags.each do |k, v|
551
- pool = self.tag_map[{k.to_s => v}]
552
- if pool
553
- socket = pool.checkout
554
- return socket
555
- end
556
- end
557
-
558
- raise NodeWithTagsNotFound,
559
- "Could not find a connection tagged with #{tags}."
560
- end
561
481
 
562
482
  def prune_managers
563
483
  @old_managers.each do |manager|
@@ -574,8 +494,9 @@ module Mongo
574
494
  def sync_refresh
575
495
  if @refresh_mode == :sync &&
576
496
  ((Time.now - @last_refresh) > @refresh_interval)
497
+
577
498
  @last_refresh = Time.now
578
-
499
+
579
500
  if @refresh_mutex.try_lock
580
501
  begin
581
502
  refresh