Lock
Mutex and distributed lock abstraction
lock provides named lock instances. The default driver is memory, suitable for same-process mutual exclusion. Use the Redis driver when you need cross-process locking.
Typical use cases include preventing the same scheduled job from running twice, preventing two workers from handling the same order at the same time, or ensuring one section of code runs only once at a time.
Install
go get github.com/duxweb/runa/lock
Install the Redis driver only when needed:
go get github.com/duxweb/runa/lock/redis
Connect to an application
package main
import (
"context"
"time"
"github.com/duxweb/runa"
"github.com/duxweb/runa/lock"
)
func main() {
app := runa.New()
app.Install(lock.Provider(
lock.RegisterLocker("jobs", lock.TTL(30*time.Second), lock.Wait(5*time.Second)),
))
if err := app.Freeze(context.Background()); err != nil {
panic(err)
}
locker := lock.Default().MustOf("jobs")
_ = locker.With(context.Background(), "daily-report", func(ctx context.Context) error {
return nil
})
}
Standalone New usage
registry := lock.New()
locker := registry.MustOf(lock.DefaultName)
lease, ok, err := locker.Try(context.Background(), "job", lock.TTL(time.Minute))
if err != nil {
panic(err)
}
if ok {
defer lease.Release(context.Background())
}
Choosing Try, Wait, and With
| Method | Behavior | Good for |
|---|---|---|
Try |
Attempts immediately and returns ok=false if locked |
Tasks that can be skipped |
Wait |
Waits until the lock is acquired or times out | Tasks that must run but can wait |
With |
Acquires the lock, runs a function, and releases automatically | Most business code |
If you are new to locks, start with With because it is harder to forget releasing the lease.
Config
lock reads lock.lockers.<name> and only applies config to lockers that have already been registered.
[lock.lockers.default]
driver = "memory"
prefix = "lock:"
ttl = "30s"
wait = "5s"
retry_interval = "100ms"
auto_renew = true
| Key | Type | Description |
|---|---|---|
driver |
string | driver name, default memory |
prefix |
string | lock key prefix |
ttl |
duration | lease lifetime |
wait |
duration | maximum wait time for acquiring a lock |
retry_interval |
duration | retry interval while waiting |
auto_renew |
bool | automatically renew during With |
meta |
table | custom metadata |
Drivers
The built-in memory driver is suitable for same-process mutual exclusion. For cross-process or multi-instance deployments, install lock/redis, register the Redis driver with RegisterDriver, then use lock.Use("redis") for the target locker.
import (
goredis "github.com/redis/go-redis/v9"
lockredis "github.com/duxweb/runa/lock/redis"
)
client := goredis.NewClient(&goredis.Options{Addr: "127.0.0.1:6379"})
app.Install(lock.Provider(
lock.RegisterDriver("redis", lockredis.Driver(client, lock.DriverMeta("role", "shared"))),
lock.RegisterLocker("default", lock.Use("redis"), lock.Prefix("runa:lock:")),
))
Common mistakes
Using memory locks for multi-instance mutual exclusion
The memory driver only works inside the current process. Use Redis locks for multi-instance deployments.
Forgetting to release a lock
After manual Try or Wait, call defer lease.Release(ctx). Prefer locker.With(...) when possible.
Setting TTL too short
If the task may run longer than the TTL, enable auto-renewal or use a more realistic TTL.
API quick reference
lock.New()creates a standalone registry.lock.Provider(...)connects to the framework lifecycle.lock.Default()reads*lock.Registryfrom default DI.lock.RegisterDriver(name, driver)registers a driver.lock.RegisterLocker(name, options...)registers a locker.locker.Try(ctx, key, options...)tries to acquire a lock.locker.Wait(ctx, key, options...)waits for a lock.locker.With(ctx, key, fn, options...)runs a function after acquiring a lock and releases it automatically.