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 package lockedfile 6 7 import ( 8 "fmt" 9 "os" 10 "sync" 11 ) 12 13 // A Mutex provides mutual exclusion within and across processes by locking a 14 // well-known file. Such a file generally guards some other part of the 15 // filesystem: for example, a Mutex file in a directory might guard access to 16 // the entire tree rooted in that directory. 17 // 18 // Mutex does not implement sync.Locker: unlike a sync.Mutex, a lockedfile.Mutex 19 // can fail to lock (e.g. if there is a permission error in the filesystem). 20 // 21 // Like a sync.Mutex, a Mutex may be included as a field of a larger struct but 22 // must not be copied after first use. The Path field must be set before first 23 // use and must not be change thereafter. 24 type Mutex struct { 25 Path string // The path to the well-known lock file. Must be non-empty. 26 mu sync.Mutex // A redundant mutex. The race detector doesn't know about file locking, so in tests we may need to lock something that it understands. 27 } 28 29 // MutexAt returns a new Mutex with Path set to the given non-empty path. 30 func MutexAt(path string) *Mutex { 31 if path == "" { 32 panic("lockedfile.MutexAt: path must be non-empty") 33 } 34 return &Mutex{Path: path} 35 } 36 37 func (mu *Mutex) String() string { 38 return fmt.Sprintf("lockedfile.Mutex(%s)", mu.Path) 39 } 40 41 // Lock attempts to lock the Mutex. 42 // 43 // If successful, Lock returns a non-nil unlock function: it is provided as a 44 // return-value instead of a separate method to remind the caller to check the 45 // accompanying error. (See https://golang.org/issue/20803.) 46 func (mu *Mutex) Lock() (unlock func(), err error) { 47 if mu.Path == "" { 48 panic("lockedfile.Mutex: missing Path during Lock") 49 } 50 51 // We could use either O_RDWR or O_WRONLY here. If we choose O_RDWR and the 52 // file at mu.Path is write-only, the call to OpenFile will fail with a 53 // permission error. That's actually what we want: if we add an RLock method 54 // in the future, it should call OpenFile with O_RDONLY and will require the 55 // files must be readable, so we should not let the caller make any 56 // assumptions about Mutex working with write-only files. 57 f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0666) 58 if err != nil { 59 return nil, err 60 } 61 mu.mu.Lock() 62 63 return func() { 64 mu.mu.Unlock() 65 f.Close() 66 }, nil 67 } 68