redislock

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: MIT Imports: 7 Imported by: 0

README

redis-lock

A small, correct distributed lock for Go, built on Redis.

Go Reference CI

Use it when only one worker should act at a time: a cron that must run on a single instance, a job processed exactly once, a record updated without a race.

Why not just SETNX + DEL?

The naive lock is unsafe:

  1. Worker A acquires the lock with a TTL.
  2. A's work runs longer than the TTL — the lock expires.
  3. Worker B acquires the now-free lock.
  4. A finishes and runs DEL — deleting B's lock.

This library stores a unique token per lock and releases/extends with a Lua script that acts only if the token still matches. A holder whose lock expired can never delete or extend someone else's lock.

Install

go get github.com/YusufDrymz/redis-lock

Usage

locker := redislock.New(redisClient)

lock, err := locker.TryLock(ctx, "cron:report", 30*time.Second)
if errors.Is(err, redislock.ErrNotAcquired) {
    return // someone else holds it
}
defer lock.Unlock(ctx)

// long job? renew while you work:
lock.Extend(ctx, 30*time.Second)

Lock blocks (with jittered retries) until the key is free or the context ends:

lock, err := locker.Lock(ctx, "cron:report", 30*time.Second)

Try it

The tests need no Redis — they run against an in-memory fake (miniredis), so you can clone and run them with zero setup:

go test ./...

To run the example or use the lock for real you need a Redis instance. The quickest way is Docker:

docker run --rm -p 6379:6379 redis
go run ./examples

API

Method Description
New(client, ...Option) Locker over a redis.UniversalClient
TryLock(ctx, key, ttl) Acquire once, or ErrNotAcquired
Lock(ctx, key, ttl) Block until acquired or ctx done
(*Lock) Unlock(ctx) Release if still owned, else ErrNotHeld
(*Lock) Extend(ctx, ttl) Renew TTL if still owned
(*Lock) Token() The lock's unique token

WithRetryInterval(d) tunes the blocking retry cadence (default 100ms).

Caveats

This is a single-instance lock and relies on Redis key expiry. It is correct for the common "only one worker at a time" case. It is not a Redlock multi-node consensus lock, and like all TTL locks it cannot by itself prevent a paused process from acting after its lock expired — pair it with fencing tokens if you need that guarantee.

🇹🇷 Türkçe

Go için küçük ve doğru bir Redis distributed lock. Sıfır gereksiz bağımlılık (yalnızca go-redis).

Neden SETNX + DEL yetmez? İşin TTL'i aşarsa lock expire olur, başka bir worker onu alır, sen DEL yapınca onun lock'unu silersin. Bu kütüphane her lock'a benzersiz bir token verir; release/extend işlemini token hâlâ eşleşiyorsa çalışan bir Lua script ile yapar. Süresi dolmuş bir sahip, başkasının lock'unu asla silemez/uzatamaz.

Kurulum: go get github.com/YusufDrymz/redis-lock

locker := redislock.New(redisClient)
lock, err := locker.TryLock(ctx, "cron:report", 30*time.Second)
if errors.Is(err, redislock.ErrNotAcquired) { return }
defer lock.Unlock(ctx)
lock.Extend(ctx, 30*time.Second) // uzun iş için yenile

Not: Bu tek-instance bir lock'tur (Redlock değil). TTL tabanlı lock'ların doğası gereği, duraklayan bir process'in lock süresi dolduktan sonra iş yapmasını tek başına engelleyemez; bu garanti gerekiyorsa fencing token ile birlikte kullan.

License

MIT — see LICENSE.

Documentation

Overview

Package redislock is a small, correct distributed lock built on Redis.

Acquiring sets a key to a unique random token with NX and a TTL. Releasing and extending run Lua scripts that act only if the token still matches. So a lock whose TTL expired (and was meanwhile taken by someone else) can never be released or extended by its previous holder.

A naive SETNX + DEL lock does not have this property: if your work outlives the TTL, the lock expires, another worker acquires it, and your DEL deletes *their* lock. The token + Lua compare-and-delete here prevents exactly that.

Index

Constants

This section is empty.

Variables

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

ErrNotAcquired is returned when a lock cannot be obtained. ErrNotHeld is returned when releasing or extending a lock we no longer own.

Functions

This section is empty.

Types

type Lock

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

Lock is a held lock identified by a unique token.

func (*Lock) Extend

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

Extend renews the lock's TTL, but only if it is still ours.

func (*Lock) Key

func (lk *Lock) Key() string

Key returns the locked key.

func (*Lock) Token

func (lk *Lock) Token() string

Token returns the lock's unique token.

func (*Lock) Unlock

func (lk *Lock) Unlock(ctx context.Context) error

Unlock releases the lock, but only if it is still ours. It returns ErrNotHeld if the lock had expired and was taken by someone else.

type Locker

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

Locker acquires locks against a Redis client.

func New

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

New returns a Locker using the given Redis client.

func (*Locker) Lock

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

Lock blocks until key is acquired or ctx is done, retrying with a jittered interval. It returns ErrNotAcquired if ctx ends first.

func (*Locker) TryLock

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

TryLock attempts to acquire key once. It returns ErrNotAcquired if the key is already held.

type Option

type Option func(*config)

Option configures a Locker.

func WithRetryInterval

func WithRetryInterval(d time.Duration) Option

WithRetryInterval sets the base wait between acquisition attempts in the blocking Lock call (jitter is added on top). Default is 100ms.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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