pmux/pmuxlib/pmuxlib.go

110 lines
2.6 KiB
Go
Raw Normal View History

// Package pmuxlib implements the process management aspects of the pmux
// process.
package pmuxlib
import (
"fmt"
2022-10-25 18:54:05 +00:00
"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 Logger
}
// 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
2024-07-19 13:49:13 +00:00
var minGroup, maxGroup int
for _, proc := range p.processes {
2024-07-19 13:49:13 +00:00
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
}
2024-07-19 13:49:13 +00:00
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()
}