"veyron/lib/filelocker": TryLock
Adds TryLock function to the filelocker. This will be used in trying
to lock the veyron credentials directory during rt.Init/rt.New.
Change-Id: I7bb4f4585f550186c125c9b0b3fd0ba656c7a83b
diff --git a/lib/filelocker/locker.go b/lib/filelocker/locker.go
index 5e6674f..43dfd5f 100644
--- a/lib/filelocker/locker.go
+++ b/lib/filelocker/locker.go
@@ -18,7 +18,7 @@
// Lock locks the provided file.
//
// If the file is already locked then the calling goroutine
-// blocks until the lock can be acquired.
+// blocks until the lock can be grabbed.
//
// The file must exist otherwise an error is returned.
func Lock(filepath string) (Unlocker, error) {
@@ -32,6 +32,21 @@
return &unlocker{file}, nil
}
+// TryLock tries to grab a lock on the provided file and
+// returns an error if it fails. This function is non-blocking.
+//
+// The file must exist otherwise an error is returned.
+func TryLock(filepath string) (Unlocker, error) {
+ file, err := os.Open(filepath)
+ if err != nil {
+ return nil, err
+ }
+ if err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
+ return nil, err
+ }
+ return &unlocker{file}, nil
+}
+
// unlocker implements Unlocker.
type unlocker struct {
file *os.File
diff --git a/lib/filelocker/locker_test.go b/lib/filelocker/locker_test.go
index 0060210..f3ce658 100644
--- a/lib/filelocker/locker_test.go
+++ b/lib/filelocker/locker_test.go
@@ -6,6 +6,7 @@
"io"
"io/ioutil"
"os"
+ "syscall"
"testing"
"time"
@@ -14,7 +15,7 @@
)
func init() {
- modules.RegisterChild("testLockUnlockChild", "", testLockUnlockChild)
+ modules.RegisterChild("testLockChild", "", testLockChild)
}
func TestHelperProcess(t *testing.T) {
@@ -39,7 +40,7 @@
}
}
-func testLockUnlockChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+func testLockChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
// Lock the file
unlocker, err := Lock(args[1])
if err != nil {
@@ -58,13 +59,13 @@
return nil
}
-func TestLockUnlockInterProcess(t *testing.T) {
+func TestLockInterProcess(t *testing.T) {
filepath := newFile()
defer os.Remove(filepath)
sh := modules.NewShell()
defer sh.Cleanup(os.Stderr, os.Stderr)
- h, err := sh.Start("testLockUnlockChild", nil, filepath)
+ h, err := sh.Start("testLockChild", nil, filepath)
if err != nil {
t.Fatalf("sh.Start failed: %v", err)
}
@@ -97,7 +98,7 @@
s.ExpectEOF()
}
-func TestLockUnlockIntraProcess(t *testing.T) {
+func TestLockIntraProcess(t *testing.T) {
filepath := newFile()
defer os.Remove(filepath)
@@ -127,3 +128,33 @@
t.Fatal("Another goroutine failed to grab the lock after this goroutine released it")
}
}
+
+func TestTryLock(t *testing.T) {
+ filepath := newFile()
+ defer os.Remove(filepath)
+
+ sh := modules.NewShell()
+ defer sh.Cleanup(os.Stderr, os.Stderr)
+ h, err := sh.Start("testLockChild", nil, filepath)
+ if err != nil {
+ t.Fatalf("sh.Start failed: %v", err)
+ }
+ s := expect.NewSession(t, h.Stdout(), time.Minute)
+
+ // Test that child grabbed the lock.
+ s.Expect("locked")
+
+ // Test that parent cannot grab the lock, and then send a message
+ // to the child to release the lock.
+ if _, err := TryLock(filepath); err != syscall.EWOULDBLOCK {
+ t.Fatal("TryLock returned error: %v, want: %v", err, syscall.EWOULDBLOCK)
+ }
+
+ // Test that the parent can grab the lock after the child has released it.
+ h.Stdin().Write([]byte("unlock\n"))
+ s.Expect("unlocked")
+ if _, err = TryLock(filepath); err != nil {
+ t.Fatalf("TryLock failed: %v", err)
+ }
+ s.ExpectEOF()
+}