Module: WithAdvisoryLock::PostgreSQLAdvisory
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/with_advisory_lock/postgresql_advisory.rb
Constant Summary collapse
- LOCK_PREFIX_ENV =
'WITH_ADVISORY_LOCK_PREFIX'
- LOCK_RESULT_VALUES =
['t', true].freeze
- ERROR_MESSAGE_REGEX =
/ ERROR: +current transaction is aborted,/
Instance Method Summary collapse
-
#advisory_lock_exists_for?(lock_name, shared: false) ⇒ Boolean
Non-blocking check for advisory lock existence to avoid race conditions This queries pg_locks directly instead of trying to acquire the lock.
- #lock_keys_for(lock_name) ⇒ Object
- #release_advisory_lock(*args) ⇒ Object
- #supports_database_timeout? ⇒ Boolean
- #try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil) ⇒ Object
Instance Method Details
#advisory_lock_exists_for?(lock_name, shared: false) ⇒ Boolean
Non-blocking check for advisory lock existence to avoid race conditions This queries pg_locks directly instead of trying to acquire the lock
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/with_advisory_lock/postgresql_advisory.rb', line 61 def advisory_lock_exists_for?(lock_name, shared: false) lock_keys = lock_keys_for(lock_name) query = <<~SQL.squish SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND database = (SELECT oid FROM pg_database WHERE datname = CURRENT_DATABASE()) AND classid = #{lock_keys.first} AND objid = #{lock_keys.last} AND mode = '#{shared ? 'ShareLock' : 'ExclusiveLock'}' LIMIT 1 SQL query_value(query).present? rescue ActiveRecord::StatementInvalid # If pg_locks is not accessible, fall back to nil to indicate we should use the default method nil end |
#lock_keys_for(lock_name) ⇒ Object
48 49 50 51 52 53 |
# File 'lib/with_advisory_lock/postgresql_advisory.rb', line 48 def lock_keys_for(lock_name) [ stable_hashcode(lock_name), ENV.fetch(LOCK_PREFIX_ENV, nil) ].map { |ea| ea.to_i & 0x7fffffff } end |
#release_advisory_lock(*args) ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/with_advisory_lock/postgresql_advisory.rb', line 20 def release_advisory_lock(*args) # Handle both signatures - ActiveRecord's built-in and ours if args.length == 1 && args[0].is_a?(Integer) # ActiveRecord's built-in signature: release_advisory_lock(lock_id) super else # Our signature: release_advisory_lock(lock_keys, lock_name:, shared:, transaction:) lock_keys, = args return if [:transaction] function = advisory_unlock_function([:shared]) execute_advisory(function, lock_keys, [:lock_name]) end rescue ActiveRecord::StatementInvalid => e # If the connection is broken, the lock is automatically released by PostgreSQL # No need to fail the release operation return if e.cause.is_a?(PG::ConnectionBad) || e. =~ /PG::ConnectionBad/ raise unless e. =~ ERROR_MESSAGE_REGEX begin rollback_db_transaction execute_advisory(function, lock_keys, [:lock_name]) ensure begin_db_transaction end end |
#supports_database_timeout? ⇒ Boolean
55 56 57 |
# File 'lib/with_advisory_lock/postgresql_advisory.rb', line 55 def supports_database_timeout? false end |
#try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil) ⇒ Object
13 14 15 16 17 18 |
# File 'lib/with_advisory_lock/postgresql_advisory.rb', line 13 def try_advisory_lock(lock_keys, lock_name:, shared:, transaction:, timeout_seconds: nil) # timeout_seconds is accepted for compatibility but ignored - PostgreSQL doesn't support # native timeouts with pg_try_advisory_lock, requiring Ruby-level polling instead function = advisory_try_lock_function(transaction, shared) execute_advisory(function, lock_keys, lock_name) end |