class PgORM::PgAdvisoryLock

Overview

PostgreSQL advisory locks for distributed locking and synchronization.

Advisory locks are application-level locks that use PostgreSQL's locking infrastructure. They're useful for:

Features

Basic Usage

# Create a lock with a string key
lock = PgORM::PgAdvisoryLock.new("process_payments")

# Synchronize a block of code
lock.synchronize do
  # Only one fiber/process can execute this at a time
  process_payments()
end

# Manual lock/unlock
lock.lock
begin
  process_payments()
ensure
  lock.unlock
end

With Timeout

lock = PgORM::PgAdvisoryLock.new("critical_section")
lock.timeout = 10.seconds

begin
  lock.synchronize do
    # Critical section
  end
rescue PgORM::Error::LockUnavailable
  puts "Could not acquire lock within 10 seconds"
end

Try Lock (Non-blocking)

lock = PgORM::PgAdvisoryLock.new("optional_task")

if lock.try_lock
  begin
    # Got the lock, do work
    perform_task()
  ensure
    lock.unlock
  end
else
  puts "Lock already held, skipping task"
end

How It Works

The lock key (string) is hashed using SHA1 and converted to a 64-bit integer that PostgreSQL's advisory lock functions can use. This means:

Included Modules

Extended Modules

Defined in:

pg-orm/lock.cr

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) #

[View source]
def self.new(pull : JSON::PullParser) #

[View source]
def self.new(key : String, timeout : Time::Span | Nil = nil) #

Creates a new advisory lock with the given key.

Parameters

  • key: String identifier for the lock (will be hashed to Int64)
  • #timeout: Optional timeout for lock acquisition (defaults to 5 seconds)

Example

lock = PgORM::PgAdvisoryLock.new("process_payments")
lock = PgORM::PgAdvisoryLock.new("critical_section", timeout: 10.seconds)

[View source]

Class Method Detail

def self.count #

Returns the number of advisory locks currently held in the database.

Useful for monitoring and debugging.

Example

puts "Active advisory locks: #{PgORM::PgAdvisoryLock.count}"

[View source]

Instance Method Detail

def lock(timeout : Time::Span = self.timeout) : Nil #

Acquires the lock, waiting up to the specified timeout.

Uses exponential backoff (starting at 0.1s, doubling up to 1s) to reduce database load while waiting.

Example

lock = PgORM::PgAdvisoryLock.new("my_lock")
lock.lock(timeout: 10.seconds)
begin
  # Critical section
ensure
  lock.unlock
end

Raises Error::LockUnavailable if the lock cannot be acquired within the timeout.


[View source]
def locked? : Bool #

[View source]
def synchronize(**options, &) #

Acquires the lock, executes the block, and releases the lock.

This is the recommended way to use advisory locks as it ensures the lock is always released, even if an exception occurs.

Example

lock = PgORM::PgAdvisoryLock.new("process_payments")
lock.synchronize do
  # Only one fiber/process can execute this at a time
  Payment.process_pending
end

Raises Error::LockUnavailable if the lock cannot be acquired within the timeout.


[View source]
def timeout : Time::Span #

Lock acquisition timeout (defaults to 5 seconds from settings)


[View source]
def timeout=(timeout : Time::Span) #

Lock acquisition timeout (defaults to 5 seconds from settings)


[View source]
def try_lock #

Attempts to acquire the lock without waiting.

Returns true if the lock was acquired, false if it's already held. This is useful when you want to skip work if the lock is unavailable.

Example

lock = PgORM::PgAdvisoryLock.new("optional_task")
if lock.try_lock
  begin
    perform_task()
  ensure
    lock.unlock
  end
else
  puts "Lock already held, skipping"
end

Raises Error::LockInvalidOp if the lock is already held by this instance.


[View source]
def unlock : Nil #

Releases the lock.

Example

lock.lock
begin
  # Critical section
ensure
  lock.unlock
end

Raises Error::LockInvalidOp if the lock is not currently held.


[View source]