Documentation
¶
Overview ¶
Package worklease provides lease-based work coordination for distributed systems.
worklease solves a different problem than distributed locking. Distributed locks answer: who owns this resource right now? worklease answers: when ownership changes, what does the new owner need to continue the work the previous owner started?
The library is built around three primitives:
- A Lease — a time-limited, named claim on a unit of work.
- A fencing token — a monotonically incrementing integer that prevents zombie writes from a worker that was slow, not dead.
- A checkpoint — progress state written atomically with lease renewal, so the last known state survives to the next owner.
Entry point ¶
Construct a Lease with New, backed by a [Backend]:
backend, err := postgres.New(db)
lease, err := worklease.New(backend, worklease.Config{
TTL: 30 * time.Second,
HolderID: workerID,
})
See the Lease interface for the full API.
What worklease is not ¶
worklease is not a distributed lock library, a workflow engine, or a general-purpose job queue. For mutual exclusion alone, use distlock or pglock. For durable workflow execution, use Temporal. For Kubernetes-native leader election, use client-go/leaderelection. The leader subpackage provides simplified single-work-item leadership, and the pool subpackage distributes a fixed set of work IDs across competing processes — both built on worklease's own Lease primitives.
Backends ¶
Two backends are included:
- github.com/aetomala/worklease/backend/postgres — PostgreSQL-backed, suitable for production use. Requires a running PostgreSQL instance and the worklease schema applied.
- github.com/aetomala/worklease/backend/memory — in-memory, suitable for unit tests within a single process.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrFenced = errors.New(msgFenced) ErrLeaseHeld = errors.New(msgLeaseHeld) ErrLeaseExpired = errors.New(msgLeaseExpired) // ErrLeaseWindowExhausted is set as the cancel cause of the renewal context // when the renewal goroutine exhausts the remaining lease window without a // successful renewal (goroutine lifecycle path 3). // Inspect via context.Cause(renewCtx) — not returned directly from any method. ErrLeaseWindowExhausted = errors.New(msgLeaseWindowExhausted) )
Sentinel errors for Lease operations.
Functions ¶
func HasWaitForLease ¶ added in v0.3.0
func HasWaitForLease(opts []AcquireOption) bool
HasWaitForLease reports whether opts includes WithWaitForLease. Pool.New uses this to enforce that WithWaitForLease is not passed in Config.AcquireOptions at construction time.
Types ¶
type AcquireEvent ¶ added in v0.4.0
AcquireEvent carries the result of an Acquire call. Duration is the duration of the final backend call only — not the wait loop. Token is the zero value of Token if Err is non-nil.
type AcquireOption ¶
type AcquireOption func(*acquireConfig)
AcquireOption is a functional option for Acquire.
func WithPollInterval ¶
func WithPollInterval(d time.Duration) AcquireOption
WithPollInterval sets the interval at which Acquire polls when WithWaitForLease is active. If d is zero or negative, the default (2 * time.Second) is preserved.
func WithWaitForLease ¶
func WithWaitForLease() AcquireOption
WithWaitForLease configures Acquire to block and retry until a lease becomes available, rather than returning ErrLeaseHeld immediately.
type CheckpointEvent ¶ added in v0.4.0
CheckpointEvent carries the result of a Checkpoint call. Size is len(state) from the call site.
type Config ¶
type Config struct {
// TTL is the time-to-live for acquired leases. Required; zero returns an error.
TTL time.Duration
// HolderID is the identifier of the entity that will hold leases. Required; empty returns an error.
HolderID string
// Observer is the LeaseObserver that will be notified of lease events. Optional; nil defaults to noopObserver.
Observer LeaseObserver
}
Config holds configuration for a Lease instance.
type FencedEvent ¶ added in v0.4.0
FencedEvent carries the context of a fencing event. Called in addition to the operation-specific event — not instead of.
type Lease ¶
type Lease interface {
// Acquire attempts to acquire a lease for the given workID. Returns ErrLeaseHeld
// if a lease already exists for this workID. If WithWaitForLease is set, blocks
// until the lease is available, polling at the configured interval.
Acquire(ctx context.Context, workID string, opts ...AcquireOption) (Token, error)
// Checkpoint persists state associated with the current lease. The caller must
// pass a valid Token obtained from Acquire or Renew. Returns ErrFenced if the
// token's fencing token no longer matches the stored lease.
Checkpoint(ctx context.Context, token Token, state []byte) error
// Renew extends the lease expiration time. Returns ErrFenced if the token's
// fencing token no longer matches the stored lease, or ErrLeaseExpired if the
// lease has already expired.
Renew(ctx context.Context, token Token) error
// Release surrenders the lease and expires it immediately, making the work item
// available for acquisition by a successor without waiting for the TTL. Sets
// clean_handoff so the successor knows the previous owner finished intentionally.
// Returns ErrFenced if the fencing token no longer matches the stored lease.
Release(ctx context.Context, token Token) error
// ReadCheckpoint retrieves persisted state and the clean handoff flag for the
// given lease. The caller must pass a valid Token. Returns ErrFenced if the
// token's fencing token no longer matches the stored lease.
ReadCheckpoint(ctx context.Context, token Token) (state []byte, cleanHandoff bool, err error)
// StartRenewal begins automatic renewal of the lease at regular intervals. Returns
// a derived context and a stop function. Calling stop cancels the renewal context
// and terminates the renewal loop. The renewal context is cancelled if the underlying
// context is cancelled or if the lease is lost.
StartRenewal(ctx context.Context, token Token, opts ...RenewalOption) (renewCtx context.Context, stopRenewal func())
}
Lease defines the contract for acquiring, managing, and renewing leases on distributed work. Implementations are responsible for handling backend storage, fencing, and expiration logic. All methods are safe for concurrent use.
type LeaseObserver ¶ added in v0.2.0
type LeaseObserver interface {
// OnAcquire is called after every Acquire attempt, successful or not.
// e.Duration is the duration of the final backend call only — not the wait loop.
// e.Token is the zero value of Token if e.Err is non-nil.
OnAcquire(ctx context.Context, e AcquireEvent)
// OnCheckpoint is called after every Checkpoint attempt.
// If e.Err is ErrFenced, OnFenced is also called after this method returns.
OnCheckpoint(ctx context.Context, e CheckpointEvent)
// OnRenew is called after every Renew attempt.
// If e.Err is ErrFenced, OnFenced is also called after this method returns.
OnRenew(ctx context.Context, e RenewEvent)
// OnRelease is called after every Release attempt.
// If e.Err is ErrFenced, OnFenced is also called after this method returns.
OnRelease(ctx context.Context, e ReleaseEvent)
// OnReadCheckpoint is called after every ReadCheckpoint attempt.
// OnFenced is NOT called on ReadCheckpoint — ErrFenced surfaces via e.Err only.
OnReadCheckpoint(ctx context.Context, e ReadCheckpointEvent)
// OnFenced is called when Checkpoint, Renew, or Release returns ErrFenced.
// Called in addition to the operation-specific callback — not instead of.
// e.Operation identifies which operation triggered the fencing event.
OnFenced(ctx context.Context, e FencedEvent)
}
LeaseObserver receives callbacks after each Lease operation. All methods are called synchronously. Implementations must not block or panic. The zero value of Config.Observer is nil; the library substitutes a no-op observer.
type Operation ¶ added in v0.4.0
type Operation uint8
Operation identifies the Lease operation that triggered a fencing event.
type ReadCheckpointEvent ¶ added in v0.4.0
type ReadCheckpointEvent struct {
Token Token
Duration time.Duration
CleanHandoff bool
Size int
Err error
}
ReadCheckpointEvent carries the result of a ReadCheckpoint call. Size is len of the returned state slice; 0 if nil.
type ReleaseEvent ¶ added in v0.4.0
ReleaseEvent carries the result of a Release call.
type RenewEvent ¶ added in v0.4.0
RenewEvent carries the result of a Renew call. Attempt is 1 on the first attempt. The renewal goroutine increments Attempt on each retry attempt within the backoff loop. Direct calls to Renew from caller code always produce Attempt: 1.
type RenewalOption ¶
type RenewalOption func(*renewalConfig)
RenewalOption is a functional option for StartRenewal.
func WithRenewalBackoff ¶ added in v0.5.0
func WithRenewalBackoff(initial, maxInterval time.Duration, jitter float64) RenewalOption
WithRenewalBackoff configures the exponential backoff policy used by the renewal goroutine when Renew returns a non-fencing, non-nil error. The initial parameter is the first retry interval; maxInterval caps the interval after growth. The jitter parameter is a fraction in [0, 1]; the actual wait is drawn from [base, base+jitter*base]. Defaults: initial=100ms, max=5s, jitter=0.20. Clamping (in order): initial floors to 1ms if <= 0; max floors to 1ms if <= 0; jitter clamps to [0, 1]; finally initial caps at max if initial > max.
func WithRenewalInterval ¶
func WithRenewalInterval(d time.Duration) RenewalOption
WithRenewalInterval sets the time between renewal attempts. Default: TTL/2. Zero or negative values are ignored — the default is used silently.
type Token ¶
type Token struct {
// contains filtered or unexported fields
}
Token represents a currently held lease. It is returned by Acquire and Renew and must be passed back to Checkpoint, Renew, Release, ReadCheckpoint, and StartRenewal. All fields are unexported; use accessor methods to read them.
func (Token) FencingToken ¶
FencingToken returns the monotonically increasing token that prevents stale operations on the lease. If the lease is reassigned, the token changes.
Directories
¶
| Path | Synopsis |
|---|---|
|
conformance
Package conformance provides a backend-agnostic Ginkgo specification that any worklease backend must satisfy.
|
Package conformance provides a backend-agnostic Ginkgo specification that any worklease backend must satisfy. |
|
Package testutil is a generated GoMock package.
|
Package testutil is a generated GoMock package. |