このコミットが含まれているのは:
@@ -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
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする