Skip to content

Instantly share code, notes, and snippets.

@gkristic
Created February 4, 2014 01:16
Show Gist options
  • Save gkristic/8795776 to your computer and use it in GitHub Desktop.
Save gkristic/8795776 to your computer and use it in GitHub Desktop.
My idea of a process supervisor, using locking to react immediately to supervised processes deaths. As locks are guaranteed to be released when the process ends, no matter the reason, this can as easily detect crashes or processes killed with SIGKILL. It also writes pid files, though that's a bonus feature, not strictly required.
package main
import (
"fmt"
"os"
"strings"
"syscall"
"time"
)
type Log string
func (l Log) Print(args ...interface{}) {
msg := fmt.Sprint(args...)
fmt.Print(string(l), ": ", msg, "\n")
}
var log = Log(os.Args[0])
const Unavailable = "resource temporarily unavailable"
func Lock(f *os.File, block bool) error {
how := syscall.LOCK_EX
if !block {
how |= syscall.LOCK_NB
}
err := syscall.Flock(int(f.Fd()), how)
if err != nil {
if !block && err.Error() == Unavailable {
return err
}
log.Print(err)
os.Exit(1)
}
return nil
}
func WritePid() {
name := os.Args[0] + ".pid"
flags := syscall.O_WRONLY | syscall.O_CREAT | syscall.O_TRUNC
f, err := os.OpenFile(name, flags, os.FileMode(0644))
if err != nil {
log.Print("unable to create pidfile: ", err)
os.Exit(1)
}
if _, err := f.WriteString(fmt.Sprintf("%d\n", os.Getpid())); err != nil {
log.Print("error writing pid: ", err)
os.Exit(1)
}
f.Close()
}
func childMain() {
log.Print("starting with pid ", os.Getpid())
name := os.Args[0] + ".lck"
flags := syscall.O_RDONLY | syscall.O_CREAT
f, err := os.OpenFile(name, flags, os.FileMode(0600))
if err != nil {
log.Print("unable to create lockfile: ", err)
os.Exit(1)
}
if Lock(f, false) != nil {
log.Print("unable to lock (already running?)")
os.Exit(1)
}
WritePid()
for {
log.Print("doing my stuff")
time.Sleep(time.Duration(5) * time.Second)
}
}
func spawnChild(name string) {
log.Print("starting ", name)
p, err := os.StartProcess("./proc", []string{name}, &os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})
if err != nil {
log.Print("unable to spawn ", name, ": ", err)
os.Exit(1)
}
log.Print("started ", name, " with pid ", p.Pid)
// Here's the time we're willing to wait for it to initialize
time.Sleep(time.Duration(500) * time.Millisecond)
log.Print("monitoring process")
f, err := os.OpenFile(name+".lck", syscall.O_RDONLY, os.FileMode(0))
if err != nil {
log.Print("unable to open lockfile: ", err)
os.Exit(1)
}
Lock(f, true)
log.Print(name, " died")
p.Kill()
p.Wait()
f.Close()
}
func parentMain() {
flags := syscall.O_RDONLY | syscall.O_CREAT
f, err := os.OpenFile(os.Args[0] + ".lck", flags, os.FileMode(0600))
if err != nil {
log.Print("unable to open lockfile: ", err)
os.Exit(1)
}
if Lock(f, false) != nil {
log.Print("unable to lock (already running?)")
os.Exit(1)
}
children := []string{"cats", "bacon", "natalie"}
for _, name := range children {
go func(name string) {
for {
spawnChild(name)
}
}("proc-" + name)
}
select {}
}
func main() {
if strings.HasPrefix(os.Args[0], "proc-") {
childMain()
} else {
parentMain()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment