@@ -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