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:
Constant Summary collapse
- RecordAlreadyAdvisoryLockedError =
Indicates an advisory lock is already held on a record by another database session.
Class.new(StandardError)
Instance Method Summary collapse
-
#advisory_lock(key: lockable_key, function: advisory_lockable_function) ⇒ Boolean
Acquires an advisory lock on this record if it is not already locked by another database session.
-
#advisory_lock!(key: lockable_key, function: advisory_lockable_function) ⇒ Boolean
Acquires an advisory lock on this record or raises RecordAlreadyAdvisoryLockedError if it is already locked by another database session.
-
#advisory_locked?(key: lockable_key) ⇒ Boolean
Tests whether this record has an advisory lock on it.
-
#advisory_unlock(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function)) ⇒ Boolean
Releases an advisory lock on this record if it is locked by this database session.
-
#advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function) ⇒ void
Releases all advisory locks on the record that are held by the current database session.
-
#advisory_unlocked?(key: lockable_key) ⇒ Boolean
Tests whether this record does not have an advisory lock on it.
-
#lockable_column_key(column: self.class._advisory_lockable_column) ⇒ String
Default Advisory Lock key for column-based locking.
-
#lockable_key ⇒ String
Default Advisory Lock key.
-
#owns_advisory_lock?(key: lockable_key) ⇒ Boolean
Tests whether this record is locked by the current database session.
-
#with_advisory_lock(key: lockable_key, function: advisory_lockable_function) { ... } ⇒ Object
Acquires an advisory lock on this record and safely releases it after the passed block is completed.
Instance Method Details
#advisory_lock(key: lockable_key, function: advisory_lockable_function) ⇒ Boolean
Acquires an advisory lock on this record if it is not already locked by another database session. Be careful to ensure you release the lock when you are done with #advisory_unlock (or #advisory_unlock! to release all remaining locks).
325 326 327 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 325 def advisory_lock(key: lockable_key, function: advisory_lockable_function) self.class.advisory_lock_key(key, function: function) end |
#advisory_lock!(key: lockable_key, function: advisory_lockable_function) ⇒ Boolean
Acquires an advisory lock on this record or raises RecordAlreadyAdvisoryLockedError if it is already locked by another database session.
346 347 348 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 346 def advisory_lock!(key: lockable_key, function: advisory_lockable_function) self.class.advisory_lock_key(key, function: function) || raise(RecordAlreadyAdvisoryLockedError) end |
#advisory_locked?(key: lockable_key) ⇒ Boolean
Tests whether this record has an advisory lock on it.
378 379 380 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 378 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)) ⇒ 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.
335 336 337 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 335 def advisory_unlock(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_function)) self.class.advisory_unlock_key(key, function: function) 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.
416 417 418 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 416 def advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function) advisory_unlock(key: key, function: function) while advisory_locked? end |
#advisory_unlocked?(key: lockable_key) ⇒ Boolean
Tests whether this record does not have an advisory lock on it.
385 386 387 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 385 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
428 429 430 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 428 def lockable_column_key(column: self.class._advisory_lockable_column) "#{self.class.table_name}-#{self[column]}" end |
#lockable_key ⇒ String
Default Advisory Lock key
422 423 424 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 422 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.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 392 def owns_advisory_lock?(key: lockable_key) self.class.owns_advisory_lock_key?(key) query = <<~SQL.squish SELECT 1 AS one FROM pg_locks WHERE pg_locks.locktype = 'advisory' AND pg_locks.objsubid = 1 AND pg_locks.classid = ('x' || substr(md5($1::text), 1, 16))::bit(32)::int AND pg_locks.objid = (('x' || substr(md5($2::text), 1, 16))::bit(64) << 32)::bit(32)::int AND pg_locks.pid = pg_backend_pid() LIMIT 1 SQL binds = [ ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new), ActiveRecord::Relation::QueryAttribute.new('key', key, ActiveRecord::Type::String.new), ] self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Owns Advisory Lock?', binds).any? end |
#with_advisory_lock(key: lockable_key, function: advisory_lockable_function) { ... } ⇒ Object
Acquires an advisory lock on this record and safely releases it after the passed block is completed. If the record is locked by another database session, this raises RecordAlreadyAdvisoryLockedError.
363 364 365 366 367 368 369 370 371 372 373 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 363 def with_advisory_lock(key: lockable_key, function: advisory_lockable_function) raise ArgumentError, "Must provide a block" unless block_given? advisory_lock!(key: key, function: function) begin yield ensure unlock_function = self.class.advisory_unlockable_function(function) advisory_unlock(key: key, function: unlock_function) if unlock_function end end |