Module: GoodJob::AdvisoryLockable
- Extended by:
- ActiveSupport::Concern
- Included in:
- BaseExecution, BatchRecord, 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(advisory_lockable_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).
316 317 318 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 316 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.
337 338 339 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 337 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.
368 369 370 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 368 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.
326 327 328 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 326 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(advisory_lockable_function)) ⇒ void
This method returns an undefined value.
Releases all advisory locks on the record that are held by the current database session.
406 407 408 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 406 def advisory_unlock!(key: lockable_key, function: self.class.advisory_unlockable_function(advisory_lockable_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.
375 376 377 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 375 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
418 419 420 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 418 def lockable_column_key(column: self.class._advisory_lockable_column) "#{self.class.table_name}-#{self[column]}" end |
#lockable_key ⇒ String
Default Advisory Lock key
412 413 414 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 412 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.
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 382 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.
354 355 356 357 358 359 360 361 362 363 |
# File 'app/models/concerns/good_job/advisory_lockable.rb', line 354 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 advisory_unlock(key: key, function: self.class.advisory_unlockable_function(function)) end end |