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 dirVar := t.Name() + "DIR"
184
185 if dir := os.Getenv(dirVar); dir != "" {
186
187 b, err := lockedfile.Edit(filepath.Join(dir, "B"))
188 if err != nil {
189 t.Fatal(err)
190 }
191 defer b.Close()
192
193 if err := os.WriteFile(filepath.Join(dir, "locked"), []byte("ok"), 0666); err != nil {
194 t.Fatal(err)
195 }
196
197
198 a, err := lockedfile.Edit(filepath.Join(dir, "A"))
199
200 if err != nil {
201 t.Fatal(err)
202 }
203 defer a.Close()
204
205
206 return
207 }
208
209 dir := t.TempDir()
210
211
212 a, err := lockedfile.Edit(filepath.Join(dir, "A"))
213 if err != nil {
214 t.Fatal(err)
215 }
216
217 cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^"+t.Name()+"$")
218 cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
219
220 qDone := make(chan struct{})
221 waitQ := mustBlock(t, "Edit A and B in subprocess", func() {
222 out, err := cmd.CombinedOutput()
223 if err != nil {
224 t.Errorf("%v:\n%s", err, out)
225 }
226 close(qDone)
227 })
228
229
230
231 locked:
232 for {
233 if _, err := os.Stat(filepath.Join(dir, "locked")); !os.IsNotExist(err) {
234 break locked
235 }
236 timer := time.NewTimer(1 * time.Millisecond)
237 select {
238 case <-qDone:
239 timer.Stop()
240 break locked
241 case <-timer.C:
242 }
243 }
244
245 waitP2 := mustBlock(t, "Edit B", func() {
246
247 b, err := lockedfile.Edit(filepath.Join(dir, "B"))
248
249 if err != nil {
250 t.Error(err)
251 return
252 }
253
254 b.Close()
255 })
256
257
258 a.Close()
259
260 waitQ(t)
261 waitP2(t)
262 }
263
View as plain text