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
AREL_TABLE_NEW_KWARGS =
Arel::Table.instance_method(:initialize).parameters.any? { |type, name| type == :key && name == :name }

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.hash_functionObject



38
39
40
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 38

def hash_function
  @_hash_function ||= "md5"
end

.hash_function=(value) ⇒ Object



34
35
36
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 34

def hash_function=(value)
  @_hash_function = value
end

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.



674
675
676
677
678
679
680
681
682
683
684
685
686
687
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 674

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:



706
707
708
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 706

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)


720
721
722
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 720

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.



695
696
697
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 695

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



743
744
745
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 743

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)


727
728
729
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 727

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)


755
756
757
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 755

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)


749
750
751
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 749

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)


734
735
736
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 734

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)


711
712
713
714
715
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 711

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