Skip to content

Commit

Permalink
chore: refractor postgresql class (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
seuros authored Feb 12, 2024
1 parent a078c31 commit 5b1ed5a
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 19 deletions.
1 change: 1 addition & 0 deletions lib/with_advisory_lock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
loader.setup

module WithAdvisoryLock
LOCK_PREFIX_ENV = 'WITH_ADVISORY_LOCK_PREFIX'.freeze
end

ActiveSupport.on_load :active_record do
Expand Down
6 changes: 2 additions & 4 deletions lib/with_advisory_lock/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def initialize(connection, lock_name, options)
end

def lock_str
@lock_str ||= "#{ENV['WITH_ADVISORY_LOCK_PREFIX']}#{lock_name}"
@lock_str ||= "#{ENV[LOCK_PREFIX_ENV]}#{lock_name}"
end

def lock_stack_item
Expand All @@ -56,9 +56,7 @@ def already_locked?
def with_advisory_lock_if_needed(&block)
if disable_query_cache
return lock_and_yield do
connection.uncached do
yield
end
connection.uncached(&block)
end
end

Expand Down
53 changes: 38 additions & 15 deletions lib/with_advisory_lock/postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,65 @@

module WithAdvisoryLock
class PostgreSQL < Base
# See http://www.postgresql.org/docs/9.1/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
# See https://www.postgresql.org/docs/16/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS

# MRI returns 't', jruby returns true. YAY!
LOCK_RESULT_VALUES = ['t', true].freeze
PG_ADVISORY_UNLOCK = 'pg_advisory_unlock'
PG_TRY_ADVISORY = 'pg_try_advisory'
ERROR_MESSAGE_REGEX = / ERROR: +current transaction is aborted,/

def try_lock
pg_function = "pg_try_advisory#{transaction ? '_xact' : ''}_lock#{shared ? '_shared' : ''}"
execute_successful?(pg_function)
execute_successful?(advisory_try_lock_function(transaction))
end

def release_lock
return if transaction

pg_function = "pg_advisory_unlock#{shared ? '_shared' : ''}"
execute_successful?(pg_function)
execute_successful?(advisory_unlock_function)
rescue ActiveRecord::StatementInvalid => e
raise unless e.message =~ / ERROR: +current transaction is aborted,/
raise unless e.message =~ ERROR_MESSAGE_REGEX

begin
connection.rollback_db_transaction
execute_successful?(pg_function)
execute_successful?(advisory_unlock_function)
ensure
connection.begin_db_transaction
end
end

def advisory_try_lock_function(transaction_scope)
[
'pg_try_advisory',
transaction_scope ? '_xact' : nil,
'_lock',
shared ? '_shared' : nil
].compact.join
end

def advisory_unlock_function
[
'pg_advisory_unlock',
shared ? '_shared' : nil
].compact.join
end

def execute_successful?(pg_function)
result = connection.select_value(prepare_sql(pg_function))
LOCK_RESULT_VALUES.include?(result)
end

def prepare_sql(pg_function)
comment = lock_name.to_s.gsub(%r{(/\*)|(\*/)}, '--')
sql = "SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
result = connection.select_value(sql)
# MRI returns 't', jruby returns true. YAY!
['t', true].include?(result)
"SELECT #{pg_function}(#{lock_keys.join(',')}) AS #{unique_column_name} /* #{comment} */"
end

# PostgreSQL wants 2 32bit integers as the lock key.
def lock_keys
@lock_keys ||= [stable_hashcode(lock_name), ENV['WITH_ADVISORY_LOCK_PREFIX']].map do |ea|
# pg advisory args must be 31 bit ints
ea.to_i & 0x7fffffff
end
@lock_keys ||= [
stable_hashcode(lock_name),
ENV[LOCK_PREFIX_ENV]
].map { |ea| ea.to_i & 0x7fffffff }
end
end
end

0 comments on commit 5b1ed5a

Please sign in to comment.