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