diff --git a/go/hummingbird/recon.go b/go/hummingbird/recon.go index 1c3bb36865..f7c23f1080 100644 --- a/go/hummingbird/recon.go +++ b/go/hummingbird/recon.go @@ -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() diff --git a/go/hummingbird/utils.go b/go/hummingbird/utils.go index 29577cab3e..83fe846038 100644 --- a/go/hummingbird/utils.go +++ b/go/hummingbird/utils.go @@ -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) { diff --git a/go/hummingbird/utils_test.go b/go/hummingbird/utils_test.go index 78ba962387..7ce0568876 100644 --- a/go/hummingbird/utils_test.go +++ b/go/hummingbird/utils_test.go @@ -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) +} diff --git a/go/objectserver/backend.go b/go/objectserver/backend.go index 12a1f4a8ed..d0f38ac604 100644 --- a/go/objectserver/backend.go +++ b/go/objectserver/backend.go @@ -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}