Module: GoodJob::AdvisoryLockable

Extended by:
ActiveSupport::Concern
Included in:
BatchRecord, Job, Process
Defined in:
app/models/concerns/good_job/advisory_lockable.rb

Overview

Adds Postgres advisory locking capabilities to an ActiveRecord record. For details on advisory locks, see the Postgres documentation:

Examples:

Add this concern to a MyRecord class:

class MyRecord < ActiveRecord::Base
  include Lockable

  def my_method
    ...
  end
end

Defined Under Namespace

Classes: AdvisoryLockCounter

Constant Summary collapse

RecordAlreadyAdvisoryLockedError =

Indicates an advisory lock is already held on a record by another database session.

Class.new(StandardError)
ADVISORY_LOCK_COUNTS =
AdvisoryLockCounter.new

Instance Method Summary collapse

Instance Method Details

#advisory_lock(key: lockable_key, function: advisory_lockable_function, connection: nil, &block) ⇒ Boolean, Object

Acquires an advisory lock on this record if it is not already locked by another database session.

Without a block, acquires the lock and returns true/false. The caller is responsible for releasing with #advisory_unlock.

With a block, acquires the lock (raising RecordAlreadyAdvisoryLockedError if it cannot), yields, and releases the lock when the block completes.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to Advisory Lock against

  • function (String, Symbol) (defaults to: advisory_lockable_function)

    Postgres Advisory Lock function name to use

Returns:

  • (Boolean, Object)

    whether the lock was acquired (no block), or the block result.



606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 606

def advisory_lock(key: lockable_key, function: advisory_lockable_function, connection: nil, &block)
  if block
    acquired = false
    result = self.class.advisory_lock_key(key, function: function, connection: connection) do
      acquired = true
      yield
    end
    raise RecordAlreadyAdvisoryLockedError unless acquired

    result
  else
    self.class.advisory_lock_key(key, function: function, connection: connection)
  end
end

#advisory_lock!(key: lockable_key, function: advisory_lockable_function, connection: nil) ⇒ Boolean

Acquires an advisory lock on this record or raises RecordAlreadyAdvisoryLockedError if it is already locked by another database session.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to lock against

  • function (String, Symbol) (defaults to: advisory_lockable_function)

    Postgres Advisory Lock function name to use

Returns:

  • (Boolean)

    true

Raises:



638
639
640
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 638

def advisory_lock!(key: lockable_key, function: advisory_lockable_function, connection: nil)
  advisory_lock(key: key, function: function, connection: connection) || raise(RecordAlreadyAdvisoryLockedError)
end

#advisory_locked?(key: lockable_key) ⇒ Boolean

Tests whether this record has an advisory lock on it.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to test lock against

Returns:

  • (Boolean)


652
653
654
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 652

def advisory_locked?(key: lockable_key)
  self.class.advisory_locked_key?(key)
end

#advisory_unlock(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function), connection: nil) ⇒ Boolean

Releases an advisory lock on this record if it is locked by this database session. Note that advisory locks stack, so you must call #advisory_unlock and #advisory_lock the same number of times.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to lock against

  • function (String, Symbol) (defaults to: self.class.advisory_unlockable_function(advisory_lockable_function))

    Postgres Advisory Lock function name to use

Returns:

  • (Boolean)

    whether the lock was released.



627
628
629
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 627

def advisory_unlock(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function), connection: nil)
  self.class.advisory_unlock_key(key, function: function, connection: connection || self.class.lease_connection)
end

#advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function) ⇒ void

This method returns an undefined value.

Releases all advisory locks on the record that are held by the current database session.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to lock against

  • function (String, Symbol) (defaults to: self.class.advisory_unlockable_function)

    Postgres Advisory Lock function name to use



675
676
677
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 675

def advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function)
  self.class.advisory_unlock_key!(key, function: function)
end

#advisory_unlocked?(key: lockable_key) ⇒ Boolean

Tests whether this record does not have an advisory lock on it.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to test lock against

Returns:

  • (Boolean)


659
660
661
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 659

def advisory_unlocked?(key: lockable_key)
  !advisory_locked?(key: key)
end

#lockable_column_key(column: self.class._advisory_lockable_column) ⇒ String

Default Advisory Lock key for column-based locking

Returns:

  • (String)


687
688
689
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 687

def lockable_column_key(column: self.class._advisory_lockable_column)
  "#{self.class.table_name}-#{self[column]}"
end

#lockable_keyString

Default Advisory Lock key

Returns:

  • (String)


681
682
683
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 681

def lockable_key
  lockable_column_key
end

#owns_advisory_lock?(key: lockable_key) ⇒ Boolean

Tests whether this record is locked by the current database session.

Parameters:

  • key (String, Symbol) (defaults to: lockable_key)

    Key to test lock against

Returns:

  • (Boolean)


666
667
668
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 666

def owns_advisory_lock?(key: lockable_key)
  self.class.owns_advisory_lock_key?(key)
end

#with_advisory_lock(key: lockable_key, function: advisory_lockable_function, &block) ⇒ Object

Deprecated.

Use #advisory_lock with a block instead.

Raises:

  • (ArgumentError)


643
644
645
646
647
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 643

def with_advisory_lock(key: lockable_key, function: advisory_lockable_function, &block)
  raise ArgumentError, "Must provide a block" unless block

  advisory_lock(key: key, function: function, &block)
end