lock

package
v0.1.53 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 9 Imported by: 0

README

lock

Distributed locking using Redis. Ensures only one instance executes a critical section.

What It Does

Implements distributed locks using Redis with automatic expiration, retry logic, and safe release. Useful for:

  • Preventing duplicate job processing
  • Coordinating across multiple service instances
  • Ensuring single execution of background tasks

Basic Usage

lock := lock.NewRedisLock(redisClient)

// Execute function while holding lock
err := lock.Execute(ctx, "my-resource", 30*time.Second, func() error {
    // Only one instance can be here at a time
    return doCriticalWork()
})

Manual Lock Control

// Try to acquire (non-blocking)
result, err := lock.TryAcquire(ctx, "resource", 30*time.Second)
if result.Acquired {
    // Got the lock
    defer lock.Release(ctx, "resource")
}

// Acquire with retry
result, err := lock.Acquire(ctx, "resource", 30*time.Second, 10*time.Second)
if err != nil {
    return err // Couldn't get lock in time
}
// result.LockID identifies this lock instance
defer lock.ReleaseWithID(ctx, "resource", result.LockID)

Lock Result

type LockResult struct {
    Acquired  bool          // Whether lock was obtained
    LockID    string        // Unique ID for this lock instance
    WaitTime  time.Duration // How long we waited
    ExpiresAt time.Time     // When lock expires
}

Safe Release

// Release only if we own the lock (matches LockID)
err := lock.ReleaseWithID(ctx, "resource", lockID)

// Simple release (may release someone else's lock - use carefully)
err := lock.Release(ctx, "resource")

Extending Locks

// Refresh TTL (must own the lock)
err := lock.Refresh(ctx, "resource", lockID, 30*time.Second)

// Auto-extend while running long tasks
go lock.ExtendLock(ctx, "resource", lockID, 30*time.Second, 10*time.Second)
// Continues extending until context cancelled or lock lost

Retry with Backoff

// Execute with automatic retry on failure
err := lock.ExecuteWithRetry(ctx, "resource", 30*time.Second, 3, func() error {
    return doWork()
})
// Retries up to 3 times if the function fails

Checking Lock Status

// Is lock held?
isLocked, err := lock.IsLocked(ctx, "resource")

// Get current lock holder (if available)
lockID, err := lock.GetLockID(ctx, "resource")

In-Memory Lock (Testing)

// For testing or single-instance scenarios
memLock := lock.NewMemoryLock()
memLock.TryAcquire(ctx, "resource", time.Second)
// Doesn't support same features as Redis lock

Error Handling

var (
    ErrLockFailed        = errors.New("failed to acquire lock")
    ErrLockNotHeld       = errors.New("lock not held")
    ErrLockExpired       = errors.New("lock has expired")
    ErrInvalidLockDuration = errors.New("lock duration must be positive")
)

Metrics

// Enable metrics collection
lock := lock.NewRedisLock(redisClient, lock.WithMetrics(lock.MetricsConfig{
    EnableWaitTime:   true,   // track wait time histogram
    EnableContention: true,   // track contention events
}))

// When lock is acquired:
// mesh_lock_acquired_total{key="resource",owner="12345"} 1
// mesh_lock_wait_seconds{key="resource",owner="12345"} 0.234
// mesh_lock_contention_total{key="resource",owner="12345"} 3

Best Practices

  1. Always use ReleaseWithID - safer than simple Release
  2. Set reasonable TTL - not too short (task doesn't finish), not too long (slow recovery if crash)
  3. Use Execute when possible - handles acquisition and release automatically
  4. Handle context cancellation - if your operation is cancelled, the lock will auto-expire

Documentation

Overview

Package lock provides distributed locking utilities for all services.

This package implements distributed locking using Redis with proper error handling, automatic retry, and lock expiration.

Example:

lock := lock.NewRedisLock(redisClient)
err := lock.Execute(ctx, "my-resource", 30*time.Second, func() error {
    // Critical section - only one instance will execute this
    return doWork()
})

Metrics support:

lock := lock.NewRedisLock(redisClient, lock.WithMetrics(lock.MetricsConfig{
    EnableWaitTime:   true,
    EnableContention: true,
}))

Metrics exported:

  • mesh_lock_acquired_total{key,owner} - locks acquired
  • mesh_lock_wait_seconds{key,owner} - wait time to acquire
  • mesh_lock_contention_total{key,owner} - contention events

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrLockFailed is returned when lock cannot be acquired
	ErrLockFailed = errors.New("failed to acquire lock")
	// ErrLockNotHeld is returned when trying to release a lock not held
	ErrLockNotHeld = errors.New("lock not held")
	// ErrLockExpired is returned when a lock has expired
	ErrLockExpired = errors.New("lock has expired")
	// ErrInvalidLockDuration is returned for invalid lock durations
	ErrInvalidLockDuration = errors.New("lock duration must be positive")
)

Functions

This section is empty.

Types

type LockResult

type LockResult struct {
	Acquired  bool          // Whether the lock was acquired
	LockID    string        // Unique identifier for this lock instance
	WaitTime  time.Duration // Time waited to acquire the lock
	ExpiresAt time.Time     // When the lock will expire
}

LockResult contains information about a lock operation

type MemoryLock

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

MemoryLock is an in-memory implementation for testing or single-instance deployments

func NewMemoryLock

func NewMemoryLock() *MemoryLock

NewMemoryLock creates a new in-memory lock manager

func (*MemoryLock) Execute

func (m *MemoryLock) Execute(ctx context.Context, key string, ttl time.Duration, fn func() error) error

Execute runs a function while holding an in-memory lock

func (*MemoryLock) Release

func (m *MemoryLock) Release(ctx context.Context, key string) error

Release releases an in-memory lock

func (*MemoryLock) TryAcquire

func (m *MemoryLock) TryAcquire(ctx context.Context, key string, ttl time.Duration) (*LockResult, error)

TryAcquire attempts to acquire an in-memory lock

type MetricsConfig added in v0.1.16

type MetricsConfig struct {
	// EnableWaitTime tracks time waited to acquire locks
	EnableWaitTime bool
	// EnableContention tracks contention events
	EnableContention bool
}

MetricsConfig holds configuration for lock metrics.

type Option added in v0.1.16

type Option func(*RedisLock)

Option configures RedisLock

func WithMetrics added in v0.1.16

func WithMetrics(cfg MetricsConfig) Option

WithMetrics enables metrics collection

type RedisClient

type RedisClient interface {
	SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.BoolCmd
	Del(ctx context.Context, keys ...string) *redis.IntCmd
	Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd
}

RedisClient interface defines the required Redis methods for locking

type RedisLock

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

RedisLock implements distributed locking using Redis

func NewRedisLock

func NewRedisLock(client RedisClient, opts ...Option) *RedisLock

NewRedisLock creates a new Redis-based lock manager

func (*RedisLock) Acquire

func (r *RedisLock) Acquire(ctx context.Context, key string, ttl time.Duration, maxWait time.Duration) (*LockResult, error)

Acquire acquires a lock with automatic retry

func (*RedisLock) Execute

func (r *RedisLock) Execute(ctx context.Context, key string, ttl time.Duration, fn func() error) error

Execute runs a function while holding a lock

func (*RedisLock) ExecuteWithRetry

func (r *RedisLock) ExecuteWithRetry(ctx context.Context, key string, ttl time.Duration, maxRetries int, fn func() error) error

ExecuteWithRetry runs a function while holding a lock, with retry if the function fails

func (*RedisLock) ExtendLock

func (r *RedisLock) ExtendLock(ctx context.Context, key, lockID string, ttl, interval time.Duration) error

ExtendLock automatically extends a lock at regular intervals

func (*RedisLock) GetLockID

func (r *RedisLock) GetLockID(ctx context.Context, key string) (string, error)

GetLockID returns the current lock ID for a key

func (*RedisLock) IsLocked

func (r *RedisLock) IsLocked(ctx context.Context, key string) (bool, error)

IsLocked checks if a lock is currently held

func (*RedisLock) Refresh

func (r *RedisLock) Refresh(ctx context.Context, key, lockID string, ttl time.Duration) error

Refresh extends the lock expiration time

func (*RedisLock) Release

func (r *RedisLock) Release(ctx context.Context, key string) error

Release releases a lock by key

func (*RedisLock) ReleaseWithID

func (r *RedisLock) ReleaseWithID(ctx context.Context, key, lockID string) error

ReleaseWithID releases a lock only if it matches the given lock ID

func (*RedisLock) TryAcquire

func (r *RedisLock) TryAcquire(ctx context.Context, key string, ttl time.Duration) (*LockResult, error)

TryAcquire attempts to acquire a lock without retry

Jump to

Keyboard shortcuts

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