git.deanishe.net/deanishe/awgo.git@v0.15.0/background.go (about) 1 // 2 // Copyright (c) 2016 Dean Jackson <deanishe@deanishe.net> 3 // 4 // MIT Licence. See http://opensource.org/licenses/MIT 5 // 6 // Created on 2016-11-08 7 // 8 9 package aw 10 11 import ( 12 "fmt" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "strconv" 18 "syscall" 19 20 "github.com/deanishe/awgo/util" 21 ) 22 23 // ErrJobExists is the error returned by RunInBackground if a job with 24 // the given name is already running. 25 type ErrJobExists struct { 26 Name string // Name of the job 27 Pid int // PID of the running job 28 } 29 30 // Error implements error interface. 31 func (a ErrJobExists) Error() string { 32 return fmt.Sprintf("Job '%s' already running with PID %d", a.Name, a.Pid) 33 } 34 35 // IsJobExists returns true if error is of type ErrJobExists. 36 func IsJobExists(err error) bool { 37 _, ok := err.(ErrJobExists) 38 return ok 39 } 40 41 // RunInBackground executes cmd in the background. It returns an 42 // ErrJobExists error if a job of the same name is already running. 43 func (wf *Workflow) RunInBackground(jobName string, cmd *exec.Cmd) error { 44 if wf.IsRunning(jobName) { 45 pid, _ := wf.getPid(jobName) 46 return ErrJobExists{jobName, pid} 47 } 48 49 if cmd.SysProcAttr == nil { 50 cmd.SysProcAttr = &syscall.SysProcAttr{} 51 } 52 // Prevent process from being killed when parent is 53 cmd.SysProcAttr.Setpgid = true 54 if err := cmd.Start(); err != nil { 55 return err 56 } 57 58 return wf.savePid(jobName, cmd.Process.Pid) 59 } 60 61 // Kill stops a background job. 62 func (wf *Workflow) Kill(jobName string) error { 63 pid, err := wf.getPid(jobName) 64 if err != nil { 65 return err 66 } 67 p := wf.pidFile(jobName) 68 if err = syscall.Kill(pid, syscall.SIGTERM); err != nil { 69 // Delete stale PID file 70 os.Remove(p) 71 return err 72 } 73 os.Remove(p) 74 return nil 75 } 76 77 // IsRunning returns true if a job with name jobName is currently running. 78 func (wf *Workflow) IsRunning(jobName string) bool { 79 pid, err := wf.getPid(jobName) 80 if err != nil { 81 return false 82 } 83 if err = syscall.Kill(pid, 0); err != nil { 84 // Delete stale PID file 85 os.Remove(wf.pidFile(jobName)) 86 return false 87 } 88 return true 89 } 90 91 // Save PID a job-specific file. 92 func (wf *Workflow) savePid(jobName string, pid int) error { 93 p := wf.pidFile(jobName) 94 return ioutil.WriteFile(p, []byte(strconv.Itoa(pid)), 0600) 95 } 96 97 // Return PID for job. 98 func (wf *Workflow) getPid(jobName string) (int, error) { 99 p := wf.pidFile(jobName) 100 data, err := ioutil.ReadFile(p) 101 if err != nil { 102 return 0, err 103 } 104 pid, err := strconv.Atoi(string(data)) 105 if err != nil { 106 return 0, err 107 } 108 return pid, nil 109 } 110 111 // Path to PID file for job. 112 func (wf *Workflow) pidFile(jobName string) string { 113 dir := util.MustExist(filepath.Join(wf.awCacheDir(), "jobs")) 114 return filepath.Join(dir, jobName+".pid") 115 }