Skip to content

Commit

Permalink
Merge pull request nightlyone#12 from stewi1014/master
Browse files Browse the repository at this point in the history
Added relevant and go-compatable tests to testing file. Fixed dead PID on windows.
  • Loading branch information
nightlyone committed May 20, 2016
2 parents fbe4e7f + eb87890 commit 941fc61
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 41 deletions.
14 changes: 12 additions & 2 deletions lockfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
111 changes: 108 additions & 3 deletions lockfile_test.go
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
}
}
28 changes: 11 additions & 17 deletions lockfile_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
36 changes: 17 additions & 19 deletions lockfile_windows.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 941fc61

Please sign in to comment.