mongo 2.4.0.rc1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo/auth/cr/conversation.rb +1 -1
  5. data/lib/mongo/auth/ldap/conversation.rb +1 -1
  6. data/lib/mongo/auth/scram/conversation.rb +1 -1
  7. data/lib/mongo/auth/x509/conversation.rb +4 -2
  8. data/lib/mongo/client.rb +7 -4
  9. data/lib/mongo/cluster.rb +55 -5
  10. data/lib/mongo/cluster/app_metadata.rb +7 -1
  11. data/lib/mongo/cluster/topology.rb +7 -6
  12. data/lib/mongo/cluster/topology/replica_set.rb +48 -2
  13. data/lib/mongo/cluster/topology/sharded.rb +47 -1
  14. data/lib/mongo/cluster/topology/single.rb +55 -4
  15. data/lib/mongo/cluster/topology/unknown.rb +65 -9
  16. data/lib/mongo/error/invalid_server_preference.rb +3 -1
  17. data/lib/mongo/event.rb +8 -0
  18. data/lib/mongo/event/description_changed.rb +20 -2
  19. data/lib/mongo/event/member_discovered.rb +65 -0
  20. data/lib/mongo/event/primary_elected.rb +3 -1
  21. data/lib/mongo/event/standalone_discovered.rb +1 -1
  22. data/lib/mongo/monitoring.rb +41 -0
  23. data/lib/mongo/monitoring/event.rb +6 -0
  24. data/lib/mongo/monitoring/event/server_closed.rb +46 -0
  25. data/lib/mongo/monitoring/event/server_description_changed.rb +58 -0
  26. data/lib/mongo/monitoring/event/server_opening.rb +46 -0
  27. data/lib/mongo/monitoring/event/topology_changed.rb +46 -0
  28. data/lib/mongo/monitoring/event/topology_closed.rb +41 -0
  29. data/lib/mongo/monitoring/event/topology_opening.rb +41 -0
  30. data/lib/mongo/monitoring/publishable.rb +12 -0
  31. data/lib/mongo/monitoring/sdam_log_subscriber.rb +54 -0
  32. data/lib/mongo/monitoring/server_closed_log_subscriber.rb +30 -0
  33. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +33 -0
  34. data/lib/mongo/monitoring/server_opening_log_subscriber.rb +30 -0
  35. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +40 -0
  36. data/lib/mongo/monitoring/topology_opening_log_subscriber.rb +30 -0
  37. data/lib/mongo/server.rb +6 -0
  38. data/lib/mongo/server/connection.rb +1 -1
  39. data/lib/mongo/server/description.rb +23 -3
  40. data/lib/mongo/server/description/inspector.rb +4 -2
  41. data/lib/mongo/server/description/inspector/description_changed.rb +2 -2
  42. data/lib/mongo/server/description/inspector/member_discovered.rb +59 -0
  43. data/lib/mongo/server/description/inspector/primary_elected.rb +2 -0
  44. data/lib/mongo/server_selector.rb +10 -5
  45. data/lib/mongo/server_selector/nearest.rb +1 -1
  46. data/lib/mongo/server_selector/primary_preferred.rb +1 -1
  47. data/lib/mongo/server_selector/secondary.rb +1 -1
  48. data/lib/mongo/server_selector/secondary_preferred.rb +1 -1
  49. data/lib/mongo/server_selector/selectable.rb +24 -12
  50. data/lib/mongo/uri.rb +1 -1
  51. data/lib/mongo/version.rb +1 -1
  52. data/mongo.gemspec +1 -1
  53. data/spec/mongo/auth/cr_spec.rb +6 -1
  54. data/spec/mongo/auth/ldap_spec.rb +6 -1
  55. data/spec/mongo/auth/scram_spec.rb +6 -1
  56. data/spec/mongo/auth/x509/conversation_spec.rb +69 -0
  57. data/spec/mongo/auth/x509_spec.rb +9 -4
  58. data/spec/mongo/client_spec.rb +40 -2
  59. data/spec/mongo/cluster/topology/replica_set_spec.rb +218 -9
  60. data/spec/mongo/cluster/topology/sharded_spec.rb +17 -2
  61. data/spec/mongo/cluster/topology/single_spec.rb +19 -4
  62. data/spec/mongo/cluster/topology/unknown_spec.rb +19 -1
  63. data/spec/mongo/cluster/topology_spec.rb +11 -7
  64. data/spec/mongo/cluster_spec.rb +25 -7
  65. data/spec/mongo/max_staleness_spec.rb +40 -22
  66. data/spec/mongo/monitoring_spec.rb +2 -2
  67. data/spec/mongo/sdam_monitoring_spec.rb +60 -0
  68. data/spec/mongo/sdam_spec.rb +77 -0
  69. data/spec/mongo/server/connection_pool_spec.rb +6 -1
  70. data/spec/mongo/server/connection_spec.rb +6 -1
  71. data/spec/mongo/server/description_spec.rb +90 -1
  72. data/spec/mongo/server_selection_spec.rb +7 -6
  73. data/spec/mongo/server_selector/nearest_spec.rb +7 -7
  74. data/spec/mongo/server_selector/primary_preferred_spec.rb +7 -7
  75. data/spec/mongo/server_selector/primary_spec.rb +4 -4
  76. data/spec/mongo/server_selector/secondary_preferred_spec.rb +6 -6
  77. data/spec/mongo/server_selector/secondary_spec.rb +6 -6
  78. data/spec/mongo/server_selector_spec.rb +8 -0
  79. data/spec/mongo/server_spec.rb +6 -1
  80. data/spec/mongo/uri_spec.rb +4 -4
  81. data/spec/spec_helper.rb +2 -0
  82. data/spec/support/max_staleness/ReplicaSetNoPrimary/Incompatible.yml +4 -4
  83. data/spec/support/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
  84. data/spec/support/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
  85. data/spec/support/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
  86. data/spec/support/max_staleness/ReplicaSetNoPrimary/NoKnownServers.yml +15 -0
  87. data/spec/support/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +1 -1
  88. data/spec/support/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +3 -3
  89. data/spec/support/max_staleness/ReplicaSetNoPrimary/Secondary.yml +3 -3
  90. data/spec/support/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +1 -1
  91. data/spec/support/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +3 -3
  92. data/spec/support/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +23 -0
  93. data/spec/support/max_staleness/ReplicaSetWithPrimary/Incompatible.yml +4 -4
  94. data/spec/support/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +5 -5
  95. data/spec/support/max_staleness/ReplicaSetWithPrimary/{ShortHeartbeartShortMaxStaleness2.yml → LongHeartbeat.yml} +4 -4
  96. data/spec/support/max_staleness/ReplicaSetWithPrimary/{ShortHeartbeartShortMaxStaleness.yml → LongHeartbeat2.yml} +6 -10
  97. data/spec/support/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +3 -2
  98. data/spec/support/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
  99. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
  100. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
  101. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +3 -3
  102. data/spec/support/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
  103. data/spec/support/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred_incompatible.yml +3 -3
  104. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +1 -1
  105. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +3 -3
  106. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
  107. data/spec/support/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +3 -3
  108. data/spec/support/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
  109. data/spec/support/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +7 -11
  110. data/spec/support/max_staleness/Sharded/Incompatible.yml +4 -4
  111. data/spec/support/max_staleness/Sharded/SmallMaxStaleness.yml +10 -2
  112. data/spec/support/max_staleness/Single/Incompatible.yml +4 -4
  113. data/spec/support/max_staleness/Single/SmallMaxStaleness.yml +2 -2
  114. data/spec/support/max_staleness/Unknown/SmallMaxStaleness.yml +14 -0
  115. data/spec/support/sdam/rs/primary_mismatched_me.yml +2 -2
  116. data/spec/support/sdam/rs/secondary_mismatched_me.yml +2 -2
  117. data/spec/support/sdam_monitoring.rb +144 -0
  118. data/spec/support/sdam_monitoring/replica_set_with_no_primary.yml +112 -0
  119. data/spec/support/sdam_monitoring/replica_set_with_primary.yml +111 -0
  120. data/spec/support/sdam_monitoring/replica_set_with_removal.yml +106 -0
  121. data/spec/support/sdam_monitoring/required_replica_set.yml +84 -0
  122. data/spec/support/sdam_monitoring/standalone.yml +70 -0
  123. data/spec/support/server_discovery_and_monitoring.rb +34 -1
  124. data/spec/support/server_selection.rb +14 -11
  125. data/spec/support/shared/server_selector.rb +6 -0
  126. metadata +49 -13
  127. metadata.gz.sig +0 -0
  128. data/spec/mongo/server_discovery_and_monitoring_spec.rb +0 -115
@@ -0,0 +1,40 @@
1
+ # Copyright (C) 2016 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Monitoring
17
+
18
+ # Subscribes to Topology Changed events and logs them.
19
+ #
20
+ # @since 2.4.0
21
+ class TopologyChangedLogSubscriber < SDAMLogSubscriber
22
+
23
+ private
24
+
25
+ def log_event(event)
26
+ if event.previous_topology != event.new_topology
27
+ log_debug(
28
+ "Topology type '#{event.previous_topology.display_name.downcase}' changed to " +
29
+ "type '#{event.new_topology.display_name.downcase}'."
30
+ )
31
+ else
32
+ log_debug(
33
+ "There was a change in the members of the '#{event.new_topology.display_name.downcase}' " +
34
+ "topology."
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright (C) 2016 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Monitoring
17
+
18
+ # Subscribes to Topology Openeing events and logs them.
19
+ #
20
+ # @since 2.4.0
21
+ class TopologyOpeningLogSubscriber < SDAMLogSubscriber
22
+
23
+ private
24
+
25
+ def log_event(event)
26
+ log_debug("Topology type '#{event.topology.display_name.downcase}' initializing.")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -27,6 +27,7 @@ module Mongo
27
27
  # @since 2.0.0
28
28
  class Server
29
29
  extend Forwardable
30
+ include Monitoring::Publishable
30
31
 
31
32
  # @return [ String ] The configured address for the server.
32
33
  attr_reader :address
@@ -45,6 +46,7 @@ module Mongo
45
46
 
46
47
  # Get the description from the monitor and scan on monitor.
47
48
  def_delegators :monitor, :description, :scan!, :heartbeat_frequency, :last_scan
49
+ alias :heartbeat_frequency_seconds :heartbeat_frequency
48
50
 
49
51
  # Delegate convenience methods to the monitor description.
50
52
  def_delegators :description,
@@ -163,6 +165,10 @@ module Mongo
163
165
  @cluster = cluster
164
166
  @monitoring = monitoring
165
167
  @options = options.freeze
168
+ publish_sdam_event(
169
+ Monitoring::SERVER_OPENING,
170
+ Monitoring::Event::ServerOpening.new(address, cluster.topology)
171
+ )
166
172
  @monitor = Monitor.new(address, event_listeners, options.merge(app_metadata: cluster.app_metadata))
167
173
  monitor.scan!
168
174
  monitor.run!
@@ -175,7 +175,7 @@ module Mongo
175
175
  end
176
176
 
177
177
  def authenticate!
178
- if options[:user]
178
+ if options[:user] || options[:auth_mech]
179
179
  user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism).merge(options))
180
180
  @server.handle_auth_failure! do
181
181
  Auth.get(user).login(self)
@@ -239,7 +239,7 @@ module Mongo
239
239
  #
240
240
  # @param [ Address ] address The server address.
241
241
  # @param [ Hash ] config The result of the ismaster command.
242
- # @param [ Float ] average_round_trip_time The moving average time (ms) the ismaster
242
+ # @param [ Float ] average_round_trip_time The moving average time (sec) the ismaster
243
243
  # call took to complete.
244
244
  #
245
245
  # @since 2.0.0
@@ -480,6 +480,24 @@ module Mongo
480
480
  !!config[SECONDARY] && !replica_set_name.nil?
481
481
  end
482
482
 
483
+ # Returns the server type as a symbol.
484
+ #
485
+ # @example Get the server type.
486
+ # description.server_type
487
+ #
488
+ # @return [ Symbol ] The server type.
489
+ #
490
+ # @since 2.4.0
491
+ def server_type
492
+ return :arbiter if arbiter?
493
+ return :ghost if ghost?
494
+ return :sharded if mongos?
495
+ return :primary if primary?
496
+ return :secondary if secondary?
497
+ return :standalone if standalone?
498
+ :unknown
499
+ end
500
+
483
501
  # Is this server a standalone server?
484
502
  #
485
503
  # @example Is the server standalone?
@@ -501,7 +519,8 @@ module Mongo
501
519
  #
502
520
  # @since 2.0.0
503
521
  def unknown?
504
- config.empty? || config[Operation::Result::OK] != 1
522
+ config.empty? || (config[Operation::Result::OK] &&
523
+ config[Operation::Result::OK] != 1)
505
524
  end
506
525
 
507
526
  # A result from another server's ismaster command before this server has
@@ -592,6 +611,7 @@ module Mongo
592
611
  # @since 2.0.6
593
612
  def ==(other)
594
613
  return false if self.class != other.class
614
+ return false if unknown? || other.unknown?
595
615
  compare_config(other)
596
616
  end
597
617
  alias_method :eql?, :==
@@ -599,7 +619,7 @@ module Mongo
599
619
  private
600
620
 
601
621
  def compare_config(other)
602
- !config.keys.empty? && config.keys.all? do |k|
622
+ config.keys.all? do |k|
603
623
  config[k] == other.config[k] || EXCLUDE_FOR_COMPARISON.include?(k)
604
624
  end
605
625
  end
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ require 'mongo/server/description/inspector/member_discovered'
16
+ # @deprecated. Will be removed in 3.0
15
17
  require 'mongo/server/description/inspector/primary_elected'
16
18
  require 'mongo/server/description/inspector/description_changed'
17
19
  require 'mongo/server/description/inspector/standalone_discovered'
@@ -34,7 +36,7 @@ module Mongo
34
36
  INSPECTORS = [
35
37
  Inspector::StandaloneDiscovered,
36
38
  Inspector::DescriptionChanged,
37
- Inspector::PrimaryElected
39
+ Inspector::MemberDiscovered
38
40
  ].freeze
39
41
 
40
42
  # @return [ Array ] inspectors The description inspectors.
@@ -61,7 +63,7 @@ module Mongo
61
63
  #
62
64
  # @param [ Description ] description The old description.
63
65
  # @param [ Hash ] ismaster The updated ismaster.
64
- # @param [ Float ] average_round_trip_time The moving average round trip time (ms).
66
+ # @param [ Float ] average_round_trip_time The moving average round trip time (sec).
65
67
  #
66
68
  # @return [ Description ] The new description.
67
69
  #
@@ -46,8 +46,8 @@ module Mongo
46
46
  #
47
47
  # @since 2.0.0
48
48
  def run(description, updated)
49
- unless description == updated
50
- publish(Event::DESCRIPTION_CHANGED, updated)
49
+ unless (description.unknown? && updated.unknown?) || (description == updated)
50
+ publish(Event::DESCRIPTION_CHANGED, description, updated)
51
51
  end
52
52
  end
53
53
  end
@@ -0,0 +1,59 @@
1
+ # Copyright (C) 2015 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Server
17
+ class Description
18
+ class Inspector
19
+
20
+ # Handles inspecting the result of an ismaster command to check if this
21
+ # a server is a member of a known topology.
22
+ #
23
+ # @since 2.4.0
24
+ class MemberDiscovered
25
+ include Event::Publisher
26
+
27
+ # Instantiate the member discovered inspection.
28
+ #
29
+ # @example Instantiate the inspection.
30
+ # MemberDiscovered.new(listeners)
31
+ #
32
+ # @param [ Event::Listeners ] event_listeners The event listeners.
33
+ #
34
+ # @since 2.4.0
35
+ def initialize(event_listeners)
36
+ @event_listeners = event_listeners
37
+ end
38
+
39
+ # Run the member discovered inspection.
40
+ #
41
+ # @example Run the inspection.
42
+ # MemberDiscovered.run(description, {})
43
+ #
44
+ # @param [ Description ] description The server description.
45
+ # @param [ Description ] updated The updated description.
46
+ #
47
+ # @since 2.4.0
48
+ def run(description, updated)
49
+ if (!description.primary? && updated.primary?) ||
50
+ (!description.mongos? && updated.mongos?) ||
51
+ (description.unknown? && !updated.unknown?)
52
+ publish(Event::MEMBER_DISCOVERED, description, updated)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -21,6 +21,8 @@ module Mongo
21
21
  # server was elected primary.
22
22
  #
23
23
  # @since 2.0.0
24
+ #
25
+ # @deprecated. Will be removed in 3.0
24
26
  class PrimaryElected
25
27
  include Event::Publisher
26
28
 
@@ -38,6 +38,11 @@ module Mongo
38
38
  # @since 2.0.0
39
39
  SERVER_SELECTION_TIMEOUT = 30.freeze
40
40
 
41
+ # The smallest allowed max staleness value, in seconds.
42
+ #
43
+ # @since 2.4.0
44
+ SMALLEST_MAX_STALENESS_SECONDS = 90
45
+
41
46
  # Primary read preference.
42
47
  #
43
48
  # @since 2.1.0
@@ -48,11 +53,11 @@ module Mongo
48
53
  #
49
54
  # @since 2.0.0
50
55
  PREFERENCES = {
51
- nearest: Nearest,
52
- primary: Primary,
53
- primary_preferred: PrimaryPreferred,
54
- secondary: Secondary,
55
- secondary_preferred: SecondaryPreferred
56
+ nearest: Nearest,
57
+ primary: Primary,
58
+ primary_preferred: PrimaryPreferred,
59
+ secondary: Secondary,
60
+ secondary_preferred: SecondaryPreferred
56
61
  }.freeze
57
62
 
58
63
  # Create a server selector object.
@@ -67,7 +67,7 @@ module Mongo
67
67
  def to_mongos
68
68
  preference = { :mode => 'nearest' }
69
69
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
70
- preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
70
+ preference.merge!({ maxStalenessSeconds: max_staleness }) if max_staleness
71
71
  preference
72
72
  end
73
73
 
@@ -68,7 +68,7 @@ module Mongo
68
68
  def to_mongos
69
69
  preference = { :mode => 'primaryPreferred' }
70
70
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
71
- preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
71
+ preference.merge!({ maxStalenessSeconds: max_staleness }) if max_staleness
72
72
  preference
73
73
  end
74
74
 
@@ -68,7 +68,7 @@ module Mongo
68
68
  def to_mongos
69
69
  preference = { :mode => 'secondary' }
70
70
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
71
- preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
71
+ preference.merge!({ maxStalenessSeconds: max_staleness }) if max_staleness
72
72
  preference
73
73
  end
74
74
 
@@ -71,7 +71,7 @@ module Mongo
71
71
  return nil if tag_sets.empty? && max_staleness.nil?
72
72
  preference = { mode: 'secondaryPreferred' }
73
73
  preference.merge!({ tags: tag_sets }) unless tag_sets.empty?
74
- preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
74
+ preference.merge!({ maxStalenessSeconds: max_staleness }) if max_staleness
75
75
  preference
76
76
  end
77
77
 
@@ -26,7 +26,7 @@ module Mongo
26
26
  # @return [ Array ] tag_sets The tag sets used to select servers.
27
27
  attr_reader :tag_sets
28
28
 
29
- # @return [ Float ] max_staleness The maximum replication lag, in seconds, that a
29
+ # @return [ Integer ] max_staleness The maximum replication lag, in seconds, that a
30
30
  # secondary can suffer and still be eligible for a read.
31
31
  #
32
32
  # @since 2.4.0
@@ -68,7 +68,7 @@ module Mongo
68
68
  def initialize(options = {})
69
69
  @options = (options || {}).freeze
70
70
  @tag_sets = (options[:tag_sets] || []).freeze
71
- @max_staleness = options[:max_staleness] if options[:max_staleness] && options[:max_staleness] > 0
71
+ @max_staleness = options[:max_staleness] unless options[:max_staleness] == -1
72
72
  validate!
73
73
  end
74
74
 
@@ -146,19 +146,29 @@ module Mongo
146
146
  @local_threshold ||= (options[:local_threshold] || ServerSelector::LOCAL_THRESHOLD)
147
147
  end
148
148
 
149
- private
150
-
149
+ # Get the potential candidates to select from the cluster.
150
+ #
151
+ # @example Get the server candidates.
152
+ # selectable.candidates(cluster)
153
+ #
154
+ # @param [ Cluster ] cluster The cluster.
155
+ #
156
+ # @return [ Array<Server> ] The candidate servers.
157
+ #
158
+ # @since 2.4.0
151
159
  def candidates(cluster)
152
160
  if cluster.single?
153
161
  cluster.servers.each { |server| validate_max_staleness_support!(server) }
154
162
  elsif cluster.sharded?
155
163
  near_servers(cluster.servers).each { |server| validate_max_staleness_support!(server) }
156
164
  else
157
- validate_max_staleness_value!(cluster)
165
+ validate_max_staleness_value!(cluster) unless cluster.unknown?
158
166
  select(cluster.servers)
159
167
  end
160
168
  end
161
169
 
170
+ private
171
+
162
172
  # Select the primary from a list of provided candidates.
163
173
  #
164
174
  # @param [ Array ] candidates List of candidate servers to select the
@@ -200,7 +210,7 @@ module Mongo
200
210
  def near_servers(candidates = [])
201
211
  return candidates if candidates.empty?
202
212
  nearest_server = candidates.min_by(&:average_round_trip_time)
203
- threshold = nearest_server.average_round_trip_time + (local_threshold * 1000)
213
+ threshold = nearest_server.average_round_trip_time + local_threshold
204
214
  candidates.select { |server| server.average_round_trip_time <= threshold }.shuffle!
205
215
  end
206
216
 
@@ -230,14 +240,14 @@ module Mongo
230
240
  validate_max_staleness_support!(server)
231
241
  staleness = (server.last_scan - server.last_write_date) -
232
242
  (primary.last_scan - primary.last_write_date) +
233
- (server.heartbeat_frequency * 1000)
243
+ (server.heartbeat_frequency_seconds * 1000)
234
244
  staleness <= max_staleness_ms
235
245
  end
236
246
  else
237
247
  max_write_date = candidates.collect(&:last_write_date).max
238
248
  candidates.select do |server|
239
249
  validate_max_staleness_support!(server)
240
- staleness = max_write_date - server.last_write_date + (server.heartbeat_frequency * 1000)
250
+ staleness = max_write_date - server.last_write_date + (server.heartbeat_frequency_seconds * 1000)
241
251
  staleness <= max_staleness_ms
242
252
  end
243
253
  end
@@ -258,10 +268,12 @@ module Mongo
258
268
  end
259
269
 
260
270
  def validate_max_staleness_value!(cluster)
261
- return unless @max_staleness
262
- heartbeat_frequency = cluster.options[:heartbeat_frequency] || Server::Monitor::HEARTBEAT_FREQUENCY
263
- if @max_staleness < heartbeat_frequency * 2
264
- raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::INVALID_MAX_STALENESS)
271
+ if @max_staleness
272
+ heartbeat_frequency_seconds = cluster.options[:heartbeat_frequency] || Server::Monitor::HEARTBEAT_FREQUENCY
273
+ unless @max_staleness >= [ SMALLEST_MAX_STALENESS_SECONDS,
274
+ (heartbeat_frequency_seconds + Cluster::IDLE_WRITE_PERIOD_SECONDS) ].max
275
+ raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::INVALID_MAX_STALENESS)
276
+ end
265
277
  end
266
278
  end
267
279
  end
@@ -374,7 +374,7 @@ module Mongo
374
374
  # Read Options
375
375
  uri_option 'readpreference', :mode, :group => :read, :type => :read_mode
376
376
  uri_option 'readpreferencetags', :tag_sets, :group => :read, :type => :read_tags
377
- uri_option 'maxstalenessms', :max_staleness, :group => :read, :type => :ms_convert
377
+ uri_option 'maxstalenessseconds', :max_staleness, :group => :read
378
378
 
379
379
  # Pool options
380
380
  uri_option 'minpoolsize', :min_pool_size