mongo 1.6.4 → 1.7.0.rc0

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 (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