57 行
1.8 KiB
Ruby
57 行
1.8 KiB
Ruby
require 'digest'
|
|
|
|
|
|
class SharedResourceIdAllocator
|
|
SPACES = {
|
|
'tags' => ['tags', 'external_tags'],
|
|
'wiki' => ['tag_descriptions', 'tag_groups'] }.freeze
|
|
LOCK_TIMEOUT = 10
|
|
|
|
def self.assign! record
|
|
space, tables = SPACES.find { |_name, names| names.include?(record.class.table_name) }
|
|
raise ArgumentError, 'Unknown shared ID resource' unless tables
|
|
|
|
connection = record.class.connection
|
|
transaction = record.class.current_transaction
|
|
raise ActiveRecord::ActiveRecordError, 'Shared ID requires a transaction' unless transaction.open?
|
|
|
|
database = connection.pool.db_config.database
|
|
name = "btrc:ids:#{ Digest::SHA256.hexdigest(database)[0, 16] }:#{ space }"
|
|
quoted_name = connection.quote(name)
|
|
acquired = record.class.uncached do
|
|
connection.select_value("SELECT GET_LOCK(#{ quoted_name }, #{ LOCK_TIMEOUT })")
|
|
end
|
|
unless acquired.to_i == 1
|
|
raise ActiveRecord::LockWaitTimeout, "Shared ID lock unavailable: #{ space }"
|
|
end
|
|
|
|
release = lambda do
|
|
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
|
|
end
|
|
begin
|
|
transaction.after_commit(&release)
|
|
transaction.after_rollback(&release)
|
|
rescue StandardError
|
|
release.call
|
|
raise
|
|
end
|
|
|
|
return if record.id.present?
|
|
|
|
# Locking reads see committed maxima even after earlier repeatable reads.
|
|
maxima = record.class.uncached do
|
|
tables.map do |table|
|
|
quoted_table = connection.quote_table_name(table)
|
|
connection.select_value(
|
|
"SELECT id FROM #{ quoted_table } ORDER BY id DESC LIMIT 1 FOR UPDATE").to_i
|
|
end
|
|
end
|
|
record.id = maxima.max + 1
|
|
end
|
|
end
|