Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// TTL is the lease duration for each EID lock row.
TTL time.Duration `yaml:"ttl"`
// AcquireBackoff is the wait between retry attempts when a lock is contended.
AcquireBackoff time.Duration `yaml:"acquireBackoff"`
// AcquireDeadline is the total time allowed to acquire all EID locks.
AcquireDeadline time.Duration `yaml:"acquireDeadline"`
// Heartbeat is the interval at which held leases are renewed (~TTL/3).
Heartbeat time.Duration `yaml:"heartbeat"`
// Owner identifies this replica and is required: it scopes every lease
// query, so two replicas sharing one owner value share their leases.
// Defaults to the FSC node ID when empty; locker construction fails when
// both this field and the node ID are empty or blank.
Owner string `yaml:"owner"`
}
Config holds Postgres lease-table locking settings.
type Locker ¶
type Locker struct {
// contains filtered or unexported fields
}
Locker implements locker.Locker using a SQL lease table. Acquire and renew queries use Postgres-specific features (TIMESTAMPTZ, ON CONFLICT DO UPDATE … RETURNING, ::interval casts).
func New ¶
New creates a Postgres-backed distributed Locker. The table is created if it does not exist. db must be a *sql.DB connected to Postgres.
cfg.Owner defaults to the identifier reported by replicaID (the FSC node ID in production). It returns an error wrapping errs.ErrLockerOwnerRequired when the resolved owner is empty or blank — including when replicaID is nil or reports an empty ID — because an owner shared by several replicas disables mutual exclusion across all of them. That check runs before the table is created, so a misconfigured node fails without touching the database.
func (*Locker) AcquireLocks ¶
AcquireLocks claims a lease on every enrollment ID in eIDs for anchor, across all replicas sharing the table.
Implementation: the IDs are deduplicated and sorted (dedup.AndSort) for the same deadlock-free ordering the in-memory locker relies on. It then retries tryAcquireAll — a single atomic upsert that succeeds only if it can claim every ID — sleeping AcquireBackoff between attempts and giving up with a contention error once AcquireDeadline passes (or ctx is cancelled). On success it records the held IDs under anchor and starts a background heartbeat that renews the leases before they expire, so a long-running audit keeps its locks while a crashed replica's leases expire and become claimable by others. Any partial state is released on the give-up/cancel paths.
func (*Locker) AssertLocksHeld ¶
AssertLocksHeld verifies this replica still holds every lease it acquired for anchor. It compares the number of IDs recorded locally at acquisition time against the count of matching, non-expired, owner-scoped rows in the table. A mismatch (or no local record) means a lease expired and may have been taken over by another replica, so it returns ErrLockNotHeld. Callers use this after long-running work to confirm their locks were not silently lost.