github.com/go4org/go4@v0.0.0-20200104003542-c7e774b10ea0/lock/lock.go (about) 1 /* 2 Copyright 2013 The Go Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package lock is a file locking library. 18 package lock // import "go4.org/lock" 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "io" 24 "os" 25 "path/filepath" 26 "sync" 27 ) 28 29 // Lock locks the given file, creating the file if necessary. If the 30 // file already exists, it must have zero size or an error is returned. 31 // The lock is an exclusive lock (a write lock), but locked files 32 // should neither be read from nor written to. Such files should have 33 // zero size and only exist to co-ordinate ownership across processes. 34 // 35 // A nil Closer is returned if an error occurred. Otherwise, close that 36 // Closer to release the lock. 37 // 38 // On Linux, FreeBSD and OSX, a lock has the same semantics as fcntl(2)'s 39 // advisory locks. In particular, closing any other file descriptor for the 40 // same file will release the lock prematurely. 41 // 42 // Attempting to lock a file that is already locked by the current process 43 // has undefined behavior. 44 // 45 // On other operating systems, lock will fallback to using the presence and 46 // content of a file named name + '.lock' to implement locking behavior. 47 func Lock(name string) (io.Closer, error) { 48 abs, err := filepath.Abs(name) 49 if err != nil { 50 return nil, err 51 } 52 lockmu.Lock() 53 defer lockmu.Unlock() 54 if locked[abs] { 55 return nil, fmt.Errorf("file %q already locked", abs) 56 } 57 58 c, err := lockFn(abs) 59 if err != nil { 60 return nil, fmt.Errorf("cannot acquire lock: %v", err) 61 } 62 locked[abs] = true 63 return c, nil 64 } 65 66 var lockFn = lockPortable 67 68 // lockPortable is a portable version not using fcntl. Doesn't handle crashes as gracefully, 69 // since it can leave stale lock files. 70 func lockPortable(name string) (io.Closer, error) { 71 fi, err := os.Stat(name) 72 if err == nil && fi.Size() > 0 { 73 st := portableLockStatus(name) 74 switch st { 75 case statusLocked: 76 return nil, fmt.Errorf("file %q already locked", name) 77 case statusStale: 78 os.Remove(name) 79 case statusInvalid: 80 return nil, fmt.Errorf("can't Lock file %q: has invalid contents", name) 81 } 82 } 83 f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666) 84 if err != nil { 85 return nil, fmt.Errorf("failed to create lock file %s %v", name, err) 86 } 87 if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil { 88 return nil, fmt.Errorf("cannot write owner pid: %v", err) 89 } 90 return &unlocker{ 91 f: f, 92 abs: name, 93 portable: true, 94 }, nil 95 } 96 97 type lockStatus int 98 99 const ( 100 statusInvalid lockStatus = iota 101 statusLocked 102 statusUnlocked 103 statusStale 104 ) 105 106 type pidLockMeta struct { 107 OwnerPID int 108 } 109 110 func portableLockStatus(path string) lockStatus { 111 f, err := os.Open(path) 112 if err != nil { 113 return statusUnlocked 114 } 115 defer f.Close() 116 var meta pidLockMeta 117 if json.NewDecoder(f).Decode(&meta) != nil { 118 return statusInvalid 119 } 120 if meta.OwnerPID == 0 { 121 return statusInvalid 122 } 123 p, err := os.FindProcess(meta.OwnerPID) 124 if err != nil { 125 // e.g. on Windows 126 return statusStale 127 } 128 // On unix, os.FindProcess always is true, so we have to send 129 // it a signal to see if it's alive. 130 if signalZero != nil { 131 if p.Signal(signalZero) != nil { 132 return statusStale 133 } 134 } 135 return statusLocked 136 } 137 138 var signalZero os.Signal // nil or set by lock_sigzero.go 139 140 var ( 141 lockmu sync.Mutex 142 locked = map[string]bool{} // abs path -> true 143 ) 144 145 type unlocker struct { 146 portable bool 147 f *os.File 148 abs string 149 // once guards the close method call. 150 once sync.Once 151 // err holds the error returned by Close. 152 err error 153 } 154 155 func (u *unlocker) Close() error { 156 u.once.Do(u.close) 157 return u.err 158 } 159 160 func (u *unlocker) close() { 161 lockmu.Lock() 162 defer lockmu.Unlock() 163 delete(locked, u.abs) 164 165 if u.portable { 166 // In the portable lock implementation, it's 167 // important to close before removing because 168 // Windows won't allow us to remove an open 169 // file. 170 if err := u.f.Close(); err != nil { 171 u.err = err 172 } 173 if err := os.Remove(u.abs); err != nil { 174 // Note that if both Close and Remove fail, 175 // we care more about the latter than the former 176 // so we'll return that error. 177 u.err = err 178 } 179 return 180 } 181 // In other implementatioons, it's nice for us to clean up. 182 // If we do do this, though, it needs to be before the 183 // u.f.Close below. 184 os.Remove(u.abs) 185 u.err = u.f.Close() 186 }