Module: GoodJob::Lockable
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 ⇒ Boolean
Acquires an advisory lock on this record if it is not already locked by another database session.
-
#advisory_lock! ⇒ Boolean
Acquires an advisory lock on this record or raises RecordAlreadyAdvisoryLockedError if it is already locked by another database session.
-
#advisory_locked? ⇒ Boolean
Tests whether this record has an advisory lock on it.
-
#advisory_unlock ⇒ Boolean
Releases an advisory lock on this record if it is locked by this database session.
-
#advisory_unlock! ⇒ void
Releases all advisory locks on the record that are held by the current database session.
-
#owns_advisory_lock? ⇒ Boolean
Tests whether this record is locked by the current database session.
-
#with_advisory_lock { ... } ⇒ Object
Acquires an advisory lock on this record and safely releases it after the passed block is completed.
Instance Method Details
#advisory_lock ⇒ 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).
142 143 144 145 146 147 |
# File 'lib/good_job/lockable.rb', line 142 def advisory_lock where_sql = <<~SQL.squish pg_try_advisory_lock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint) SQL self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }]) end |
#advisory_lock! ⇒ Boolean
Acquires an advisory lock on this record or raises RecordAlreadyAdvisoryLockedError if it is already locked by another database session.
165 166 167 168 |
# File 'lib/good_job/lockable.rb', line 165 def advisory_lock! result = advisory_lock result || raise(RecordAlreadyAdvisoryLockedError) end |
#advisory_locked? ⇒ Boolean
Tests whether this record has an advisory lock on it.
193 194 195 |
# File 'lib/good_job/lockable.rb', line 193 def advisory_locked? self.class.unscoped.advisory_locked.exists?(id: send(self.class.primary_key)) end |
#advisory_unlock ⇒ 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.
153 154 155 156 157 158 |
# File 'lib/good_job/lockable.rb', line 153 def advisory_unlock where_sql = <<~SQL.squish pg_advisory_unlock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint) SQL self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }]) end |
#advisory_unlock! ⇒ void
This method returns an undefined value.
Releases all advisory locks on the record that are held by the current database session.
206 207 208 |
# File 'lib/good_job/lockable.rb', line 206 def advisory_unlock! advisory_unlock while advisory_locked? end |
#owns_advisory_lock? ⇒ Boolean
Tests whether this record is locked by the current database session.
199 200 201 |
# File 'lib/good_job/lockable.rb', line 199 def owns_advisory_lock? self.class.unscoped.owns_advisory_locked.exists?(id: send(self.class.primary_key)) end |
#with_advisory_lock { ... } ⇒ 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.
182 183 184 185 186 187 188 189 |
# File 'lib/good_job/lockable.rb', line 182 def with_advisory_lock raise ArgumentError, "Must provide a block" unless block_given? advisory_lock! yield ensure advisory_unlock unless $ERROR_INFO.is_a? RecordAlreadyAdvisoryLockedError end |