MCPcopy
hub / github.com/bsm/redislock

github.com/bsm/redislock @v0.10.0 sqlite

repository ↗ · DeepWiki ↗ · release v0.10.0 ↗
69 symbols 307 edges 5 files 24 documented · 35%
README

redislock

Test GoDoc License

Simplified distributed locking implementation using Redis. For more information, please see examples.

Documentation

Full documentation is available on GoDoc

Examples

import (
  "context"
  "fmt"
  "log"
  "time"

  "github.com/bsm/redislock"
  "github.com/redis/go-redis/v9"
)

func main() {
    // Connect to redis.
    client := redis.NewClient(&redis.Options{
        Network:    "tcp",
        Addr:       "127.0.0.1:6379",
    })
    defer client.Close()

    // Create a new lock client.
    locker := redislock.New(client)

    ctx := context.Background()

    // Try to obtain lock.
    lock, err := locker.Obtain(ctx, "my-key", 100*time.Millisecond, nil)
    if err == redislock.ErrNotObtained {
        fmt.Println("Could not obtain lock!")
        return
    } else if err != nil {
        log.Fatalln(err)
        return
    }

    // Don't forget to defer Release.
    defer lock.Release(ctx)
    fmt.Println("I have a lock!")

    // Sleep and check the remaining TTL.
    time.Sleep(50 * time.Millisecond)
    if ttl, err := lock.TTL(ctx); err != nil {
        log.Fatalln(err)
    } else if ttl > 0 {
        fmt.Println("Yay, I still have my lock!")
    }

    // Extend my lock.
    if err := lock.Refresh(ctx, 100*time.Millisecond, nil); err != nil {
        log.Fatalln(err)
    }

    // Sleep a little longer, then check.
    time.Sleep(100 * time.Millisecond)
    if ttl, err := lock.TTL(ctx); err != nil {
        log.Fatalln(err)
    } else if ttl == 0 {
        fmt.Println("Now, my lock has expired!")
    }

}

External watchdog

redislock deliberately does not bundle a built-in watchdog goroutine: refresh cadence and error handling are application concerns (log and continue, retry, cancel the protected work, page someone, etc.). If you need a long-running lock that outlives its initial TTL, drop a small ticker next to your work and let it call Refresh for you:

func watchdog() {
    client := redis.NewClient(&redis.Options{Network: "tcp", Addr: "127.0.0.1:6379"})
    defer client.Close()

    locker := redislock.New(client)

    ctx := context.Background()

    // Obtain a lock with a 30s TTL.
    const ttl = 30 * time.Second
    lock, err := locker.Obtain(ctx, "my-key", ttl, nil)
    if err != nil {
        log.Fatalln(err)
    }
    defer lock.Release(context.Background())

    // Start a watchdog that refreshes the lock every ttl/3. The work context
    // is cancelled if a refresh fails so the protected work can abort.
    workCtx, cancel := context.WithCancel(ctx)
    defer cancel()

    go func() {
        t := time.NewTicker(ttl / 3)
        defer t.Stop()
        for {
            select {
            case <-workCtx.Done():
                return
            case <-t.C:
                if err := lock.Refresh(workCtx, ttl, nil); err != nil {
                    log.Printf("lock refresh failed: %v", err)
                    cancel()
                    return
                }
            }
        }
    }()

    // ... do work using workCtx ...
    fmt.Println("I have a lock!")
}

A few notes:

  • Pick interval ≈ ttl/3 so a single transient redis blip still leaves room to retry before the lock expires.
  • A Refresh error of redislock.ErrNotObtained means the lock was lost (expired or stolen) and is usually fatal for the protected work; cancel the work context so the caller stops.
  • Release stays with the caller, so ownership is explicit.

Extension points exported contracts — how you extend this code

RetryStrategy (Interface)
RetryStrategy allows to customise the lock retry strategy. [2 implementers]
retry.go
RedisClient (Interface)
RedisClient is a minimal client interface.
redislock.go

Core symbols most depended-on inside this repo

Release
called by 31
redislock.go
Obtain
called by 14
redislock.go
LinearBackoff
called by 11
retry.go
Obtain
called by 10
redislock.go
New
called by 9
redislock.go
Refresh
called by 8
redislock.go
LimitRetry
called by 8
retry.go
ObtainMulti
called by 6
redislock.go

Shape

Function 37
Method 22
Struct 7
Interface 2
TypeAlias 1

Languages

Go100%

Modules by API surface

redislock_test.go27 symbols
redislock.go22 symbols
retry.go12 symbols
retry_test.go4 symbols
example_test.go4 symbols

Dependencies from manifests, versioned

github.com/cespare/xxhash/v2v2.3.0 · 1×
go.uber.org/atomicv1.11.0 · 1×

For agents

$ claude mcp add redislock \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact