lock

package
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package lock provides a single-instance Redis distributed mutex with owner-only release and TTL extension, for single-execution across replicas.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotAcquired = errors.New("lock: not acquired")

ErrNotAcquired is returned when a lock could not be obtained: by Acquire when the context ends before the lock is free, and by Do when another owner holds the lock (the wrapped function is not run).

View Source
var ErrNotHeld = errors.New("lock: not held")

ErrNotHeld is returned by Release/Extend when the lock is no longer owned by this handle (it expired or was acquired by another owner).

Functions

This section is empty.

Types

type Lock

type Lock struct {
	// contains filtered or unexported fields
}

Lock is a held lock handle. It carries the owner token so that Release and Extend act only when this handle still owns the key.

func (*Lock) Extend

func (l *Lock) Extend(ctx context.Context, ttl time.Duration) error

Extend renews the lock's TTL if it is still owned by this handle. It returns ErrNotHeld when the lock has been lost.

func (*Lock) Release

func (l *Lock) Release(ctx context.Context) error

Release deletes the lock if it is still owned by this handle. It returns nil when the key was deleted, or ErrNotHeld when the lock was already gone or is owned by another holder. Release is safe to call in a defer.

func (*Lock) Token

func (l *Lock) Token() string

Token returns the random owner token identifying this lock.

type Locker

type Locker struct {
	// contains filtered or unexported fields
}

Locker acquires distributed locks backed by a single Redis instance.

func New

func New(client redis.UniversalClient, opts ...Option) *Locker

New returns a Locker using the given Redis client.

func (*Locker) Acquire

func (l *Locker) Acquire(ctx context.Context, key string, ttl time.Duration) (*Lock, error)

Acquire blocks until it acquires key for ttl, retrying every retryDelay, until the context is cancelled or its deadline passes. It returns ErrNotAcquired (wrapping ctx.Err()) if the context ends first.

func (*Locker) Do

func (l *Locker) Do(ctx context.Context, key string, ttl time.Duration, fn func() error) error

Do acquires key for ttl, runs fn, then releases the lock. It returns ErrNotAcquired without running fn if the lock is held by another owner. The lock is released even when ctx is cancelled while fn runs. Release errors are ignored; the error from fn is returned.

Example

Do runs a unit of work on at most one replica. Here two Lockers (standing in for two replicas sharing one Redis) contend for the same periodic task: while replica A holds the lock and runs the work, replica B fires concurrently, fails to acquire, and skips with ErrNotAcquired.

package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/alicebob/miniredis/v2"
	"github.com/rahmadafandi/fibr/lock"
	"github.com/redis/go-redis/v9"
)

func main() {
	mr, _ := miniredis.Run()
	defer mr.Close()

	client := redis.NewClient(&redis.Options{Addr: mr.Addr()})
	defer func() { _ = client.Close() }()

	replicaA := lock.New(client)
	replicaB := lock.New(client)
	ctx := context.Background()
	const key = "cron:nightly-cleanup"

	tryB := func() {
		err := replicaB.Do(ctx, key, 30*time.Second, func() error {
			fmt.Println("running on B")
			return nil
		})
		if errors.Is(err, lock.ErrNotAcquired) {
			fmt.Println("skipped on B")
		}
	}

	_ = replicaA.Do(ctx, key, 30*time.Second, func() error {
		fmt.Println("running on A")
		// B fires while A still holds the lock.
		tryB()
		return nil
	})
}
Output:
running on A
skipped on B

func (*Locker) TryAcquire

func (l *Locker) TryAcquire(ctx context.Context, key string, ttl time.Duration) (*Lock, bool, error)

TryAcquire makes one attempt to acquire key for ttl. It returns ok=false (and a nil error) when the lock is already held by another owner; a non-nil error indicates a Redis failure.

type Option

type Option func(*Locker)

Option configures a Locker.

func WithRetryDelay

func WithRetryDelay(d time.Duration) Option

WithRetryDelay sets how long Acquire waits between attempts while a lock is held. The default is 50ms.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL