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