github.com/searKing/golang/go@v1.2.117/sync/filelock/filelock_fcntl.go (about) 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 aix || (solaris && !illumos) 6 7 // This code implements the filelock API using POSIX 'fcntl' locks, which attach 8 // to an (inode, process) pair rather than a file descriptor. To avoid unlocking 9 // files prematurely when the same file is opened through different descriptors, 10 // we allow only one read-lock at a time. 11 // 12 // Most platforms provide some alternative API, such as an 'flock' system call 13 // or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and 14 // does not require per-inode bookkeeping in the application. 15 16 // Copied from go/gc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go 17 18 package filelock 19 20 import ( 21 "errors" 22 "io" 23 "io/fs" 24 "math/rand" 25 "sync" 26 "syscall" 27 "time" 28 ) 29 30 type lockType int16 31 32 const ( 33 readLock lockType = syscall.F_RDLCK 34 writeLock lockType = syscall.F_WRLCK 35 ) 36 37 type inode = uint64 // type of syscall.Stat_t.Ino 38 39 type inodeLock struct { 40 owner File 41 queue []<-chan File 42 } 43 44 var ( 45 mu sync.Mutex 46 inodes = map[File]inode{} 47 locks = map[inode]inodeLock{} 48 ) 49 50 func lock(f File, lt lockType, try bool) (err error) { 51 // POSIX locks apply per inode and process, and the lock for an inode is 52 // released when *any* descriptor for that inode is closed. So we need to 53 // synchronize access to each inode internally, and must serialize lock and 54 // unlock calls that refer to the same inode through different descriptors. 55 fi, err := f.Stat() 56 if err != nil { 57 return err 58 } 59 ino := fi.Sys().(*syscall.Stat_t).Ino 60 61 mu.Lock() 62 if i, dup := inodes[f]; dup && i != ino { 63 mu.Unlock() 64 return &fs.PathError{ 65 Op: lt.String(), 66 Path: f.Name(), 67 Err: errors.New("inode for file changed since last Lock or RLock"), 68 } 69 } 70 inodes[f] = ino 71 72 var wait chan File 73 l := locks[ino] 74 if l.owner == f { 75 // This file already owns the lock, but the call may change its lock type. 76 } else if l.owner == nil { 77 // No owner: it's ours now. 78 l.owner = f 79 } else { 80 // Already owned: add a channel to wait on. 81 wait = make(chan File) 82 l.queue = append(l.queue, wait) 83 } 84 locks[ino] = l 85 mu.Unlock() 86 87 if wait != nil { 88 wait <- f 89 } 90 91 // Spurious EDEADLK errors arise on platforms that compute deadlock graphs at 92 // the process, rather than thread, level. Consider processes P and Q, with 93 // threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be 94 // reported as a deadlock on systems that consider only process granularity: 95 // 96 // P.1 locks file A. 97 // Q.3 locks file B. 98 // Q.3 blocks on file A. 99 // P.2 blocks on file B. (This is erroneously reported as a deadlock.) 100 // P.1 unlocks file A. 101 // Q.3 unblocks and locks file A. 102 // Q.3 unlocks files A and B. 103 // P.2 unblocks and locks file B. 104 // P.2 unlocks file B. 105 // 106 // These spurious errors were observed in practice on AIX and Solaris in 107 // cmd/go: see https://golang.org/issue/32817. 108 // 109 // We work around this bug by treating EDEADLK as always spurious. If there 110 // really is a lock-ordering bug between the interacting processes, it will 111 // become a livelock instead, but that's not appreciably worse than if we had 112 // a proper flock implementation (which generally does not even attempt to 113 // diagnose deadlocks). 114 // 115 // In the above example, that changes the trace to: 116 // 117 // P.1 locks file A. 118 // Q.3 locks file B. 119 // Q.3 blocks on file A. 120 // P.2 spuriously fails to lock file B and goes to sleep. 121 // P.1 unlocks file A. 122 // Q.3 unblocks and locks file A. 123 // Q.3 unlocks files A and B. 124 // P.2 wakes up and locks file B. 125 // P.2 unlocks file B. 126 // 127 // We know that the retry loop will not introduce a *spurious* livelock 128 // because, according to the POSIX specification, EDEADLK is only to be 129 // returned when “the lock is blocked by a lock from another process”. 130 // If that process is blocked on some lock that we are holding, then the 131 // resulting livelock is due to a real deadlock (and would manifest as such 132 // when using, for example, the flock implementation of this package). 133 // If the other process is *not* blocked on some other lock that we are 134 // holding, then it will eventually release the requested lock. 135 136 nextSleep := 1 * time.Millisecond 137 const maxSleep = 500 * time.Millisecond 138 for { 139 err = setlkw(f.Fd(), lt) 140 if err != syscall.EDEADLK { 141 break 142 } 143 if try { 144 break 145 } 146 time.Sleep(nextSleep) 147 148 nextSleep += nextSleep 149 if nextSleep > maxSleep { 150 nextSleep = maxSleep 151 } 152 // Apply 10% jitter to avoid synchronizing collisions when we finally unblock. 153 nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep)) 154 } 155 156 if err != nil && err != syscall.EDEADLK { 157 unlock(f) 158 return &fs.PathError{ 159 Op: lt.String(), 160 Path: f.Name(), 161 Err: err, 162 } 163 } 164 165 return nil 166 } 167 168 func unlock(f File) error { 169 var owner File 170 171 mu.Lock() 172 ino, ok := inodes[f] 173 if ok { 174 owner = locks[ino].owner 175 } 176 mu.Unlock() 177 178 if owner != f { 179 panic("unlock called on a file that is not locked") 180 } 181 182 err := setlkw(f.Fd(), syscall.F_UNLCK) 183 184 mu.Lock() 185 l := locks[ino] 186 if len(l.queue) == 0 { 187 // No waiters: remove the map entry. 188 delete(locks, ino) 189 } else { 190 // The first waiter is sending us their file now. 191 // Receive it and update the queue. 192 l.owner = <-l.queue[0] 193 l.queue = l.queue[1:] 194 locks[ino] = l 195 } 196 delete(inodes, f) 197 mu.Unlock() 198 199 return err 200 } 201 202 // setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd. 203 func setlkw(fd uintptr, lt lockType) error { 204 for { 205 err := syscall.FcntlFlock(fd, syscall.F_SETLKW, &syscall.Flock_t{ 206 Type: int16(lt), 207 Whence: io.SeekStart, 208 Start: 0, 209 Len: 0, // All bytes. 210 }) 211 if err != syscall.EINTR { 212 return err 213 } 214 } 215 }