This commit is contained in:
Julien Neuhart
2019-04-14 17:07:45 +02:00
committed by GitHub
parent c4222e0981
commit 194670c3bf
63 changed files with 2114 additions and 1503 deletions

View File

@@ -2,30 +2,36 @@ package pm2
import (
"context"
"fmt"
"time"
"github.com/mafredri/cdp/devtool"
"github.com/thecodingmachine/gotenberg/internal/pkg/notify"
)
// Chrome facilitates starting or shutting down
// Chrome headless with PM2.
type Chrome struct{}
// Launch starts Chrome headless with PM2.
func (c *Chrome) Launch() error {
return launch(c)
type chrome struct {
manager *processManager
}
// Shutdown stops Chrome headless and
// removes it from the list of PM2
// processes.
func (c *Chrome) Shutdown() error {
return shutdown(c)
// NewChrome retruns a Google Chrome
// headless process.
func NewChrome() Process {
return &chrome{
manager: &processManager{},
}
}
func (c *Chrome) getArgs() []string {
func (p *chrome) Fullname() string {
return "Google Chrome headless"
}
func (p *chrome) Start() error {
return p.manager.start(p)
}
func (p *chrome) Shutdown() error {
return p.manager.shutdown(p)
}
func (p *chrome) args() []string {
return []string{
"--no-sandbox",
"--headless",
@@ -44,28 +50,23 @@ func (c *Chrome) getArgs() []string {
}
}
func (c *Chrome) getName() string {
func (p *chrome) name() string {
return "google-chrome-stable"
}
func (c *Chrome) getFullname() string {
return "Chrome headless"
}
func (c *Chrome) isViable() bool {
// check if Chrome is correctly running.
func (p *chrome) viable() bool {
// check if Google Chrome is correctly running.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err := devtool.New("http://localhost:9222").Version(ctx)
return err == nil
}
func (c *Chrome) warmup() {
notify.Println(fmt.Sprintf("warming-up %s", c.getFullname()))
func (p *chrome) warmup() {
time.Sleep(5 * time.Second)
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Process(new(Chrome))
_ = Process(new(chrome))
)

View File

@@ -6,14 +6,14 @@ import (
"github.com/stretchr/testify/require"
)
func TestChromeLaunch(t *testing.T) {
p := &Chrome{}
err := p.Launch()
func TestChromeStart(t *testing.T) {
p := NewChrome()
err := p.Start()
require.Nil(t, err)
}
func TestChromeShutdown(t *testing.T) {
p := &Chrome{}
p := NewChrome()
err := p.Shutdown()
require.Nil(t, err)
}

View File

@@ -2,7 +2,7 @@
Package pm2 facilitates starting external
processes on which our API depends.
For instance, it starts Chrome headless and
For instance, it may start Google Chrome headless and
unoconv listener with PM2.
The PM2 process manager launch those processes and keep

View File

@@ -3,69 +3,81 @@ package pm2
import (
"fmt"
"os/exec"
"github.com/thecodingmachine/gotenberg/internal/pkg/notify"
)
// Process is a type that can launch or
const (
stoppedState = iota
runningState
errorState
)
// Process is a type that can start or
// shutdown a process with PM2.
type Process interface {
Launch() error
Fullname() string
Start() error
Shutdown() error
getArgs() []string
getName() string
getFullname() string
isViable() bool
args() []string
name() string
viable() bool
warmup()
}
const maxRestartAttempts int = 5
var humanNames = map[string]string{
"start": "started",
"restart": "restarted",
"stop": "stopped",
type processManager struct {
heuristicState int32
}
func launch(p Process) error {
if err := run(p, "start"); err != nil {
func (m *processManager) start(p Process) error {
if err := m.pm2(p, "start"); err != nil {
return err
}
p.warmup()
if !p.isViable() {
if !p.viable() {
attempts := 0
for attempts < maxRestartAttempts && !p.isViable() {
run(p, "restart")
for attempts < 5 && !p.viable() {
if err := m.pm2(p, "restart"); err != nil {
m.heuristicState = errorState
return err
}
p.warmup()
attempts++
}
if !p.isViable() {
return fmt.Errorf("failed to launch %s", p.getFullname())
if !p.viable() {
m.heuristicState = errorState
return fmt.Errorf("failed to launch %s", p.Fullname())
}
}
m.heuristicState = runningState
return nil
}
func shutdown(p Process) error {
return run(p, "stop")
func (m *processManager) shutdown(p Process) error {
if m.heuristicState != runningState {
return nil
}
if err := m.pm2(p, "stop"); err != nil {
m.heuristicState = errorState
return err
}
m.heuristicState = stoppedState
return nil
}
func run(p Process, cmdName string) error {
func (m *processManager) pm2(p Process, cmdName string) error {
cmdArgs := []string{
cmdName,
p.getName(),
p.name(),
}
if cmdName == "start" {
cmdArgs = append(cmdArgs, "--interpreter none", "--")
cmdArgs = append(cmdArgs, p.getArgs()...)
cmdArgs = append(cmdArgs, p.args()...)
}
cmd := exec.Command(
"pm2",
cmdArgs...,
)
if err := cmd.Start(); err != nil {
return fmt.Errorf("%s %s with PM2: %v", cmdName, p.getFullname(), err)
return fmt.Errorf("%s %s with PM2: %v", cmdName, p.Fullname(), err)
}
notify.Println(fmt.Sprintf("%s %s with PM2", p.getFullname(), humanNames[cmdName]))
return nil
}

View File

@@ -1,47 +1,52 @@
package pm2
// Unoconv facilitates starting or shutting down
// unoconv listener with PM2.
type Unoconv struct{}
// Launch starts unoconv listener with PM2.
func (u *Unoconv) Launch() error {
return launch(u)
type unoconv struct {
manager *processManager
}
// Shutdown stops unoconv listener and
// removes it from the list of PM2
// processes.
func (u *Unoconv) Shutdown() error {
return shutdown(u)
// NewUnoconv retruns a unoconv listener
// process.
func NewUnoconv() Process {
return &unoconv{
manager: &processManager{},
}
}
func (u *Unoconv) getArgs() []string {
func (p *unoconv) Fullname() string {
return "unoconv listener"
}
func (p *unoconv) Start() error {
return p.manager.start(p)
}
func (p *unoconv) Shutdown() error {
return p.manager.shutdown(p)
}
func (p *unoconv) args() []string {
return []string{
"--listener",
"--verbose",
}
}
func (u *Unoconv) getName() string {
func (p *unoconv) name() string {
return "unoconv"
}
func (u *Unoconv) getFullname() string {
return "unoconv listener"
}
func (u *Unoconv) isViable() bool {
func (p *unoconv) viable() bool {
// TODO find a way to check if
// unoconv is correctly started?
// the unoconv listener
// is correctly started?
return true
}
func (u *Unoconv) warmup() {
func (p *unoconv) warmup() {
// let's do nothing.
}
// Compile-time checks to ensure type implements desired interfaces.
var (
_ = Process(new(Unoconv))
_ = Process(new(unoconv))
)

View File

@@ -6,14 +6,14 @@ import (
"github.com/stretchr/testify/require"
)
func TestUnoconvLaunch(t *testing.T) {
p := &Unoconv{}
err := p.Launch()
func TestUnoconvStart(t *testing.T) {
p := NewUnoconv()
err := p.Start()
require.Nil(t, err)
}
func TestUnoconvShutdown(t *testing.T) {
p := &Unoconv{}
p := NewUnoconv()
err := p.Shutdown()
require.Nil(t, err)
}