110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
// Package pmuxlib implements the process management aspects of the pmux
|
|
// process.
|
|
package pmuxlib
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
type Config struct {
|
|
TimeFormat string `yaml:"timeFormat"`
|
|
|
|
// Set of processes to run, keyed by their name.
|
|
Processes map[string]ProcessConfig `yaml:"processes"`
|
|
}
|
|
|
|
// Pmux manages multiple child Processes. Methods on a Pmux instance are _not_
|
|
// thread-safe.
|
|
//
|
|
// Stop must be called on a Pmux before the program has exited, or there may be
|
|
// a leftover zombie child process.
|
|
type Pmux struct {
|
|
processes map[string]*Process
|
|
|
|
stdoutLogger, stderrLogger, sysLogger *PmuxLogger
|
|
}
|
|
|
|
// NewPmux starts a Pmux with the given configuration.
|
|
func NewPmux(cfg Config, stdout, stderr io.Writer) *Pmux {
|
|
var (
|
|
stdoutLogger = NewPmuxLogger(stdout, LogSepStdout, cfg.TimeFormat)
|
|
stderrLogger = NewPmuxLogger(stderr, LogSepStderr, cfg.TimeFormat)
|
|
sysLogger = stderrLogger.WithSep(LogSepSys)
|
|
|
|
p = &Pmux{
|
|
processes: map[string]*Process{},
|
|
stdoutLogger: stdoutLogger,
|
|
stderrLogger: stderrLogger,
|
|
sysLogger: sysLogger,
|
|
}
|
|
)
|
|
|
|
for name, cfgProc := range cfg.Processes {
|
|
cfgProc.StdoutLogger = stdoutLogger.WithProcessName(name)
|
|
cfgProc.StderrLogger = stderrLogger.WithProcessName(name)
|
|
cfgProc.SysLogger = sysLogger.WithProcessName(name)
|
|
p.processes[name] = NewProcess(cfgProc)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// Restart will block until the child process of the given name has been killed
|
|
// and a new one has been spawned. If there is no child of the given name then
|
|
// Restart panics.
|
|
func (p *Pmux) Restart(name string) {
|
|
proc, ok := p.processes[name]
|
|
if !ok {
|
|
panic(fmt.Sprintf("no process named %q", name))
|
|
}
|
|
proc.Restart()
|
|
}
|
|
|
|
// Stop will block until all child processes have been killed. The Pmux should
|
|
// not be used again after this.
|
|
func (p *Pmux) Stop() {
|
|
var wg sync.WaitGroup
|
|
|
|
var minGroup, maxGroup int
|
|
|
|
for _, proc := range p.processes {
|
|
if maxGroup < proc.cfg.Group {
|
|
maxGroup = proc.cfg.Group
|
|
}
|
|
|
|
if minGroup > proc.cfg.Group {
|
|
minGroup = proc.cfg.Group
|
|
}
|
|
}
|
|
|
|
for group := maxGroup; group >= minGroup; group-- {
|
|
p.sysLogger.Printf("killing child processes (group %d)", group)
|
|
for _, proc := range p.processes {
|
|
proc := proc
|
|
if proc.cfg.Group != group {
|
|
continue
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
proc.Stop()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
if err := p.stdoutLogger.Close(); err != nil {
|
|
p.sysLogger.Printf("Closing stdout logger failed: %v", err)
|
|
}
|
|
|
|
if err := p.stderrLogger.Close(); err != nil {
|
|
p.sysLogger.Printf("Closing stderr logger failed: %v", err)
|
|
}
|
|
|
|
p.sysLogger.Println("exited gracefully, ciao!")
|
|
_ = p.sysLogger.Close()
|
|
}
|