diff --git a/lockfile.go b/lockfile.go index 89d41fe..bb5c221 100644 --- a/lockfile.go +++ b/lockfile.go @@ -44,11 +44,21 @@ func (l Lockfile) GetOwner() (*os.Process, error) { // try hard for pids. If no pid, the lockfile is junk anyway and we delete it. if pid > 0 { - p, err := os.FindProcess(pid) + running, err := isRunning(pid) if err != nil { return nil, err } - return p, isProcessAlive(p) + + if running { + proc, err := os.FindProcess(pid) + if err != nil { + return nil, err + } + return proc, nil + } else { + return nil, ErrDeadOwner + } + } else { return nil, ErrInvalidPid } diff --git a/lockfile_test.go b/lockfile_test.go index f9cc509..6393250 100644 --- a/lockfile_test.go +++ b/lockfile_test.go @@ -1,14 +1,17 @@ -package lockfile_test +package lockfile import ( - lockfile "." "fmt" + "io/ioutil" + "math/rand" "os" "path/filepath" + "strconv" + "testing" ) func ExampleLockfile() { - lock, err := lockfile.New(filepath.Join(os.TempDir(), "lock.me.now.lck")) + lock, err := New(filepath.Join(os.TempDir(), "lock.me.now.lck")) if err != nil { fmt.Printf("Cannot init lock. reason: %v\n", err) panic(err) @@ -26,3 +29,105 @@ func ExampleLockfile() { fmt.Println("Do stuff under lock") // Output: Do stuff under lock } + +func TestLock(t *testing.T) { + path, err := filepath.Abs("test_lockfile.pid") + if err != nil { + panic(err) + } + + lf, err := New(path) + if err != nil { + t.Fail() + fmt.Println("Error making lockfile: ", err) + return + } + + err = lf.TryLock() + if err != nil { + t.Fail() + fmt.Println("Error locking lockfile: ", err) + return + } + + err = lf.Unlock() + if err != nil { + t.Fail() + fmt.Println("Error unlocking lockfile: ", err) + return + } +} + +func TestDeadPID(t *testing.T) { + path, err := filepath.Abs("test_lockfile.pid") + if err != nil { + panic(err) + } + + pid := GetDeadPID() + + ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0666) +} + +func GetDeadPID() int { + for { + pid := rand.Int() % 4096 //I have no idea how windows handles large PIDs, or if they even exist. Limit it to 4096 to be safe. + running, err := isRunning(pid) + if err != nil { + fmt.Println("Error checking PID: ", err) + continue + } + + if !running { + return pid + } + } +} + +func TestBusy(t *testing.T) { + path, err := filepath.Abs("test_lockfile.pid") + if err != nil { + t.Fail() + fmt.Println(err) + return + } + + lf1, err := New(path) + if err != nil { + t.Fail() + fmt.Println(err) + return + } + + err = lf1.TryLock() + if err != nil { + t.Fail() + fmt.Println(err) + return + } + + lf2, err := New(path) + if err != nil { + t.Fail() + fmt.Println(err) + return + } + + err = lf2.TryLock() + if err == nil { + t.Fail() + fmt.Println("No error locking already locked lockfile!") + return + } else if err != ErrBusy { + t.Fail() + fmt.Println(err) + return + } + + err = lf1.Unlock() + if err != nil { + t.Fail() + fmt.Println(err) + return + } +} diff --git a/lockfile_unix.go b/lockfile_unix.go index 7c780ea..36c46eb 100644 --- a/lockfile_unix.go +++ b/lockfile_unix.go @@ -7,22 +7,16 @@ import ( "syscall" ) -func isProcessAlive(p *os.Process) error { - err := p.Signal(os.Signal(syscall.Signal(0))) - if err == nil { - return nil - } - errno, ok := err.(syscall.Errno) - if !ok { - return ErrDeadOwner - } - - switch errno { - case syscall.ESRCH: - return ErrDeadOwner - case syscall.EPERM: - return nil - default: - return err +func isRunning(pid int) (bool, error) { + proc, err := os.FindProcess(pid) + if err != nil { + return false, err + } else { + err := proc.Signal(syscall.Signal(0)) + if err == nil { + return true, nil + } else { + return false, nil + } } } diff --git a/lockfile_windows.go b/lockfile_windows.go index d8c3f63..482bd91 100644 --- a/lockfile_windows.go +++ b/lockfile_windows.go @@ -1,32 +1,30 @@ package lockfile import ( - "os" - "reflect" "syscall" ) -func isProcessAlive(p *os.Process) error { - // Extract handle value from the os.Process struct to avoid the need - // of a second, manually opened process handle. - value := reflect.ValueOf(p) - // Dereference *os.Process to os.Process - value = value.Elem() - field := value.FieldByName("handle") - - handle := syscall.Handle(field.Uint()) +//For some reason these consts don't exist in syscall. +const ( + error_invalid_parameter = 87 + code_still_active = 259 +) - var code uint32 - err := syscall.GetExitCodeProcess(handle, &code) +func isRunning(pid int) (bool, error) { + procHnd, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, true, uint32(pid)) if err != nil { - return err + if scerr, ok := err.(syscall.Errno); ok { + if uintptr(scerr) == error_invalid_parameter { + return false, nil + } + } } - // code will contain the exit code of the process or 259 (STILL_ALIVE) - // if the process has not exited yet. - if code == 259 { - return nil + var code uint32 + err = syscall.GetExitCodeProcess(procHnd, &code) + if err != nil { + return false, err } - return ErrDeadOwner + return code == code_still_active, nil }