Skip to content

Commit da29ab7

Browse files
committed
RUBY-775 Fix recursive locking with both immutable and mutable pool manager state
1 parent 39ff8c3 commit da29ab7

File tree

3 files changed

+70
-88
lines changed

3 files changed

+70
-88
lines changed

lib/mongo/connection/pool_manager.rb

Lines changed: 52 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class PoolManager
1717
include ThreadLocalVariableManager
1818

1919
attr_reader :client,
20+
:hosts,
21+
:pools,
22+
:secondaries,
23+
:secondary_pools,
24+
:arbiters,
2025
:primary,
2126
:primary_pool,
2227
:seeds,
@@ -36,12 +41,12 @@ def initialize(client, seeds=[])
3641
@client = client
3742
@seeds = seeds
3843

44+
initialize_immutable_state
45+
initialize_mutable_state
46+
3947
@pools = Set.new
4048
@primary = nil
4149
@primary_pool = nil
42-
@secondaries = Set.new
43-
@secondary_pools = []
44-
@hosts = Set.new
4550
@members = Set.new
4651
@refresh_required = false
4752
@max_bson_size = DEFAULT_MAX_BSON_SIZE
@@ -70,6 +75,7 @@ def connect
7075
thread_local[:locks][:connecting_manager] = false
7176
end
7277
end
78+
clone_state
7379
end
7480

7581
def refresh!(additional_seeds)
@@ -140,47 +146,6 @@ def read
140146
read_pool.host_port
141147
end
142148

143-
def hosts
144-
@connect_mutex.synchronize do
145-
@hosts.nil? ? nil : @hosts.clone
146-
end
147-
end
148-
149-
def pools
150-
@connect_mutex.synchronize do
151-
@pools.nil? ? nil : @pools.clone
152-
end
153-
end
154-
155-
def secondaries
156-
@connect_mutex.synchronize do
157-
@secondaries.nil? ? nil : @secondaries.clone
158-
end
159-
end
160-
161-
def secondary_pools
162-
@connect_mutex.synchronize do
163-
@secondary_pools.nil? ? nil : @secondary_pools.clone
164-
end
165-
end
166-
167-
def arbiters
168-
@connect_mutex.synchronize do
169-
@arbiters.nil? ? nil : @arbiters.clone
170-
end
171-
end
172-
173-
def state_snapshot
174-
@connect_mutex.synchronize do
175-
{ :pools => @pools.nil? ? nil : @pools.clone,
176-
:secondaries => @secondaries.nil? ? nil : @secondaries.clone,
177-
:secondary_pools => @secondary_pools.nil? ? nil : @secondary_pools.clone,
178-
:hosts => @hosts.nil? ? nil : @hosts.clone,
179-
:arbiters => @arbiters.nil? ? nil : @arbiters.clone
180-
}
181-
end
182-
end
183-
184149
private
185150

186151
def update_max_sizes
@@ -203,7 +168,7 @@ def validate_existing_member(current_config, member)
203168

204169
# For any existing members, close and remove any that are unhealthy or already closed.
205170
def disconnect_old_members
206-
@pools.reject! {|pool| !pool.healthy? }
171+
@pools_mutable.reject! {|pool| !pool.healthy? }
207172
@members.reject! {|node| !node.healthy? }
208173
end
209174

@@ -243,13 +208,13 @@ def connect_to_members
243208
def initialize_pools(members)
244209
@primary_pool = nil
245210
@primary = nil
246-
@secondaries.clear
247-
@secondary_pools.clear
248-
@hosts.clear
211+
@secondaries_mutable.clear
212+
@secondary_pools_mutable.clear
213+
@hosts_mutable.clear
249214

250215
members.each do |member|
251216
member.last_state = nil
252-
@hosts << member.host_string
217+
@hosts_mutable << member.host_string
253218
if member.primary?
254219
assign_primary(member)
255220
elsif member.secondary?
@@ -258,37 +223,37 @@ def initialize_pools(members)
258223
end
259224
end
260225

261-
@arbiters = members.first.arbiters
226+
@arbiters_mutable = members.first.arbiters
262227
end
263228

264229
def assign_primary(member)
265230
member.last_state = :primary
266231
@primary = member.host_port
267-
if existing = @pools.detect {|pool| pool.node == member }
232+
if existing = @pools_mutable.detect {|pool| pool.node == member }
268233
@primary_pool = existing
269234
else
270235
@primary_pool = Pool.new(self.client, member.host, member.port,
271236
:size => self.client.pool_size,
272237
:timeout => self.client.pool_timeout,
273238
:node => member
274239
)
275-
@pools << @primary_pool
240+
@pools_mutable << @primary_pool
276241
end
277242
end
278243

279244
def assign_secondary(member)
280245
member.last_state = :secondary
281-
@secondaries << member.host_port
282-
if existing = @pools.detect {|pool| pool.node == member }
283-
@secondary_pools << existing
246+
@secondaries_mutable << member.host_port
247+
if existing = @pools_mutable.detect {|pool| pool.node == member }
248+
@secondary_pools_mutable << existing
284249
else
285250
pool = Pool.new(self.client, member.host, member.port,
286251
:size => self.client.pool_size,
287252
:timeout => self.client.pool_timeout,
288253
:node => member
289254
)
290-
@secondary_pools << pool
291-
@pools << pool
255+
@secondary_pools_mutable << pool
256+
@pools_mutable << pool
292257
end
293258
end
294259

@@ -321,5 +286,35 @@ def copy_members
321286
end
322287
members
323288
end
289+
290+
def initialize_immutable_state
291+
@hosts = Set.new.freeze
292+
@pools = Set.new.freeze
293+
@secondaries = Set.new.freeze
294+
@secondary_pools = [].freeze
295+
@arbiters = [].freeze
296+
end
297+
298+
def initialize_mutable_state
299+
@hosts_mutable = Set.new
300+
@pools_mutable = Set.new
301+
@secondaries_mutable = Set.new
302+
@secondary_pools_mutable = []
303+
@arbiters_mutable = []
304+
end
305+
306+
def clone_state
307+
@hosts = @hosts_mutable.clone
308+
@pools = @pools_mutable.clone
309+
@secondaries = @secondaries_mutable.clone
310+
@secondary_pools = @secondary_pools_mutable.clone
311+
@arbiters = @arbiters_mutable.clone
312+
313+
@hosts.freeze
314+
@pools.freeze
315+
@secondaries.freeze
316+
@secondary_pools.freeze
317+
@arbiters.freeze
318+
end
324319
end
325320
end

test/replica_set/refresh_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ def test_concurrent_refreshes
123123
end
124124
end
125125

126+
127+
def test_manager_recursive_locking
128+
# See RUBY-775
129+
# This tests that there isn't recursive locking when a pool manager reconnects
130+
# to all replica set members. The bug in RUBY-775 occurred because the same lock
131+
# acquired in order to connect the pool manager was used to read the pool manager's
132+
# state.
133+
client = MongoReplicaSetClient.new(@rs.repl_set_seeds)
134+
135+
cursor = client[TEST_DB]['rs-refresh-test'].find
136+
client.stubs(:receive_message).raises(ConnectionFailure)
137+
client.manager.stubs(:refresh_required?).returns(true)
138+
client.manager.stubs(:check_connection_health).returns(true)
139+
assert_raise ConnectionFailure do
140+
cursor.next
141+
end
142+
end
143+
126144
=begin
127145
def test_automated_refresh_with_removed_node
128146
client = MongoReplicaSetClient.new(@rs.repl_set_seeds,

test/unit/pool_manager_test.rb

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -106,37 +106,6 @@ class PoolManagerUnitTest < Test::Unit::TestCase
106106
assert_equal [['localhost', 27020]], manager.arbiters
107107
end
108108

109-
should "return clones of pool lists" do
110-
111-
@db.stubs(:command).returns(
112-
# First call to get a socket.
113-
@ismaster.merge({'ismaster' => true}),
114-
115-
# Subsequent calls to configure pools.
116-
@ismaster.merge({'ismaster' => true}),
117-
@ismaster.merge({'secondary' => true, 'maxBsonObjectSize' => 500}),
118-
@ismaster.merge({'secondary' => true, 'maxMessageSizeBytes' => 700}),
119-
@ismaster.merge({'arbiterOnly' => true})
120-
)
121-
122-
seeds = [['localhost', 27017], ['localhost', 27018]]
123-
manager = Mongo::PoolManager.new(@client, seeds)
124-
@client.stubs(:local_manager).returns(manager)
125-
manager.connect
126-
127-
assert_not_equal manager.instance_variable_get(:@arbiters).object_id, manager.arbiters.object_id
128-
assert_not_equal manager.instance_variable_get(:@secondaries).object_id, manager.secondaries.object_id
129-
assert_not_equal manager.instance_variable_get(:@secondary_pools).object_id, manager.secondary_pools.object_id
130-
assert_not_equal manager.instance_variable_get(:@hosts).object_id, manager.hosts.object_id
131-
assert_not_equal manager.instance_variable_get(:@pools).object_id, manager.pools.object_id
132-
133-
assert_not_equal manager.instance_variable_get(:@arbiters).object_id, manager.state_snapshot[:arbiters].object_id
134-
assert_not_equal manager.instance_variable_get(:@secondaries).object_id, manager.state_snapshot[:secondaries].object_id
135-
assert_not_equal manager.instance_variable_get(:@secondary_pools).object_id, manager.state_snapshot[:secondary_pools].object_id
136-
assert_not_equal manager.instance_variable_get(:@hosts).object_id, manager.state_snapshot[:hosts].object_id
137-
assert_not_equal manager.instance_variable_get(:@pools).object_id, manager.state_snapshot[:pools].object_id
138-
end
139-
140109
end
141110

142111
end

0 commit comments

Comments
 (0)