1
2
3
4
5
6
7
8
9 package lockedfile_test
10
11 import (
12 "fmt"
13 "internal/testenv"
14 "os"
15 "path/filepath"
16 "testing"
17 "time"
18
19 "cmd/go/internal/lockedfile"
20 )
21
22 const (
23 quiescent = 10 * time.Millisecond
24 probablyStillBlocked = 10 * time.Second
25 )
26
27 func mustBlock(t *testing.T, desc string, f func()) (wait func(*testing.T)) {
28 t.Helper()
29
30 done := make(chan struct{})
31 go func() {
32 f()
33 close(done)
34 }()
35
36 timer := time.NewTimer(quiescent)
37 defer timer.Stop()
38 select {
39 case <-done:
40 t.Fatalf("%s unexpectedly did not block", desc)
41 case <-timer.C:
42 }
43
44 return func(t *testing.T) {
45 logTimer := time.NewTimer(quiescent)
46 defer logTimer.Stop()
47
48 select {
49 case <-logTimer.C:
50
51
52
53 t.Helper()
54 t.Logf("%s is unexpectedly still blocked after %v", desc, quiescent)
55
56
57
58
59 <-done
60
61 case <-done:
62 }
63 }
64 }
65
66 func TestMutexExcludes(t *testing.T) {
67 t.Parallel()
68
69 path := filepath.Join(t.TempDir(), "lock")
70 mu := lockedfile.MutexAt(path)
71 t.Logf("mu := MutexAt(_)")
72
73 unlock, err := mu.Lock()
74 if err != nil {
75 t.Fatalf("mu.Lock: %v", err)
76 }
77 t.Logf("unlock, _ := mu.Lock()")
78
79 mu2 := lockedfile.MutexAt(mu.Path)
80 t.Logf("mu2 := MutexAt(mu.Path)")
81
82 wait := mustBlock(t, "mu2.Lock()", func() {
83 unlock2, err := mu2.Lock()
84 if err != nil {
85 t.Errorf("mu2.Lock: %v", err)
86 return
87 }
88 t.Logf("unlock2, _ := mu2.Lock()")
89 t.Logf("unlock2()")
90 unlock2()
91 })
92
93 t.Logf("unlock()")
94 unlock()
95 wait(t)
96 }
97
98 func TestReadWaitsForLock(t *testing.T) {
99 t.Parallel()
100
101 path := filepath.Join(t.TempDir(), "timestamp.txt")
102 f, err := lockedfile.Create(path)
103 if err != nil {
104 t.Fatalf("Create: %v", err)
105 }
106 defer f.Close()
107
108 const (
109 part1 = "part 1\n"
110 part2 = "part 2\n"
111 )
112 _, err = f.WriteString(part1)
113 if err != nil {
114 t.Fatalf("WriteString: %v", err)
115 }
116 t.Logf("WriteString(%q) = <nil>", part1)
117
118 wait := mustBlock(t, "Read", func() {
119 b, err := lockedfile.Read(path)
120 if err != nil {
121 t.Errorf("Read: %v", err)
122 return
123 }
124
125 const want = part1 + part2
126 got := string(b)
127 if got == want {
128 t.Logf("Read(_) = %q", got)
129 } else {
130 t.Errorf("Read(_) = %q, _; want %q", got, want)
131 }
132 })
133
134 _, err = f.WriteString(part2)
135 if err != nil {
136 t.Errorf("WriteString: %v", err)
137 } else {
138 t.Logf("WriteString(%q) = <nil>", part2)
139 }
140 f.Close()
141
142 wait(t)
143 }
144
145 func TestCanLockExistingFile(t *testing.T) {
146 t.Parallel()
147
148 path := filepath.Join(t.TempDir(), "existing.txt")
149 if err := os.WriteFile(path, []byte("ok"), 0777); err != nil {
150 t.Fatalf("os.WriteFile: %v", err)
151 }
152
153 f, err := lockedfile.Edit(path)
154 if err != nil {
155 t.Fatalf("first Edit: %v", err)
156 }
157
158 wait := mustBlock(t, "Edit", func() {
159 other, err := lockedfile.Edit(path)
160 if err != nil {
161 t.Errorf("second Edit: %v", err)
162 }
163 other.Close()
164 })
165
166 f.Close()
167 wait(t)
168 }
169
170
171
172 func TestSpuriousEDEADLK(t *testing.T) {
173
174
175
176
177
178
179
180
181
182
183 testenv.MustHaveExec(t)
184
185 dirVar := t.Name() + "DIR"
186
187 if dir := os.Getenv(dirVar); dir != "" {
188
189 b, err := lockedfile.Edit(filepath.Join(dir, "B"))
190 if err != nil {
191 t.Fatal(err)
192 }
193 defer b.Close()
194
195 if err := os.WriteFile(filepath.Join(dir, "locked"), []byte("ok"), 0666); err != nil {
196 t.Fatal(err)
197 }
198
199
200 a, err := lockedfile.Edit(filepath.Join(dir, "A"))
201
202 if err != nil {
203 t.Fatal(err)
204 }
205 defer a.Close()
206
207
208 return
209 }
210
211 dir := t.TempDir()
212
213
214 a, err := lockedfile.Edit(filepath.Join(dir, "A"))
215 if err != nil {
216 t.Fatal(err)
217 }
218
219 cmd := testenv.Command(t, os.Args[0], "-test.run=^"+t.Name()+"$")
220 cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
221
222 qDone := make(chan struct{})
223 waitQ := mustBlock(t, "Edit A and B in subprocess", func() {
224 out, err := cmd.CombinedOutput()
225 if err != nil {
226 t.Errorf("%v:\n%s", err, out)
227 }
228 close(qDone)
229 })
230
231
232
233 locked:
234 for {
235 if _, err := os.Stat(filepath.Join(dir, "locked")); !os.IsNotExist(err) {
236 break locked
237 }
238 timer := time.NewTimer(1 * time.Millisecond)
239 select {
240 case <-qDone:
241 timer.Stop()
242 break locked
243 case <-timer.C:
244 }
245 }
246
247 waitP2 := mustBlock(t, "Edit B", func() {
248
249 b, err := lockedfile.Edit(filepath.Join(dir, "B"))
250
251 if err != nil {
252 t.Error(err)
253 return
254 }
255
256 b.Close()
257 })
258
259
260 a.Close()
261
262 waitQ(t)
263 waitP2(t)
264 }
265
View as plain text