1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
// SPDX-License-Identifier: GPL-3.0-or-later
package lock
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestAcquireAndRelease(t *testing.T) {
path := filepath.Join(t.TempDir(), "state", "dl.lock")
l, err := Acquire(context.Background(), path, false)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(path); err != nil {
t.Errorf("lock file missing: %v", err)
}
if b, _ := os.ReadFile(path); !strings.Contains(string(b), fmt.Sprint(os.Getpid())) {
t.Errorf("lock file does not name the holder's pid: %q", b)
}
if err := l.Release(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Error("Release left the lock file behind")
}
if err := l.Release(); err != nil {
t.Errorf("a second Release must be harmless: %v", err)
}
}
func TestAcquireFailsWhenHeldAndNotWaiting(t *testing.T) {
path := filepath.Join(t.TempDir(), "dl.lock")
first, err := Acquire(context.Background(), path, false)
if err != nil {
t.Fatal(err)
}
defer first.Release()
if _, err := Acquire(context.Background(), path, false); !errors.Is(err, ErrHeld) {
t.Fatalf("second Acquire err = %v, want ErrHeld", err)
}
}
func TestAcquireWaitsUntilReleased(t *testing.T) {
path := filepath.Join(t.TempDir(), "dl.lock")
first, err := Acquire(context.Background(), path, false)
if err != nil {
t.Fatal(err)
}
go func() {
time.Sleep(150 * time.Millisecond)
first.Release()
}()
start := time.Now()
second, err := Acquire(context.Background(), path, true)
if err != nil {
t.Fatalf("waiting Acquire failed: %v", err)
}
defer second.Release()
if time.Since(start) < 100*time.Millisecond {
t.Error("Acquire returned before the first holder released")
}
}
// TestAcquireRespectsContextCancellation is fix round 2026-09-12/item 3: a
// waiting Acquire must not ignore an interrupt - a cancelled ctx must return
// promptly with ctx.Err(), not poll forever. The unfixed code HANGS rather
// than fails here, so the wait for Acquire's result is itself bounded with
// its own hard timeout: a regression must fail this test, not hang the
// whole suite.
func TestAcquireRespectsContextCancellation(t *testing.T) {
path := filepath.Join(t.TempDir(), "dl.lock")
held, err := Acquire(context.Background(), path, false)
if err != nil {
t.Fatal(err)
}
defer held.Release()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
result := make(chan error, 1)
start := time.Now()
go func() {
_, err := Acquire(ctx, path, true)
result <- err
}()
select {
case err := <-result:
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("Acquire err = %v, want context.DeadlineExceeded", err)
}
if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
t.Errorf("Acquire took %v to notice cancellation, want well under a second", elapsed)
}
case <-time.After(2 * time.Second):
t.Fatal("Acquire ignored context cancellation and is still blocked")
}
}
// TestStaleLockIsTakenOver: a lock naming a pid that is not running must not
// wedge krino - a machine that lost power mid-run would need manual cleanup.
func TestStaleLockIsTakenOver(t *testing.T) {
path := filepath.Join(t.TempDir(), "dl.lock")
if err := os.WriteFile(path, []byte("pid 4294967000\nstarted 2020-01-01T00:00:00Z\n"), 0o644); err != nil {
t.Fatal(err)
}
l, err := Acquire(context.Background(), path, false)
if err != nil {
t.Fatalf("a stale lock blocked Acquire: %v", err)
}
defer l.Release()
if !l.TookOverStale {
t.Error("the takeover was not reported to the caller")
}
}
|