github.com/quantumghost/awgo@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  }