lock
API
lock
packageAPI reference for the lock
package.
Imports
(5)
I
interface
Lease
Locker defines the interface for distributed or local locks.
core/lock/locker.go:18-21
type Lease interface
Example
// Assuming a RedisLocker implementation
locker := NewRedisLocker(redisClient)
lease, err := locker.Acquire(ctx, "my-resource", 5*time.Second)
if err == nil {
defer lease.Release(ctx)
}
Methods
S
InMemoryLocker
InMemoryLocker implements Locker using in-process mutexes.
core/lock/memory.go:10-14
type InMemoryLocker struct
Methods
Acquire
Method
Parameters
Returns
error
func (*InMemoryLocker) Acquire(ctx context.Context, key string, ttl time.Duration) (Lease, error)
{
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
lease, ok, err := l.TryLock(ctx, key, ttl)
if err != nil {
return nil, err
}
if ok {
return lease, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
}
}
}
TryLock
Method
Parameters
Returns
func (*InMemoryLocker) TryLock(ctx context.Context, key string, ttl time.Duration) (Lease, bool, error)
{
if err := ctx.Err(); err != nil {
return nil, false, err
}
l.mu.Lock()
if _, exists := l.locks[key]; exists {
l.mu.Unlock()
return nil, false, nil
}
l.next++
owner := l.next
state := &lockState{owner: owner}
l.locks[key] = state
if ttl > 0 {
state.timer = time.AfterFunc(ttl, func() {
l.release(key, owner)
})
}
l.mu.Unlock()
return &memoryLease{locker: l, key: key, owner: owner}, true, nil
}
release
Method
Parameters
key
string
owner
uint64
func (*InMemoryLocker) release(key string, owner uint64)
{
l.mu.Lock()
defer l.mu.Unlock()
state, ok := l.locks[key]
if !ok || state.owner != owner {
return
}
if state.timer != nil {
state.timer.Stop()
}
delete(l.locks, key)
}
Fields
| Name | Type | Description |
|---|---|---|
| mu | sync.Mutex | |
| locks | map[string]*lockState | |
| next | uint64 |
S
struct
lockState
core/lock/memory.go:16-19
type lockState struct
Fields
| Name | Type | Description |
|---|---|---|
| owner | uint64 | |
| timer | *time.Timer |
S
struct
memoryLease
core/lock/memory.go:21-26
type memoryLease struct
Methods
Release
Method
Parameters
Returns
error
func (*memoryLease) Release(_ context.Context) error
{
l.once.Do(func() {
l.locker.release(l.key, l.owner)
})
return nil
}
Fields
| Name | Type | Description |
|---|---|---|
| locker | *InMemoryLocker | |
| key | string | |
| owner | uint64 | |
| once | sync.Once |
F
function
NewInMemoryLocker
NewInMemoryLocker creates a new in-process Locker.
Returns
core/lock/memory.go:40-44
func NewInMemoryLocker() *InMemoryLocker
{
return &InMemoryLocker{
locks: make(map[string]*lockState),
}
}
F
function
TestInMemoryLocker_TryLock
Parameters
t
core/lock/memory_test.go:10-28
func TestInMemoryLocker_TryLock(t *testing.T)
{
l := NewInMemoryLocker()
lease, ok, err := l.TryLock(context.Background(), "key1", 0)
if err != nil {
t.Fatalf("TryLock failed: %v", err)
}
if !ok {
t.Fatal("expected lock acquired")
}
defer lease.Release(context.Background())
_, ok, err = l.TryLock(context.Background(), "key1", 0)
if err != nil {
t.Fatalf("TryLock second call: %v", err)
}
if ok {
t.Fatal("expected lock not acquired (already held)")
}
}
F
function
TestInMemoryLocker_Release
Parameters
t
core/lock/memory_test.go:30-46
func TestInMemoryLocker_Release(t *testing.T)
{
l := NewInMemoryLocker()
lease, _, _ := l.TryLock(context.Background(), "k", 0)
err := lease.Release(context.Background())
if err != nil {
t.Fatalf("Release failed: %v", err)
}
_, ok, err := l.TryLock(context.Background(), "k", 0)
if err != nil {
t.Fatalf("TryLock after release: %v", err)
}
if !ok {
t.Fatal("expected lock acquired after release")
}
}
F
function
TestInMemoryLocker_Acquire
Parameters
t
core/lock/memory_test.go:48-69
func TestInMemoryLocker_Acquire(t *testing.T)
{
l := NewInMemoryLocker()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
lease, _, _ := l.TryLock(context.Background(), "shared", 0)
time.Sleep(50 * time.Millisecond)
lease.Release(context.Background())
}()
time.Sleep(5 * time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err := l.Acquire(ctx, "shared", 0)
if err != nil {
t.Fatalf("Acquire failed: %v", err)
}
wg.Wait()
}
F
function
TestInMemoryLocker_AcquireContextCancel
Parameters
t
core/lock/memory_test.go:71-83
func TestInMemoryLocker_AcquireContextCancel(t *testing.T)
{
l := NewInMemoryLocker()
lease, _, _ := l.TryLock(context.Background(), "blocked", 0)
defer lease.Release(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_, err := l.Acquire(ctx, "blocked", 0)
if err == nil {
t.Fatal("expected error from cancelled context")
}
}
F
function
TestInMemoryLocker_DifferentKeys
Parameters
t
core/lock/memory_test.go:85-94
func TestInMemoryLocker_DifferentKeys(t *testing.T)
{
l := NewInMemoryLocker()
lease1, ok1, _ := l.TryLock(context.Background(), "a", 0)
lease2, ok2, _ := l.TryLock(context.Background(), "b", 0)
defer lease1.Release(context.Background())
defer lease2.Release(context.Background())
if !ok1 || !ok2 {
t.Fatal("different keys should not conflict")
}
}
F
function
TestInMemoryLocker_TTLReleasesLock
Parameters
t
core/lock/memory_test.go:96-110
func TestInMemoryLocker_TTLReleasesLock(t *testing.T)
{
l := NewInMemoryLocker()
_, ok, err := l.TryLock(context.Background(), "expiring", 10*time.Millisecond)
if err != nil || !ok {
t.Fatalf("TryLock() = %v, %v, want true, nil", ok, err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
lease, err := l.Acquire(ctx, "expiring", 0)
if err != nil {
t.Fatalf("Acquire() after TTL: %v", err)
}
defer lease.Release(context.Background())
}
F
function
TestInMemoryLocker_ExpiredOwnerDoesNotReleaseNewLock
Parameters
t
core/lock/memory_test.go:112-139
func TestInMemoryLocker_ExpiredOwnerDoesNotReleaseNewLock(t *testing.T)
{
l := NewInMemoryLocker()
first, ok, err := l.TryLock(context.Background(), "reused", 40*time.Millisecond)
if err != nil || !ok {
t.Fatalf("first TryLock() = %v, %v", ok, err)
}
if err := first.Release(context.Background()); err != nil {
t.Fatal(err)
}
second, ok, err := l.TryLock(context.Background(), "reused", 0)
if err != nil || !ok {
t.Fatalf("second TryLock() = %v, %v", ok, err)
}
if err := first.Release(context.Background()); err != nil {
t.Fatal(err)
}
time.Sleep(60 * time.Millisecond)
_, ok, err = l.TryLock(context.Background(), "reused", 0)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("expired owner released a newer lock")
}
if err := second.Release(context.Background()); err != nil {
t.Fatal(err)
}
}