Documentation
¶
Overview ¶
Package lock is the shared interface for the ubgo/lock family — a suite of named-mutex implementations spanning single-host (filelock, flock) and distributed (redislock, pglock, etcdlock) backends.
Each concrete backend lives at its own subpath under github.com/ubgo/lock/<name>, and ships a typed Factory with the rich API for the mechanism it implements. This root package defines a tiny minimum-common-denominator interface so consumers who want polymorphism can take "any lock" — same shape as `database/sql`'s driver interface.
Direct backend use (the dominant case) ¶
import "github.com/ubgo/lock/filelock"
locks := filelock.NewFactory(filelock.WithDir("/tmp"))
locks.WithLock(ctx, "job", fn, filelock.WithStaleAfter(5*time.Minute))
Polymorphic use (library authors) ¶
import "github.com/ubgo/lock"
type TaskRunner struct {
locks lock.Locker
}
func (r *TaskRunner) Run(ctx context.Context, name string, fn func() error) error {
h, err := r.locks.Acquire(ctx, name)
if errors.Is(err, lock.ErrLocked) {
return nil // already running; skip
}
if err != nil {
return err
}
defer h.Release()
return fn()
}
Wiring is at startup — your consumers decide which backend goes behind the Locker interface.
The interface deliberately stays minimal — `Acquire(ctx, name)` returning a `Holder` with `Release()`. Anything richer (options, fencing tokens, auto-renewal, max-concurrent, sweep) lives on the concrete backend's typed surface where callers can use it without abstraction loss.
Index ¶
Constants ¶
const ( OutcomeAcquired = "acquired" // Acquire returned a Holder. OutcomeErrLocked = "errlocked" // Acquire returned ErrLocked. OutcomeError = "error" // Acquire returned any other error. )
Outcome enumerates how an Acquire attempt ended. Stable string values across backends — observability backends (Prometheus labels, OTel attributes, log fields) can rely on them across versions.
Variables ¶
var ErrLocked = errors.New("lock: locked")
ErrLocked is the canonical sentinel returned when a lock is held by someone else. Every backend in the family returns this exact value (compare via errors.Is). Mirroring the same sentinel across backends lets polymorphic consumers branch without per-backend type switches:
h, err := locks.Acquire(ctx, "job")
if errors.Is(err, lock.ErrLocked) {
// someone else has it — skip this run
}
Functions ¶
This section is empty.
Types ¶
type Holder ¶
type Holder interface {
// Release frees the lock. Idempotent — calling Release more than
// once on the same Holder returns nil after the first call.
Release() error
}
Holder is the typed handle returned by a successful Acquire. The minimal contract is Release; concrete implementations return a richer type (e.g. *filelock.Holder with Token() and Path()) that satisfies this interface.
type Locker ¶
type Locker interface {
// Acquire takes the named lock. Returns a Holder on success or an
// error otherwise. Implementations return ErrLocked (this package's
// sentinel) when the lock is currently held by someone else.
//
// The context bounds how long Acquire may block on backend I/O —
// for backends that don't block (marker file, flock, in-memory) the
// context is honored only for cancellation.
Acquire(ctx context.Context, name string) (Holder, error)
}
Locker takes a named lock. Implementations live under github.com/ubgo/lock/<backend> — and any user-defined type that satisfies the contract.
type MetricsRecorder ¶ added in v0.2.0
type MetricsRecorder interface {
// AcquireAttempt records an Acquire that finished — successfully
// or otherwise. outcome is one of the Outcome* constants;
// duration is wall-clock time spent in Acquire.
AcquireAttempt(name, outcome string, duration time.Duration)
// HoldDuration records how long a successful Holder was held
// before Release. Called once per Release on the original
// (non-idempotent) call.
HoldDuration(name string, duration time.Duration)
// ActiveLocksDelta reports a change in the count of currently
// held locks for name (+1 on Acquire, -1 on Release). Backends
// that expose a gauge sum these into the running total.
ActiveLocksDelta(name string, delta int)
}
MetricsRecorder is the integration point for metrics backends. The interface is defined in stdlib types only so applications can wire in Prometheus, OTel meters, statsd, or anything else without forcing every consumer to pull in those deps.
Implementations must be safe for concurrent use.
type SpanStarter ¶ added in v0.2.0
SpanStarter is the integration point for tracing backends. It is invoked at the start of every Acquire / Release / Sweep, and the returned func is called when the operation completes. Designed to match the OpenTelemetry Tracer.Start shape so a one-line adapter can wire it up without dragging the otel module into core.
import "go.opentelemetry.io/otel"
starter := func(ctx context.Context, op string) (context.Context, func(err error)) {
ctx, span := otel.Tracer("lock").Start(ctx, op)
return ctx, func(err error) {
if err != nil {
span.RecordError(err)
}
span.End()
}
}
type TraceIDExtractor ¶ added in v0.2.0
TraceIDExtractor pulls a trace ID out of a context for inclusion in lock state — backends that store identifying info (filelock markers, redislock holder values, etc.) record the TraceID so operators reading lock state can jump to the originating trace. Default: returns "" (no trace ID embedded).