mirror of
https://github.com/gwoo/goforever.git
synced 2025-09-26 19:41:10 +08:00
308 lines
5.8 KiB
Go
308 lines
5.8 KiB
Go
// goforever - processes management
|
|
// Copyright (c) 2013 Garrett Woodworth (https://github.com/gwoo).
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
var ping = "1m"
|
|
|
|
//Run the process
|
|
func RunProcess(name string, p *Process) chan *Process {
|
|
ch := make(chan *Process)
|
|
go func() {
|
|
p.start(name)
|
|
p.ping(ping, func(time time.Duration, p *Process) {
|
|
if p.Pid > 0 {
|
|
p.respawns = 0
|
|
fmt.Printf("%s refreshed after %s.\n", p.Name, time)
|
|
p.Status = "running"
|
|
}
|
|
})
|
|
go p.watch()
|
|
ch <- p
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
type Process struct {
|
|
Name string
|
|
Command string
|
|
Args []string
|
|
Pidfile Pidfile
|
|
Logfile string
|
|
Errfile string
|
|
Path string
|
|
Respawn int
|
|
Delay string
|
|
Ping string
|
|
Pid int
|
|
Status string
|
|
x *os.Process
|
|
respawns int
|
|
children children
|
|
}
|
|
|
|
func (p *Process) String() string {
|
|
js, err := json.Marshal(p)
|
|
if err != nil {
|
|
log.Print(err)
|
|
return ""
|
|
}
|
|
return string(js)
|
|
}
|
|
|
|
//Find a process by name
|
|
func (p *Process) find() (*os.Process, string, error) {
|
|
if p.Pidfile == "" {
|
|
return nil, "", errors.New("Pidfile is empty.")
|
|
}
|
|
if pid := p.Pidfile.read(); pid > 0 {
|
|
process, err := os.FindProcess(pid)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
p.x = process
|
|
p.Pid = process.Pid
|
|
p.Status = "running"
|
|
message := fmt.Sprintf("%s is %#v\n", p.Name, process.Pid)
|
|
return process, message, nil
|
|
}
|
|
message := fmt.Sprintf("%s not running.\n", p.Name)
|
|
return nil, message, errors.New(fmt.Sprintf("Could not find process %s.", p.Name))
|
|
}
|
|
|
|
//Start the process
|
|
func (p *Process) start(name string) string {
|
|
p.Name = name
|
|
wd, _ := os.Getwd()
|
|
proc := &os.ProcAttr{
|
|
Dir: wd,
|
|
Env: os.Environ(),
|
|
Files: []*os.File{
|
|
os.Stdin,
|
|
NewLog(p.Logfile),
|
|
NewLog(p.Errfile),
|
|
},
|
|
}
|
|
args := append([]string{p.Name}, p.Args...)
|
|
process, err := os.StartProcess(p.Command, args, proc)
|
|
if err != nil {
|
|
log.Fatalf("%s failed. %s\n", p.Name, err)
|
|
return ""
|
|
}
|
|
err = p.Pidfile.write(process.Pid)
|
|
if err != nil {
|
|
log.Printf("%s pidfile error: %s\n", p.Name, err)
|
|
return ""
|
|
}
|
|
p.x = process
|
|
p.Pid = process.Pid
|
|
p.Status = "started"
|
|
return fmt.Sprintf("%s is %#v\n", p.Name, process.Pid)
|
|
}
|
|
|
|
//Stop the process
|
|
func (p *Process) stop() string {
|
|
if p.x != nil {
|
|
// p.x.Kill() this seems to cause trouble
|
|
cmd := exec.Command("kill", fmt.Sprintf("%d", p.x.Pid))
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
p.children.stop("all")
|
|
}
|
|
p.release("stopped")
|
|
message := fmt.Sprintf("%s stopped.\n", p.Name)
|
|
return message
|
|
}
|
|
|
|
//Release process and remove pidfile
|
|
func (p *Process) release(status string) {
|
|
if p.x != nil {
|
|
p.x.Release()
|
|
}
|
|
p.Pid = 0
|
|
p.Pidfile.delete()
|
|
p.Status = status
|
|
}
|
|
|
|
//Restart the process
|
|
func (p *Process) restart() (chan *Process, string) {
|
|
p.stop()
|
|
message := fmt.Sprintf("%s restarted.\n", p.Name)
|
|
ch := RunProcess(p.Name, p)
|
|
return ch, message
|
|
}
|
|
|
|
//Run callback on the process after given duration.
|
|
func (p *Process) ping(duration string, f func(t time.Duration, p *Process)) {
|
|
if p.Ping != "" {
|
|
duration = p.Ping
|
|
}
|
|
t, err := time.ParseDuration(duration)
|
|
if err != nil {
|
|
t, _ = time.ParseDuration(ping)
|
|
}
|
|
go func() {
|
|
select {
|
|
case <-time.After(t):
|
|
f(t, p)
|
|
}
|
|
}()
|
|
}
|
|
|
|
//Watch the process
|
|
func (p *Process) watch() {
|
|
if p.x == nil {
|
|
p.release("stopped")
|
|
return
|
|
}
|
|
status := make(chan *os.ProcessState)
|
|
died := make(chan error)
|
|
go func() {
|
|
state, err := p.x.Wait()
|
|
if err != nil {
|
|
died <- err
|
|
return
|
|
}
|
|
status <- state
|
|
}()
|
|
select {
|
|
case s := <-status:
|
|
if p.Status == "stopped" {
|
|
return
|
|
}
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", p.Name, s)
|
|
fmt.Fprintf(os.Stderr, "%s success = %#v\n", p.Name, s.Success())
|
|
fmt.Fprintf(os.Stderr, "%s exited = %#v\n", p.Name, s.Exited())
|
|
p.respawns++
|
|
if p.respawns > p.Respawn {
|
|
p.release("exited")
|
|
log.Printf("%s respawn limit reached.\n", p.Name)
|
|
return
|
|
}
|
|
fmt.Fprintf(os.Stderr, "%s respawns = %#v\n", p.Name, p.respawns)
|
|
if p.Delay != "" {
|
|
t, _ := time.ParseDuration(p.Delay)
|
|
time.Sleep(t)
|
|
}
|
|
p.restart()
|
|
p.Status = "restarted"
|
|
case err := <-died:
|
|
p.release("killed")
|
|
log.Printf("%d %s killed = %#v", p.x.Pid, p.Name, err)
|
|
}
|
|
}
|
|
|
|
//Run child processes
|
|
func (p *Process) run() {
|
|
for name, p := range p.children {
|
|
RunProcess(name, p)
|
|
}
|
|
}
|
|
|
|
//Child processes.
|
|
type children map[string]*Process
|
|
|
|
//Stringify
|
|
func (c children) String() string {
|
|
js, err := json.Marshal(c)
|
|
if err != nil {
|
|
log.Print(err)
|
|
return ""
|
|
}
|
|
return string(js)
|
|
}
|
|
|
|
//Get child processes names.
|
|
func (c children) keys() []string {
|
|
keys := []string{}
|
|
for k, _ := range c {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
//Get child process.
|
|
func (c children) get(key string) *Process {
|
|
if v, ok := c[key]; ok {
|
|
return v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c children) stop(name string) {
|
|
if name == "all" {
|
|
for name, p := range c {
|
|
p.stop()
|
|
delete(c, name)
|
|
}
|
|
return
|
|
}
|
|
p := c.get(name)
|
|
p.stop()
|
|
delete(c, name)
|
|
}
|
|
|
|
type Pidfile string
|
|
|
|
//Read the pidfile.
|
|
func (f *Pidfile) read() int {
|
|
data, err := ioutil.ReadFile(string(*f))
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
pid, err := strconv.ParseInt(string(data), 0, 32)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return int(pid)
|
|
}
|
|
|
|
//Write the pidfile.
|
|
func (f *Pidfile) write(data int) error {
|
|
err := ioutil.WriteFile(string(*f), []byte(strconv.Itoa(data)), 0660)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
//Delete the pidfile
|
|
func (f *Pidfile) delete() bool {
|
|
_, err := os.Stat(string(*f))
|
|
if err != nil {
|
|
return true
|
|
}
|
|
err = os.Remove(string(*f))
|
|
if err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
//Create a new file for logging
|
|
func NewLog(path string) *os.File {
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
|
|
if err != nil {
|
|
log.Fatalf("%s", err)
|
|
return nil
|
|
}
|
|
return file
|
|
}
|