このコミットが含まれているのは:
2026-10-18 18:28:05 +09:00
コミット 569b9234c6
25個のファイルの変更、483行の追加、144行の削除
+41 -8
ファイルの表示
@@ -13,7 +13,19 @@ class SharedResourceIdAllocator
['tag_description_versions', 'tag_id']],
'wiki' => [['tag_group_versions', 'tag_group_id']] }.freeze
LOCK_TIMEOUT = 10
TRANSACTION_LOCKS = ObjectSpace::WeakMap.new
CONNECTION_LOCKS = ObjectSpace::WeakMap.new
class LockCleanup
def initialize &finish
@finish = finish
end
def trigger_transactional_callbacks? = false
def before_committed! = nil
def committed!(**) = @finish.call(self)
def rolledback!(**) = @finish.call(self)
end
private_constant :LockCleanup
def self.assign! record
space, tables = SPACES.find { |_name, names| names.include?(record.class.table_name) }
@@ -42,14 +54,14 @@ class SharedResourceIdAllocator
raise ArgumentError, 'Unknown shared ID resource' unless SPACES.key?(space)
connection = record_class.connection
transaction = record_class.current_transaction
transaction = connection.current_transaction
unless transaction.open?
raise ActiveRecord::ActiveRecordError, 'Shared ID requires a transaction'
end
database = connection.pool.db_config.database
name = "btrc:ids:#{ Digest::SHA256.hexdigest(database)[0, 16] }:#{ space }"
held_locks = TRANSACTION_LOCKS[transaction] ||= {}
held_locks = CONNECTION_LOCKS[connection] ||= {}
return if held_locks.key?(name)
quoted_name = connection.quote(name)
@@ -61,20 +73,41 @@ class SharedResourceIdAllocator
end
held_locks[name] = true
release = lambda do
held_locks.delete(name)
finish = lambda do |cleanup|
next unless held_locks.key?(name)
# Savepoint callbacks may run before the outer transaction ends.
# Keep one session lock and transfer its cleanup to the remaining parent.
parent = connection.current_transaction
if parent.open?
connection.add_transaction_record(cleanup)
next
end
result = record_class.uncached do
connection.select_value("SELECT RELEASE_LOCK(#{ quoted_name })")
end
unless result.to_i == 1
raise ActiveRecord::ActiveRecordError, "Shared ID lock release failed: #{ space }"
end
held_locks.delete(name)
rescue StandardError
# Never return a session with an untracked advisory lock to the pool.
begin
connection.disconnect!
ensure
held_locks.clear
end
raise
end
begin
transaction.after_commit(&release)
transaction.after_rollback(&release)
# Rails finalises remaining records in ensure if another callback raises.
connection.add_transaction_record(LockCleanup.new(&finish))
rescue StandardError
release.call
begin
connection.disconnect!
ensure
held_locks.clear
end
raise
end
end