1 // Package portlock is a simple mutex for use between processes to protect a shared resource 2 package portlock // import "vimagination.zapto.org/portlock" 3 4 import ( 5 "io" 6 "net" 7 "sync" 8 ) 9 10 // Mutex is a mutual exclusion lock that can be used across different processes 11 type mutex struct { 12 addr string 13 14 mu sync.Mutex 15 l io.Closer 16 } 17 18 var readBuf [1]byte 19 20 // Type Locker combines the sync.Locker interface with the TryLock method 21 type Locker interface { 22 sync.Locker 23 TryLock() bool 24 } 25 26 // New creates a new Mutex which currently uses a tcp connection to determine 27 // the lock status, and as such requires a tcp address to listen on. 28 // 29 // This may change and is not stable. 30 func New(addr string) Locker { 31 return &mutex{addr: addr} 32 } 33 34 // Lock locks the mutex. If it is already locked, by this or another process, 35 // then the call blocks until it is unlocked. 36 func (m *mutex) Lock() { 37 for !m.TryLock() { 38 c, err := net.Dial("tcp", m.addr) 39 if err == nil { 40 c.Read(readBuf[:]) 41 } 42 } 43 } 44 45 // TryLock attempts to lock the Mutex, returning true on a success. 46 func (m *mutex) TryLock() bool { 47 l, err := net.Listen("tcp", m.addr) 48 if err == nil { 49 m.mu.Lock() 50 m.l = l 51 m.mu.Unlock() 52 return true 53 } else if oe, ok := err.(*net.OpError); ok && isOpen(oe.Err) { 54 return false 55 } else { 56 panic(err) 57 } 58 } 59 60 // Unlock removes the lock. Due to the current implementation, exiting the 61 // program will also unlock the mutex. 62 // 63 // It is the intention that this will always be true, but Unlock should be 64 // called before program exit regardless. 65 func (m *mutex) Unlock() { 66 m.mu.Lock() 67 m.l.Close() 68 m.l = nil 69 m.mu.Unlock() 70 } 71