module Sequel::AdvisoryLocking

Constants

HEX_STRING_SLICE_RANGE
POSTGRES_SIGNED_BIGINT_BOUND
POSTGRES_SIGNED_BIGINT_MAXIMUM
POSTGRES_SIGNED_BIGINT_MINIMUM
POSTGRES_SIGNED_BIGINT_RANGE
POSTGRES_UNSIGNED_BIGINT_BOUND
VERSION

Public Instance Methods

advisory_lock(key, try: false, shared: false) { || ... } click to toggle source
# File lib/sequel/extensions/advisory_locking.rb, line 19
def advisory_lock(key, try: false, shared: false, &block)
  int = advisory_lock_key(key)

  synchronize do
    begin
      # Add key to the end so that logs read easier.
      sql = "SELECT pg#{'_try' if try}_advisory_lock#{'_shared' if shared}(?) -- ?"
      locked = !!self[sql, int, key].get

      if locked && block
        if in_transaction?
          # If we're in a transaction and an error occurs at the DB level,
          # the advisory lock won't be released for us and we won't be
          # able to run the unlock function below. So, wrap the block in a
          # savepoint that will hopefully be transparent to the caller.
          transaction(savepoint: true, rollback: :reraise, &block)
        else
          # If we're not in a transaction, of course, we don't have that
          # worry, and we don't want to force the caller to enter a
          # transaction that they maybe don't want to incur the overhead
          # of, so just yield.
          yield
        end
      else
        locked
      end
    ensure
      sql = "SELECT pg_advisory_unlock#{'_shared' if shared}(?) -- ?"
      self[sql, int, key].get if locked
    end
  end
end
advisory_lock_key(key) click to toggle source
# File lib/sequel/extensions/advisory_locking.rb, line 52
def advisory_lock_key(key)
  case key
  when Integer
    advisory_lock_key_range_check(key)
  when String, Symbol
    # For an arbitrary string, pseudorandomly return an integer in
    # the PG bigint range.
    hex = Digest::MD5.hexdigest(key.to_s)[HEX_STRING_SLICE_RANGE].hex

    # Mimic PG's bigint rollover behavior.
    hex -= POSTGRES_UNSIGNED_BIGINT_BOUND if hex >= POSTGRES_SIGNED_BIGINT_BOUND

    # The keys we derive from strings shouldn't ever fall outside the
    # bigint range, but assert that just to be safe.
    advisory_lock_key_range_check(hex)
  else
    raise Error, "passed an invalid key type (#{key.class})"
  end
end

Private Instance Methods

advisory_lock_key_range_check(integer) click to toggle source
# File lib/sequel/extensions/advisory_locking.rb, line 74
def advisory_lock_key_range_check(integer)
  if POSTGRES_SIGNED_BIGINT_RANGE.cover?(integer)
    integer
  else
    raise Error, "given advisory lock integer (#{integer}) falls outside Postgres' bigint range"
  end
end