Skip to content

Commit 4af852d

Browse files
committed
Fix potential deadlock in nested locking
1 parent 73d2aeb commit 4af852d

File tree

2 files changed

+45
-0
lines changed

2 files changed

+45
-0
lines changed

lib/pg_ha_migrations/safe_statements.rb

+6
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,12 @@ def safely_acquire_lock_for_table(*tables, mode: :access_exclusive, &block)
572572

573573
until successfully_acquired_lock
574574
loop do
575+
# If in a nested context and all of the above checks have passed,
576+
# we have already acquired the lock so this check is unnecessary.
577+
# In fact, it could actually cause a deadlock if a blocking query
578+
# was executed shortly after the initial lock acquisition.
579+
break if nested_target_tables
580+
575581
blocking_transactions = PgHaMigrations::BlockingDatabaseTransactions.find_blocking_transactions("#{PgHaMigrations::LOCK_TIMEOUT_SECONDS} seconds")
576582

577583
# Locking a partitioned table will also lock child tables (including sub-partitions),

spec/safe_statements_spec.rb

+39
Original file line numberDiff line numberDiff line change
@@ -4478,6 +4478,45 @@ def up
44784478
expect(locks_for_table(table_name, connection: alternate_connection)).to be_empty
44794479
end
44804480

4481+
it "skips blocking query check for nested lock acquisition" do
4482+
stub_const("PgHaMigrations::LOCK_TIMEOUT_SECONDS", 1)
4483+
4484+
query_thread = nil
4485+
4486+
expect(PgHaMigrations::BlockingDatabaseTransactions).to receive(:find_blocking_transactions)
4487+
.once
4488+
.and_call_original
4489+
4490+
begin
4491+
migration.safely_acquire_lock_for_table(table_name) do
4492+
query_thread = Thread.new { alternate_connection.execute("SELECT * FROM bogus_table") }
4493+
4494+
sleep 2
4495+
4496+
migration.safely_acquire_lock_for_table(table_name) do
4497+
expect(locks_for_table(table_name, connection: alternate_connection_2)).to contain_exactly(
4498+
having_attributes(
4499+
table: "bogus_table",
4500+
lock_type: "AccessExclusiveLock",
4501+
granted: true,
4502+
pid: kind_of(Integer),
4503+
),
4504+
having_attributes(
4505+
table: "bogus_table",
4506+
lock_type: "AccessShareLock",
4507+
granted: false,
4508+
pid: kind_of(Integer),
4509+
),
4510+
)
4511+
end
4512+
end
4513+
ensure
4514+
query_thread.join if query_thread
4515+
end
4516+
4517+
expect(locks_for_table(table_name, connection: alternate_connection)).to be_empty
4518+
end
4519+
44814520
it "does not allow re-entrancy when lock escalation detected" do
44824521
expect do
44834522
migration.safely_acquire_lock_for_table(table_name, mode: :share) do

0 commit comments

Comments
 (0)