package keyedlock import ( "context" "errors" "sync" "sync/atomic" "testing" "time" ) func lockCount(k *Locks) int { k.mu.Lock() defer k.mu.Unlock() return len(k.locks) } func TestKeyedLocks_entryRemovedWhenUncontended(t *testing.T) { t.Parallel() k := New() release, err := k.Acquire(context.Background(), "a") if err != nil { t.Fatalf("acquire error = %v", err) } if lockCount(k) != 1 { t.Errorf("entries while held = %d, want 1", lockCount(k)) } release() if lockCount(k) != 0 { t.Errorf("entries after release = %d, want 0", lockCount(k)) } if _, err := k.Acquire(context.Background(), "a"); err != nil { t.Fatalf("acquire after release error = %v", err) } } func TestKeyedLocks_waitersShareOneLock(t *testing.T) { t.Parallel() k := New() const n = 20 var inside, overlaps atomic.Int32 var wg sync.WaitGroup for range n { wg.Go(func() { release, err := k.Acquire(context.Background(), "shared") if err != nil { t.Errorf("acquire error = %v", err) return } if inside.Add(1) > 1 { overlaps.Add(1) } time.Sleep(time.Millisecond) inside.Add(-1) release() }) } wg.Wait() if overlaps.Load() != 0 { t.Errorf("%d holders overlapped on one key", overlaps.Load()) } if lockCount(k) != 0 { t.Errorf("entries after every holder released = %d, want 0", lockCount(k)) } } func TestKeyedLocks_waiterGivesUpWithContext(t *testing.T) { t.Parallel() k := New() release, err := k.Acquire(context.Background(), "a") if err != nil { t.Fatalf("acquire error = %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) defer cancel() start := time.Now() if _, err := k.Acquire(ctx, "a"); !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("waiter error = %v, want DeadlineExceeded", err) } if time.Since(start) > time.Second { t.Errorf("waiter took %v, want to give up with its ctx", time.Since(start)) } if lockCount(k) != 1 { t.Errorf("entries with the holder still in = %d, want 1: the waiter's exit must not drop the holder's entry", lockCount(k)) } release() if lockCount(k) != 0 { t.Errorf("entries after the holder released = %d, want 0", lockCount(k)) } }