Skip to content

Commit 721ed56

Browse files
committed
Merge pull request #637 from estolfo/RUBY-932-duplicate-servers
RUBY-932 Update Server Discovery and Monitoring code and tests
2 parents 13da54a + 4f8b224 commit 721ed56

40 files changed

+1414
-392
lines changed

lib/mongo/cluster.rb

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ class Cluster
2525
include Event::Subscriber
2626
include Loggable
2727

28-
# @return [ Array<String> ] The provided seed addresses.
29-
attr_reader :addresses
30-
3128
# @return [ Hash ] The options hash.
3229
attr_reader :options
3330

@@ -69,9 +66,9 @@ def add(host)
6966
if !addresses.include?(address)
7067
if addition_allowed?(address)
7168
log_debug([ "Adding #{address.to_s} to the cluster." ])
72-
addresses.push(address)
69+
@update_lock.synchronize { @addresses.push(address) }
7370
server = Server.new(address, self, event_listeners, options)
74-
@servers.push(server)
71+
@update_lock.synchronize { @servers.push(server) }
7572
server
7673
end
7774
end
@@ -92,9 +89,9 @@ def initialize(seeds, options = {})
9289
@event_listeners = Event::Listeners.new
9390
@options = options.freeze
9491
@topology = Topology.initial(seeds, options)
92+
@update_lock = Mutex.new
9593

96-
subscribe_to(Event::SERVER_ADDED, Event::ServerAdded.new(self))
97-
subscribe_to(Event::SERVER_REMOVED, Event::ServerRemoved.new(self))
94+
subscribe_to(Event::DESCRIPTION_CHANGED, Event::DescriptionChanged.new(self))
9895
subscribe_to(Event::PRIMARY_ELECTED, Event::PrimaryElected.new(self))
9996

10097
seeds.each{ |seed| add(seed) }
@@ -136,10 +133,10 @@ def next_primary
136133
#
137134
# @since 2.0.0
138135
def elect_primary!(description)
139-
@topology = topology.elect_primary(description, @servers)
136+
@topology = topology.elect_primary(description, servers_list)
140137
end
141138

142-
# Removed the server from the cluster for the provided address, if it
139+
# Remove the server from the cluster for the provided address, if it
143140
# exists.
144141
#
145142
# @example Remove the server from the cluster.
@@ -151,9 +148,10 @@ def elect_primary!(description)
151148
def remove(host)
152149
log_debug([ "#{host} being removed from the cluster." ])
153150
address = Address.new(host)
154-
removed_servers = @servers.reject!{ |server| server.address == address }
151+
removed_servers = @servers.select { |s| s.address == address }
152+
@update_lock.synchronize { @servers = @servers - removed_servers }
155153
removed_servers.each{ |server| server.disconnect! } if removed_servers
156-
addresses.reject!{ |addr| addr == address }
154+
@update_lock.synchronize { @addresses.reject! { |addr| addr == address } }
157155
end
158156

159157
# Force a scan of all known servers in the cluster.
@@ -168,7 +166,7 @@ def remove(host)
168166
#
169167
# @since 2.0.0
170168
def scan!
171-
@servers.each{ |server| server.scan! } and true
169+
servers_list.each{ |server| server.scan! } and true
172170
end
173171

174172
# Get a list of server candidates from the cluster that can have operations
@@ -181,7 +179,37 @@ def scan!
181179
#
182180
# @since 2.0.0
183181
def servers
184-
topology.servers(@servers.compact).compact
182+
topology.servers(servers_list.compact).compact
183+
end
184+
185+
# Add hosts in a description to the cluster.
186+
#
187+
# @example Add hosts in a description to the cluster.
188+
# cluster.add_hosts(description)
189+
#
190+
# @param [ Mongo::Server::Description ] description The description.
191+
#
192+
# @since 2.0.6
193+
def add_hosts(description)
194+
if topology.add_hosts?(description, servers_list)
195+
description.servers.each { |s| add(s) }
196+
end
197+
end
198+
199+
# Remove hosts in a description from the cluster.
200+
#
201+
# @example Remove hosts in a description from the cluster.
202+
# cluster.remove_hosts(description)
203+
#
204+
# @param [ Mongo::Server::Description ] description The description.
205+
#
206+
# @since 2.0.6
207+
def remove_hosts(description)
208+
if topology.remove_hosts?(description)
209+
servers_list.each do |s|
210+
remove(s.address.to_s) if topology.remove_server?(description, s)
211+
end
212+
end
185213
end
186214

187215
# Create a cluster for the provided client, for use when we don't want the
@@ -202,6 +230,18 @@ def self.create(client)
202230
client.instance_variable_set(:@cluster, cluster)
203231
end
204232

233+
# The addresses in the cluster.
234+
#
235+
# @example Get the addresses in the cluster.
236+
# cluster.addresses
237+
#
238+
# @return [ Array<Mongo::Address> ] The addresses.
239+
#
240+
# @since 2.0.6
241+
def addresses
242+
addresses_list
243+
end
244+
205245
private
206246

207247
def direct_connection?(address)
@@ -211,5 +251,21 @@ def direct_connection?(address)
211251
def addition_allowed?(address)
212252
!@topology.single? || direct_connection?(address)
213253
end
254+
255+
def servers_list
256+
@update_lock.synchronize do
257+
@servers.reduce([]) do |servers, server|
258+
servers << server
259+
end
260+
end
261+
end
262+
263+
def addresses_list
264+
@update_lock.synchronize do
265+
@addresses.reduce([]) do |addresses, address|
266+
addresses << address
267+
end
268+
end
269+
end
214270
end
215271
end

lib/mongo/cluster/topology/replica_set.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,54 @@ def servers(servers)
125125
end
126126
end
127127

128+
# Whether a server description's hosts may be added to the cluster.
129+
#
130+
# @example Check if a description's hosts may be added to the cluster.
131+
# topology.add_hosts?(description, servers)
132+
#
133+
# @param [ Mongo::Server::Description ] description The description.
134+
# @param [ Array<Mongo::Server> ] servers The cluster servers.
135+
#
136+
# @return [ true, false ] Whether a description's hosts may be added.
137+
#
138+
# @since 2.0.6
139+
def add_hosts?(description, servers)
140+
!!(member_of_this_set?(description) && !has_primary?(servers))
141+
end
142+
143+
# Whether a description can be used to remove hosts from the cluster.
144+
#
145+
# @example Check if a description can be used to remove hosts from the cluster.
146+
# topology.remove_hosts?(description)
147+
#
148+
# @param [ Mongo::Server::Description ] description The description.
149+
#
150+
# @return [ true, false ] Whether hosts may be removed from the cluster.
151+
#
152+
# @since 2.0.6
153+
def remove_hosts?(description)
154+
!description.config.empty? &&
155+
(description.primary? ||
156+
description.hosts.empty? ||
157+
!member_of_this_set?(description))
158+
end
159+
160+
# Whether a specific server in the cluster can be removed, given a description.
161+
#
162+
# @example Check if a specific server can be removed from the cluster.
163+
# topology.remove_server?(description, server)
164+
#
165+
# @param [ Mongo::Server::Description ] description The description.
166+
# @param [ Mongo::Serve ] server The server in question.
167+
#
168+
# @return [ true, false ] Whether the server can be removed from the cluster.
169+
#
170+
# @since 2.0.6
171+
def remove_server?(description, server)
172+
remove_self?(description, server) ||
173+
(member_of_this_set?(description) && !description.lists_server?(server))
174+
end
175+
128176
# A replica set topology is not sharded.
129177
#
130178
# @example Is the topology sharded?
@@ -154,6 +202,23 @@ def single?; false; end
154202
#
155203
# @since 2.0.0
156204
def unknown?; false; end
205+
206+
private
207+
208+
def has_primary?(servers)
209+
servers.find { |s| s.primary? }
210+
end
211+
212+
def member_of_this_set?(description)
213+
description.replica_set_member? &&
214+
description.replica_set_name == replica_set_name
215+
end
216+
217+
def remove_self?(description, server)
218+
!member_of_this_set?(description) &&
219+
description.is_server?(server) &&
220+
!description.ghost?
221+
end
157222
end
158223
end
159224
end

lib/mongo/cluster/topology/sharded.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,50 @@ def servers(servers)
9797
servers.select{ |server| server.mongos? }
9898
end
9999

100+
# Whether a server description's hosts may be added to the cluster.
101+
#
102+
# @example Check if a description's hosts may be added to the cluster.
103+
# topology.add_hosts?(description, servers)
104+
#
105+
# @param [ Mongo::Server::Description ] description The description.
106+
# @param [ Array<Mongo::Server> ] servers The cluster servers.
107+
#
108+
# @return [ false ] A description's hosts are never added to a
109+
# sharded cluster.
110+
#
111+
# @since 2.0.6
112+
def add_hosts?(description, servers); false; end
113+
114+
# Whether a description can be used to remove hosts from the cluster.
115+
#
116+
# @example Check if a description can be used to remove hosts from
117+
# the cluster.
118+
# topology.remove_hosts?(description)
119+
#
120+
# @param [ Mongo::Server::Description ] description The description.
121+
#
122+
# @return [ true ] A description can always be used to remove hosts
123+
# from a sharded cluster.
124+
#
125+
# @since 2.0.6
126+
def remove_hosts?(description); true; end
127+
128+
# Whether a specific server in the cluster can be removed, given a description.
129+
#
130+
# @example Check if a specific server can be removed from the cluster.
131+
# topology.remove_server?(description, server)
132+
#
133+
# @param [ Mongo::Server::Description ] description The description.
134+
# @param [ Mongo::Serve ] server The server in question.
135+
#
136+
# @return [ true, false ] Whether the server can be removed from the cluster.
137+
#
138+
# @since 2.0.6
139+
def remove_server?(description, server)
140+
remove_self?(description, server) ||
141+
!(server.mongos? || server.unknown?)
142+
end
143+
100144
# A sharded topology is sharded.
101145
#
102146
# @example Is the topology sharded?
@@ -126,6 +170,12 @@ def single?; false; end
126170
#
127171
# @since 2.0.0
128172
def unknown?; false; end
173+
174+
private
175+
176+
def remove_self?(description, server)
177+
description.is_server?(server) && !description.mongos?
178+
end
129179
end
130180
end
131181
end

lib/mongo/cluster/topology/single.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,47 @@ def servers(servers, name = nil)
101101
[ servers.detect { |server| !server.unknown? } ]
102102
end
103103

104+
# Whether a server description's hosts may be added to the cluster.
105+
#
106+
# @example Check if a description's hosts may be added to the cluster.
107+
# topology.add_hosts?(description, servers)
108+
#
109+
# @param [ Mongo::Server::Description ] description The description.
110+
# @param [ Array<Mongo::Server> ] servers The cluster servers.
111+
#
112+
# @return [ false ] A description's hosts are never added to a
113+
# cluster of Single topology.
114+
#
115+
# @since 2.0.6
116+
def add_hosts?(description, servers); false; end
117+
118+
# Whether a description can be used to remove hosts from the cluster.
119+
#
120+
# @example Check if a description can be used to remove hosts from
121+
# the cluster.
122+
# topology.remove_hosts?(description)
123+
#
124+
# @param [ Mongo::Server::Description ] description The description.
125+
#
126+
# @return [ true ] A description can never be used to remove hosts
127+
# from a cluster of Single topology.
128+
#
129+
# @since 2.0.6
130+
def remove_hosts?(description); false; end
131+
132+
# Whether a specific server in the cluster can be removed, given a description.
133+
#
134+
# @example Check if a specific server can be removed from the cluster.
135+
# topology.remove_server?(description, server)
136+
#
137+
# @param [ Mongo::Server::Description ] description The description.
138+
# @param [ Mongo::Serve ] server The server in question.
139+
#
140+
# @return [ false ] A server is never removed from a cluster of Single topology.
141+
#
142+
# @since 2.0.6
143+
def remove_server?(description, server); false; end
144+
104145
# A single topology is not sharded.
105146
#
106147
# @example Is the topology sharded?

lib/mongo/cluster/topology/unknown.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,50 @@ def single?; false; end
139139
# @since 2.0.0
140140
def unknown?; true; end
141141

142+
# Whether a server description's hosts may be added to the cluster.
143+
#
144+
# @example Check if a description's hosts may be added to the cluster.
145+
# topology.add_hosts?(description, servers)
146+
#
147+
# @param [ Mongo::Server::Description ] description The description.
148+
# @param [ Array<Mongo::Server> ] servers The cluster servers.
149+
#
150+
# @return [ true, false ] Whether a description's hosts may be added.
151+
#
152+
# @since 2.0.6
153+
def add_hosts?(description, servers)
154+
!(description.unknown? || description.ghost?)
155+
end
156+
157+
# Whether a description can be used to remove hosts from the cluster.
158+
#
159+
# @example Check if a description can be used to remove hosts from the cluster.
160+
# topology.remove_hosts?(description)
161+
#
162+
# @param [ Mongo::Server::Description ] description The description.
163+
#
164+
# @return [ true, false ] Whether hosts may be removed from the cluster.
165+
#
166+
# @since 2.0.6
167+
def remove_hosts?(description)
168+
description.standalone?
169+
end
170+
171+
# Whether a specific server in the cluster can be removed, given a description.
172+
#
173+
# @example Check if a specific server can be removed from the cluster.
174+
# topology.remove_server?(description, server)
175+
#
176+
# @param [ Mongo::Server::Description ] description The description.
177+
# @param [ Mongo::Serve ] server The server in question.
178+
#
179+
# @return [ true, false ] Whether the server can be removed from the cluster.
180+
#
181+
# @since 2.0.6
182+
def remove_server?(description, server)
183+
description.standalone? && description.is_server?(server)
184+
end
185+
142186
private
143187

144188
def initialize_replica_set(description, servers)

0 commit comments

Comments
 (0)