Files
gotenberg/internal/pkg/pm2/pm2.go
2019-07-07 19:54:44 +02:00

125 lines
2.7 KiB
Go

package pm2
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
"github.com/thecodingmachine/gotenberg/internal/pkg/logger"
"github.com/thecodingmachine/gotenberg/internal/pkg/standarderror"
)
const (
stoppedState = iota
runningState
errorState
)
// Process is a type that can start or
// shutdown a process with PM2.
type Process interface {
Fullname() string
Start() error
Shutdown() error
args() []string
name() string
viable() bool
warmup()
}
type processManager struct {
heuristicState int32
logger *logger.Logger
}
func (m *processManager) start(p Process) error {
const op = "pm2.start"
if err := m.pm2(p, "start"); err != nil {
return &standarderror.Error{Op: op, Err: err}
}
p.warmup()
if !p.viable() {
attempts := 0
for attempts < 5 && !p.viable() {
if err := m.pm2(p, "restart"); err != nil {
m.heuristicState = errorState
return &standarderror.Error{Op: op, Err: err}
}
p.warmup()
attempts++
}
if !p.viable() {
m.heuristicState = errorState
return &standarderror.Error{
Op: op,
Message: fmt.Sprintf("failed to launch %s", p.Fullname()),
}
}
}
m.heuristicState = runningState
return nil
}
func (m *processManager) shutdown(p Process) error {
const op = "pm2.shutdown"
if m.heuristicState != runningState {
return nil
}
if err := m.pm2(p, "stop"); err != nil {
m.heuristicState = errorState
return &standarderror.Error{Op: op, Err: err}
}
m.heuristicState = stoppedState
return nil
}
func (m *processManager) pm2(p Process, cmdName string) error {
const op = "pm2.pm2"
cmdArgs := []string{
cmdName,
p.name(),
}
if cmdName == "start" {
cmdArgs = append(cmdArgs, "--interpreter=none", "--")
cmdArgs = append(cmdArgs, p.args()...)
}
cmd := exec.Command(
"pm2",
cmdArgs...,
)
m.logger.DebugfOp(op, "executing command: %s", strings.Join(cmd.Args, " "))
processStdOut, err := cmd.StdoutPipe()
if err != nil {
return &standarderror.Error{Op: op, Err: err}
}
processStdErr, err := cmd.StderrPipe()
if err != nil {
return &standarderror.Error{Op: op, Err: err}
}
readFromPipe := func(outputType string, reader io.ReadCloser) {
readFromPipeOp := fmt.Sprintf("pm2.%s.%s", p.name(), outputType)
r := bufio.NewReader(reader)
defer reader.Close() // nolint: errcheck
for {
line, _, err := r.ReadLine()
if err != nil {
if err != io.EOF {
m.logger.ErrorOp(readFromPipeOp, err)
}
break
}
if len(line) != 0 {
m.logger.DebugfOp(readFromPipeOp, string(line))
}
}
}
go readFromPipe("stdout", processStdOut)
go readFromPipe("stderr", processStdErr)
if err := cmd.Start(); err != nil {
return &standarderror.Error{Op: op, Err: err}
}
return nil
}