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).
333 334 335 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 333 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.
354 355 356 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 354 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.
386 387 388 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 386 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.
343 344 345 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 343 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.
424 425 426 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 424 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.
393 394 395 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 393 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
436 437 438 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 436 def lockable_column_key(column: self.class._advisory_lockable_column) "#{self.class.table_name}-#{self[column]}" end |
#lockable_key ⇒ String
Default Advisory Lock key
430 431 432 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 430 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.
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 400 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.
371 372 373 374 375 376 377 378 379 380 381 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 371 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 |