1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5//go:build !js && !plan9 && !wasip1 6 7package filelock_test 8 9import ( 10 "fmt" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "runtime" 15 "testing" 16 "time" 17 18 "cmd/go/internal/lockedfile/internal/filelock" 19) 20 21func lock(t *testing.T, f *os.File) { 22 t.Helper() 23 err := filelock.Lock(f) 24 t.Logf("Lock(fd %d) = %v", f.Fd(), err) 25 if err != nil { 26 t.Fail() 27 } 28} 29 30func rLock(t *testing.T, f *os.File) { 31 t.Helper() 32 err := filelock.RLock(f) 33 t.Logf("RLock(fd %d) = %v", f.Fd(), err) 34 if err != nil { 35 t.Fail() 36 } 37} 38 39func unlock(t *testing.T, f *os.File) { 40 t.Helper() 41 err := filelock.Unlock(f) 42 t.Logf("Unlock(fd %d) = %v", f.Fd(), err) 43 if err != nil { 44 t.Fail() 45 } 46} 47 48func mustTempFile(t *testing.T) (f *os.File, remove func()) { 49 t.Helper() 50 51 base := filepath.Base(t.Name()) 52 f, err := os.CreateTemp("", base) 53 if err != nil { 54 t.Fatalf(`os.CreateTemp("", %q) = %v`, base, err) 55 } 56 t.Logf("fd %d = %s", f.Fd(), f.Name()) 57 58 return f, func() { 59 f.Close() 60 os.Remove(f.Name()) 61 } 62} 63 64func mustOpen(t *testing.T, name string) *os.File { 65 t.Helper() 66 67 f, err := os.OpenFile(name, os.O_RDWR, 0) 68 if err != nil { 69 t.Fatalf("os.Open(%q) = %v", name, err) 70 } 71 72 t.Logf("fd %d = os.Open(%q)", f.Fd(), name) 73 return f 74} 75 76const ( 77 quiescent = 10 * time.Millisecond 78 probablyStillBlocked = 10 * time.Second 79) 80 81func mustBlock(t *testing.T, op string, f *os.File) (wait func(*testing.T)) { 82 t.Helper() 83 84 desc := fmt.Sprintf("%s(fd %d)", op, f.Fd()) 85 86 done := make(chan struct{}) 87 go func() { 88 t.Helper() 89 switch op { 90 case "Lock": 91 lock(t, f) 92 case "RLock": 93 rLock(t, f) 94 default: 95 panic("invalid op: " + op) 96 } 97 close(done) 98 }() 99 100 select { 101 case <-done: 102 t.Fatalf("%s unexpectedly did not block", desc) 103 return nil 104 105 case <-time.After(quiescent): 106 t.Logf("%s is blocked (as expected)", desc) 107 return func(t *testing.T) { 108 t.Helper() 109 select { 110 case <-time.After(probablyStillBlocked): 111 t.Fatalf("%s is unexpectedly still blocked", desc) 112 case <-done: 113 } 114 } 115 } 116} 117 118func TestLockExcludesLock(t *testing.T) { 119 t.Parallel() 120 121 f, remove := mustTempFile(t) 122 defer remove() 123 124 other := mustOpen(t, f.Name()) 125 defer other.Close() 126 127 lock(t, f) 128 lockOther := mustBlock(t, "Lock", other) 129 unlock(t, f) 130 lockOther(t) 131 unlock(t, other) 132} 133 134func TestLockExcludesRLock(t *testing.T) { 135 t.Parallel() 136 137 f, remove := mustTempFile(t) 138 defer remove() 139 140 other := mustOpen(t, f.Name()) 141 defer other.Close() 142 143 lock(t, f) 144 rLockOther := mustBlock(t, "RLock", other) 145 unlock(t, f) 146 rLockOther(t) 147 unlock(t, other) 148} 149 150func TestRLockExcludesOnlyLock(t *testing.T) { 151 t.Parallel() 152 153 f, remove := mustTempFile(t) 154 defer remove() 155 rLock(t, f) 156 157 f2 := mustOpen(t, f.Name()) 158 defer f2.Close() 159 160 doUnlockTF := false 161 switch runtime.GOOS { 162 case "aix", "solaris": 163 // When using POSIX locks (as on Solaris), we can't safely read-lock the 164 // same inode through two different descriptors at the same time: when the 165 // first descriptor is closed, the second descriptor would still be open but 166 // silently unlocked. So a second RLock must block instead of proceeding. 167 lockF2 := mustBlock(t, "RLock", f2) 168 unlock(t, f) 169 lockF2(t) 170 default: 171 rLock(t, f2) 172 doUnlockTF = true 173 } 174 175 other := mustOpen(t, f.Name()) 176 defer other.Close() 177 lockOther := mustBlock(t, "Lock", other) 178 179 unlock(t, f2) 180 if doUnlockTF { 181 unlock(t, f) 182 } 183 lockOther(t) 184 unlock(t, other) 185} 186 187func TestLockNotDroppedByExecCommand(t *testing.T) { 188 testenv.MustHaveExec(t) 189 190 f, remove := mustTempFile(t) 191 defer remove() 192 193 lock(t, f) 194 195 other := mustOpen(t, f.Name()) 196 defer other.Close() 197 198 // Some kinds of file locks are dropped when a duplicated or forked file 199 // descriptor is unlocked. Double-check that the approach used by os/exec does 200 // not accidentally drop locks. 201 cmd := testenv.Command(t, os.Args[0], "-test.run=^$") 202 if err := cmd.Run(); err != nil { 203 t.Fatalf("exec failed: %v", err) 204 } 205 206 lockOther := mustBlock(t, "Lock", other) 207 unlock(t, f) 208 lockOther(t) 209 unlock(t, other) 210} 211