Module: Sequel::AdvisoryLocking

Defined in:
lib/sequel/extensions/advisory_locking.rb,
lib/sequel/extensions/advisory_locking/version.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

HEX_STRING_SLICE_RANGE =
(0..15).freeze
POSTGRES_SIGNED_BIGINT_BOUND =
2**63
POSTGRES_UNSIGNED_BIGINT_BOUND =
2**64
POSTGRES_SIGNED_BIGINT_MAXIMUM =
POSTGRES_SIGNED_BIGINT_BOUND - 1
POSTGRES_SIGNED_BIGINT_MINIMUM =
-POSTGRES_SIGNED_BIGINT_BOUND
POSTGRES_SIGNED_BIGINT_RANGE =
(POSTGRES_SIGNED_BIGINT_MINIMUM..POSTGRES_SIGNED_BIGINT_MAXIMUM).freeze
VERSION =
'0.2.0'

Instance Method Summary collapse

Instance Method Details

#advisory_lock(key, try: false, shared: false, &block) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 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) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 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