go: time out flock differently

Use channels and timers to time out a blocking flock, rather than
non-blocking + polling.

Change-Id: Idd7d5d035a5ccf1c609bbd79575ded1e896a3f07
This commit is contained in:
Michael Barton 2016-06-27 17:58:49 +00:00
parent a331f77253
commit ab08119d2f
4 changed files with 48 additions and 16 deletions

View File

@ -29,6 +29,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/load"
@ -39,7 +40,7 @@ import (
func DumpReconCache(reconCachePath string, source string, cacheData map[string]interface{}) error {
reconFile := filepath.Join(reconCachePath, source+".recon")
if lock, err := LockParent(reconFile, 5); err != nil {
if lock, err := LockPath(filepath.Dir(reconFile), 5*time.Second); err != nil {
return err
} else {
defer lock.Close()

View File

@ -84,32 +84,39 @@ func WriteFileAtomic(filename string, data []byte, perm os.FileMode) error {
return nil
}
func LockPath(directory string, timeout int) (*os.File, error) {
sleepTime := 5
// LockPath locks a directory with a timeout.
func LockPath(directory string, timeout time.Duration) (*os.File, error) {
lockfile := filepath.Join(directory, ".lock")
file, err := os.OpenFile(lockfile, os.O_RDWR|os.O_CREATE, 0660)
if err != nil {
if os.IsNotExist(err) && os.MkdirAll(directory, 0755) == nil {
file, err = os.OpenFile(lockfile, os.O_RDWR|os.O_CREATE, 0660)
}
if file == nil {
return nil, errors.New(fmt.Sprintf("Unable to open file ccc. ( %s )", err.Error()))
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to open lock file (%v)", err))
}
}
for stop := time.Now().Add(time.Duration(timeout) * time.Second); time.Now().Before(stop); {
err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
success := make(chan error)
cancel := make(chan struct{})
defer close(cancel)
timer := time.NewTimer(timeout)
defer timer.Stop()
go func(fd int) {
select {
case success <- syscall.Flock(fd, syscall.LOCK_EX):
case <-cancel:
}
}(int(file.Fd()))
select {
case err = <-success:
if err == nil {
return file, nil
}
time.Sleep(time.Millisecond * time.Duration(sleepTime))
sleepTime += 5
case <-timer.C:
err = errors.New("Flock timed out")
}
file.Close()
return nil, errors.New("Timed out")
}
func LockParent(file string, timeout int) (*os.File, error) {
return LockPath(filepath.Dir(file), timeout)
return nil, err
}
func IsMount(dir string) (bool, error) {

View File

@ -288,3 +288,27 @@ func TestGetHashPrefixAndSuffix(t *testing.T) {
t.Error("Error prefix and suffix not being set")
}
}
func TestLockPath(t *testing.T) {
tempDir, err := ioutil.TempDir("", "")
defer os.RemoveAll(tempDir)
require.Nil(t, err)
c := make(chan bool)
ended := make(chan struct{})
defer close(ended)
go func() {
f, err := LockPath(tempDir, time.Millisecond)
c <- true
require.Nil(t, err)
require.NotNil(t, f)
defer f.Close()
select {
case <-time.After(time.Second):
case <-ended:
}
}()
<-c
f, err := LockPath(tempDir, time.Millisecond)
require.Nil(t, f)
require.NotNil(t, err)
}

View File

@ -150,7 +150,7 @@ func InvalidateHash(hashDir string) error {
suffDir := filepath.Dir(hashDir)
partitionDir := filepath.Dir(suffDir)
if partitionLock, err := hummingbird.LockPath(partitionDir, 10); err != nil {
if partitionLock, err := hummingbird.LockPath(partitionDir, 10*time.Second); err != nil {
return err
} else {
defer partitionLock.Close()
@ -322,7 +322,7 @@ func GetHashes(driveRoot string, device string, partition string, recalculate []
}
}
if modified {
partitionLock, err := hummingbird.LockPath(partitionDir, 10)
partitionLock, err := hummingbird.LockPath(partitionDir, 10*time.Second)
defer partitionLock.Close()
if err != nil {
return nil, &hummingbird.BackendError{Err: err, Code: hummingbird.LockPathError}