2013-08-23 16:54:25 +00:00
|
|
|
/*
|
2013-08-26 00:47:56 +00:00
|
|
|
Copyright 2013 The Go Authors
|
2013-08-23 16:54:25 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2013-08-26 00:47:56 +00:00
|
|
|
package lock
|
2013-08-23 16:54:25 +00:00
|
|
|
|
|
|
|
import (
|
2016-01-27 14:40:06 +00:00
|
|
|
"bufio"
|
|
|
|
"errors"
|
2013-08-23 16:54:25 +00:00
|
|
|
"fmt"
|
2016-01-27 14:40:06 +00:00
|
|
|
"io"
|
2013-08-23 16:54:25 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestLock(t *testing.T) {
|
|
|
|
testLock(t, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockPortable(t *testing.T) {
|
|
|
|
testLock(t, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockInChild(t *testing.T) {
|
|
|
|
f := os.Getenv("TEST_LOCK_FILE")
|
|
|
|
if f == "" {
|
|
|
|
// not child
|
|
|
|
return
|
|
|
|
}
|
|
|
|
lock := Lock
|
|
|
|
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
|
|
|
|
lock = lockPortable
|
|
|
|
}
|
|
|
|
|
2016-01-27 14:40:06 +00:00
|
|
|
var lk io.Closer
|
|
|
|
for scan := bufio.NewScanner(os.Stdin); scan.Scan(); {
|
|
|
|
var err error
|
|
|
|
switch scan.Text() {
|
|
|
|
case "lock":
|
|
|
|
lk, err = lock(f)
|
|
|
|
case "unlock":
|
|
|
|
err = lk.Close()
|
|
|
|
lk = nil
|
|
|
|
case "exit":
|
|
|
|
// Simulate a crash, or at least not unlocking the lock.
|
|
|
|
os.Exit(0)
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("unexpected child command %q", scan.Text())
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
} else {
|
|
|
|
fmt.Println("")
|
|
|
|
}
|
2013-08-23 16:54:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testLock(t *testing.T, portable bool) {
|
|
|
|
lock := Lock
|
|
|
|
if portable {
|
|
|
|
lock = lockPortable
|
|
|
|
}
|
2016-01-27 14:40:06 +00:00
|
|
|
t.Logf("test lock, portable %v", portable)
|
2013-08-23 16:54:25 +00:00
|
|
|
|
|
|
|
td, err := ioutil.TempDir("", "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(td)
|
|
|
|
|
|
|
|
path := filepath.Join(td, "foo.lock")
|
|
|
|
|
2016-01-27 14:40:06 +00:00
|
|
|
proc := newChildProc(t, path, portable)
|
|
|
|
defer proc.kill()
|
2013-08-23 16:54:25 +00:00
|
|
|
|
2016-01-27 14:40:06 +00:00
|
|
|
t.Logf("First lock in child")
|
|
|
|
if err := proc.do("lock"); err != nil {
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Fatalf("first lock in child process: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-01-27 14:40:06 +00:00
|
|
|
t.Logf("Crash child")
|
|
|
|
if err := proc.do("exit"); err != nil {
|
|
|
|
t.Fatalf("crash in child process: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
proc = newChildProc(t, path, portable)
|
|
|
|
defer proc.kill()
|
|
|
|
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Logf("Locking+unlocking in child...")
|
2016-01-27 14:40:06 +00:00
|
|
|
if err := proc.do("lock"); err != nil {
|
|
|
|
t.Fatalf("lock in child process after crashing child: %v", err)
|
|
|
|
}
|
|
|
|
if err := proc.do("unlock"); err != nil {
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Fatalf("lock in child process after crashing child: %v", err)
|
2013-08-23 16:54:25 +00:00
|
|
|
}
|
|
|
|
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Logf("Locking in parent...")
|
2013-08-23 16:54:25 +00:00
|
|
|
lk1, err := lock(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Logf("Again in parent...")
|
2013-08-23 16:54:25 +00:00
|
|
|
_, err = lock(path)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected second lock to fail")
|
|
|
|
}
|
|
|
|
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Logf("Locking in child...")
|
2016-01-27 14:40:06 +00:00
|
|
|
if err := proc.do("lock"); err == nil {
|
2013-08-23 16:54:25 +00:00
|
|
|
t.Fatalf("expected lock in child process to fail")
|
|
|
|
}
|
|
|
|
|
2013-08-26 14:06:55 +00:00
|
|
|
t.Logf("Unlocking lock in parent")
|
2013-08-23 16:54:25 +00:00
|
|
|
if err := lk1.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2016-01-27 14:40:06 +00:00
|
|
|
t.Logf("Trying lock again in child...")
|
|
|
|
if err := proc.do("lock"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := proc.do("unlock"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2013-08-23 16:54:25 +00:00
|
|
|
lk3, err := lock(path)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
lk3.Close()
|
|
|
|
}
|
2016-01-27 14:40:06 +00:00
|
|
|
|
|
|
|
type childLockCmd struct {
|
|
|
|
op string
|
|
|
|
reply chan<- error
|
|
|
|
}
|
|
|
|
|
|
|
|
type childProc struct {
|
|
|
|
proc *os.Process
|
|
|
|
c chan childLockCmd
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *childProc) kill() {
|
|
|
|
c.proc.Kill()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *childProc) do(op string) error {
|
|
|
|
reply := make(chan error)
|
|
|
|
c.c <- childLockCmd{
|
|
|
|
op: op,
|
|
|
|
reply: reply,
|
|
|
|
}
|
|
|
|
return <-reply
|
|
|
|
}
|
|
|
|
|
|
|
|
func newChildProc(t *testing.T, path string, portable bool) *childProc {
|
|
|
|
cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
|
|
|
|
cmd.Env = []string{"TEST_LOCK_FILE=" + path}
|
|
|
|
toChild, err := cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot make pipe: %v", err)
|
|
|
|
}
|
|
|
|
fromChild, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("cannot make pipe: %v", err)
|
|
|
|
}
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if portable {
|
|
|
|
cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
|
|
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
t.Fatalf("cannot start child: %v", err)
|
|
|
|
}
|
|
|
|
cmdChan := make(chan childLockCmd)
|
|
|
|
go func() {
|
|
|
|
defer fromChild.Close()
|
|
|
|
defer toChild.Close()
|
|
|
|
inScan := bufio.NewScanner(fromChild)
|
|
|
|
for c := range cmdChan {
|
|
|
|
fmt.Fprintln(toChild, c.op)
|
|
|
|
ok := inScan.Scan()
|
|
|
|
if c.op == "exit" {
|
|
|
|
if ok {
|
|
|
|
c.reply <- errors.New("child did not exit")
|
|
|
|
} else {
|
|
|
|
cmd.Wait()
|
|
|
|
c.reply <- nil
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
panic("child exited early")
|
|
|
|
}
|
|
|
|
if errText := inScan.Text(); errText != "" {
|
|
|
|
c.reply <- errors.New(errText)
|
|
|
|
} else {
|
|
|
|
c.reply <- nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return &childProc{
|
|
|
|
c: cmdChan,
|
|
|
|
proc: cmd.Process,
|
|
|
|
}
|
|
|
|
}
|