github.com/grailbio/base@v0.0.11/flock/flock_unix.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // Package flock implements a simple POSIX file-based advisory lock. 6 package flock 7 8 import ( 9 "context" 10 "sync" 11 "syscall" 12 13 "github.com/grailbio/base/log" 14 ) 15 16 type T struct { 17 name string 18 fd int 19 mu sync.Mutex 20 } 21 22 // New creates an object that locks the given path. 23 func New(path string) *T { 24 return &T{name: path} 25 } 26 27 // Lock locks the file. Iff Lock() returns nil, the caller must call Unlock() 28 // later. 29 func (f *T) Lock(ctx context.Context) (err error) { 30 reqCh := make(chan func() error, 2) 31 doneCh := make(chan error) 32 go func() { 33 var err error 34 for req := range reqCh { 35 if err == nil { 36 err = req() 37 } 38 doneCh <- err 39 } 40 }() 41 reqCh <- f.doLock 42 select { 43 case <-ctx.Done(): 44 reqCh <- f.doUnlock 45 err = ctx.Err() 46 case err = <-doneCh: 47 } 48 close(reqCh) 49 return err 50 } 51 52 // Unlock unlocks the file. 53 func (f *T) Unlock() error { 54 return f.doUnlock() 55 } 56 57 func (f *T) doLock() error { 58 f.mu.Lock() // Serialize the lock within one process. 59 60 var err error 61 f.fd, err = syscall.Open(f.name, syscall.O_CREAT|syscall.O_RDWR, 0777) 62 if err != nil { 63 f.mu.Unlock() 64 return err 65 } 66 err = syscall.Flock(f.fd, syscall.LOCK_EX|syscall.LOCK_NB) 67 for err == syscall.EWOULDBLOCK || err == syscall.EAGAIN { 68 log.Printf("waiting for lock %s", f.name) 69 err = syscall.Flock(f.fd, syscall.LOCK_EX) 70 } 71 if err != nil { 72 f.mu.Unlock() 73 } 74 return err 75 } 76 77 func (f *T) doUnlock() error { 78 err := syscall.Flock(f.fd, syscall.LOCK_UN) 79 if err := syscall.Close(f.fd); err != nil { 80 log.Error.Printf("close %s: %v", f.name, err) 81 } 82 f.mu.Unlock() 83 return err 84 }