mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-11 18:02:14 +01:00
125 lines
2.7 KiB
Go
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
|
|
}
|