Documentation
¶
Overview ¶
Package postgres implements distributedlock.Locker against PostgreSQL session- scoped advisory locks (pg_try_advisory_lock). It uses an existing platform/database.Client for connection management.
TTL semantics: PostgreSQL advisory locks have no native TTL, so this provider enforces the TTL client-side. Acquire stamps an expiry, Expired reports against it, and Refresh both verifies the underlying session is alive and extends the expiry by the TTL it is given.
What that does and does not buy you is worth being precise about. The expiry is this process's own bookkeeping: it stops a caller from believing it still holds a lock it has held past its TTL. It is not enforced by the database, so a process that ignores Expired keeps the advisory lock until Release is called or the dedicated session ends (a network failure, or Locker.Close). Another process will not acquire the lock merely because the first one's TTL lapsed — for a hard, server-side bound, use a lease table rather than advisory locks.
Each Acquire reserves a dedicated *sql.Conn from the database client's pool so that the matching pg_advisory_unlock targets the same session. The Locker tracks outstanding connections internally and releases all of them on Close.
Index ¶
Constants ¶
const DefaultConnWaitTimeout = 5 * time.Second
DefaultConnWaitTimeout bounds how long Acquire waits to reserve a write-pool connection before giving up. See Config.ConnWaitTimeout.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Namespace is mixed into the lock-id hash so independent services sharing a
// Postgres cluster do not collide on the same advisory-lock id space.
Namespace int32 `env:"NAMESPACE" envDefault:"0" json:"namespace,omitempty" yaml:"namespace,omitempty"`
// ConnWaitTimeout bounds how long Acquire will wait to reserve a connection
// from the write pool. Each held lock pins one write connection for its whole
// lifetime, so a saturated pool would otherwise make Acquire block indefinitely
// in database/sql's Conn(). When the wait is exceeded, Acquire returns
// distributedlock.ErrLockNotAcquired instead of blocking. Zero uses
// DefaultConnWaitTimeout; a negative value disables the bound (wait forever).
ConnWaitTimeout time.Duration `env:"CONN_WAIT_TIMEOUT" envDefault:"5s" json:"connWaitTimeout,omitempty" yaml:"connWaitTimeout,omitempty"`
}
Config configures a Postgres-backed distributed locker. Namespace is mixed into the lock-id hash so independent services that share a Postgres cluster do not collide on the same advisory-lock id space.
type Locker ¶
type Locker struct {
// contains filtered or unexported fields
}
func NewPostgresLocker ¶
func NewPostgresLocker( cfg *Config, db database.Client, cb circuitbreaking.CircuitBreaker, opts ...Option, ) (*Locker, error)
NewPostgresLocker constructs a new postgres-backed distributedlock.Locker.
func (*Locker) Acquire ¶
func (l *Locker) Acquire(ctx context.Context, key string, ttl time.Duration) (distributedlock.Lock, error)
Acquire implements distributedlock.Locker.
type Option ¶
type Option func(*options)
Option configures the postgres-backed lockers this package constructs. The zero configuration works: an absent logger logs nowhere, an absent tracer provider traces nowhere, and an absent metrics provider records nothing.
func WithMetricsProvider ¶
WithMetricsProvider attaches a metrics provider for the locker's counters and latency histogram.
func WithTracerProvider ¶
WithTracerProvider attaches a tracer provider, enabling spans on every lock operation.
type ScopedLocker ¶
type ScopedLocker struct {
// contains filtered or unexported fields
}
ScopedLocker implements distributedlock.ScopedLocker with transaction-scoped advisory locks. Each call opens a throwaway transaction that exists only to hold the lock while fn runs: the lock dies with the transaction on return, on error, on panic, or — crucially — with the connection if the process crashes mid-fn. There is no session pinning, no outstanding-lock bookkeeping, and no TTL: the lock is held exactly as long as fn runs.
func NewPostgresScopedLocker ¶
func NewPostgresScopedLocker( cfg *Config, db database.Client, cb circuitbreaking.CircuitBreaker, opts ...Option, ) (*ScopedLocker, error)
NewPostgresScopedLocker constructs a transaction-scoped distributedlock.ScopedLocker. Unlike NewPostgresLocker it needs only the safe database.Client surface (WithTransaction), not RawAccess.
fn runs while the calling transaction holds the advisory lock, but receives only a context: any database work fn performs goes through its own connections and is NOT part of the lock-holding transaction. WithLock waits in the database (pg_advisory_xact_lock queues server-side, so waiters need no polling and are granted the lock in request order); each in-flight call occupies one write-pool connection for fn's duration.
func (*ScopedLocker) TryWithLock ¶
func (s *ScopedLocker) TryWithLock(ctx context.Context, key string, fn func(ctx context.Context) error) (bool, error)
TryWithLock implements distributedlock.ScopedLocker without waiting.
func (*ScopedLocker) WithLock ¶
func (s *ScopedLocker) WithLock(ctx context.Context, key string, fn func(ctx context.Context) error) error
WithLock implements distributedlock.ScopedLocker. The wait happens in the database: pg_advisory_xact_lock blocks until the lock is granted, and a canceled ctx aborts the wait through the driver.