Skip to content

Commit e29e569

Browse files
committed
support rails 8.2
1 parent 28b8c98 commit e29e569

1 file changed

Lines changed: 25 additions & 5 deletions

File tree

lib/with_advisory_lock/postgresql_advisory.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,20 @@ def try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seco
1919
advisory_try_lock_function(transaction, shared)
2020
end
2121
execute_advisory(function, lock_keys, lock_name, blocking: blocking)
22+
rescue ActiveRecord::Deadlocked
23+
# Rails 8.2+ raises ActiveRecord::Deadlocked directly for PostgreSQL deadlocks
24+
# When using blocking locks, treat deadlocks as lock acquisition failure
25+
return false if blocking
26+
27+
raise
2228
rescue ActiveRecord::StatementInvalid => e
2329
# PostgreSQL deadlock detection raises PG::TRDeadlockDetected (SQLSTATE 40P01)
24-
# When using blocking locks, treat deadlocks as lock acquisition failure
25-
if blocking && (e.cause.is_a?(PG::TRDeadlockDetected) || e.message.include?('deadlock detected'))
30+
# When using blocking locks, treat deadlocks as lock acquisition failure.
31+
# Rails 8.2+ may also retry after deadlock and get "current transaction is aborted"
32+
# when the transaction was rolled back by PostgreSQL's deadlock detection.
33+
if blocking && (e.cause.is_a?(PG::TRDeadlockDetected) ||
34+
e.message.include?('deadlock detected') ||
35+
e.message =~ ERROR_MESSAGE_REGEX)
2636
false
2737
else
2838
raise
@@ -117,13 +127,23 @@ def advisory_unlock_function(shared)
117127
end
118128

119129
def execute_advisory(function, lock_keys, lock_name, blocking: false)
130+
sql = prepare_sql(function, lock_keys, lock_name)
120131
if blocking
121-
# Blocking locks return void - if the query executes successfully, the lock was acquired
122-
query_value(prepare_sql(function, lock_keys, lock_name))
132+
# Blocking locks return void - if the query executes successfully, the lock was acquired.
133+
# Rails 8.2+ uses lazy transaction materialization. We must use materialize_transactions: true
134+
# to ensure the transaction is started on the database before acquiring the lock,
135+
# otherwise the lock won't actually block other connections.
136+
if respond_to?(:internal_exec_query, true)
137+
# Rails < 8.2
138+
query_value(sql)
139+
else
140+
# Rails 8.2+ - use query_all with materialize_transactions: true
141+
send(:query_all, sql, 'AdvisoryLock', materialize_transactions: true)
142+
end
123143
true
124144
else
125145
# Non-blocking try locks return boolean
126-
result = query_value(prepare_sql(function, lock_keys, lock_name))
146+
result = query_value(sql)
127147
LOCK_RESULT_VALUES.include?(result)
128148
end
129149
end

0 commit comments

Comments
 (0)