mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-18 13:12:17 +01:00
process balancing: better implementation (wip)
This commit is contained in:
365
internal/pkg/prinery/pm2.go
Normal file
365
internal/pkg/prinery/pm2.go
Normal file
@@ -0,0 +1,365 @@
|
||||
package prinery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/conf"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xexec"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
|
||||
)
|
||||
|
||||
type command string
|
||||
|
||||
const (
|
||||
startCommand command = "start"
|
||||
restartCommand command = "restart"
|
||||
stopCommand command = "stop"
|
||||
logsCommand command = "logs"
|
||||
jlistCommand command = "jlist"
|
||||
)
|
||||
|
||||
const maximumRestartAttempts uint = 3
|
||||
|
||||
type pm2 struct {
|
||||
logger xlog.Logger
|
||||
config conf.Config
|
||||
chromeUnit *unit
|
||||
sofficeUnit *unit
|
||||
processes []process
|
||||
}
|
||||
|
||||
func NewPM2Prinery(logger xlog.Logger, config conf.Config) (Prinery, error) {
|
||||
const op string = "prinery.NewPM2Prinery"
|
||||
resolver := func() (*pm2, error) {
|
||||
m := &pm2{
|
||||
logger: logger,
|
||||
config: config,
|
||||
}
|
||||
chromeProcesses := newChromeProcesses(config.GoogleChromeInstances())
|
||||
if len(chromeProcesses) > 0 {
|
||||
unit, err := newUnit(logger, processesToSpecs(chromeProcesses))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.chromeUnit = unit
|
||||
}
|
||||
sofficeProcesses := newSofficesProcesses(config.SofficeInstances())
|
||||
if len(sofficeProcesses) > 0 {
|
||||
unit, err := newUnit(logger, processesToSpecs(sofficeProcesses))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.sofficeUnit = unit
|
||||
}
|
||||
m.processes = append(chromeProcesses, sofficeProcesses...)
|
||||
return m, nil
|
||||
}
|
||||
p, err := resolver()
|
||||
if err != nil {
|
||||
return nil, xerror.New(op, err)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (m *pm2) Start(emergency chan error) error {
|
||||
const op string = "prinery.pm2.Start"
|
||||
// start processes.
|
||||
// those lines "works" but processes
|
||||
// fail to start...
|
||||
/*var wg sync.WaitGroup
|
||||
result := make(chan error, len(m.processes))
|
||||
for _, proc := range m.processes {
|
||||
wg.Add(1)
|
||||
go func(proc process, result chan error) {
|
||||
result <- m.start(proc)
|
||||
wg.Done()
|
||||
}(proc, result)
|
||||
}
|
||||
wg.Wait()
|
||||
close(result)
|
||||
for err := range result {
|
||||
if err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
}*/
|
||||
for _, proc := range m.processes {
|
||||
if err := m.start(proc); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
}
|
||||
// start units.
|
||||
if m.chromeUnit != nil {
|
||||
go m.chromeUnit.start(emergency)
|
||||
}
|
||||
if m.sofficeUnit != nil {
|
||||
go m.sofficeUnit.start(emergency)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) HTML(
|
||||
ctx context.Context,
|
||||
logger xlog.Logger,
|
||||
dest, fpath string,
|
||||
opts ChromePrintOptions,
|
||||
) error {
|
||||
const op string = "prinery.pm2.HTML"
|
||||
resolver := func() error {
|
||||
if m.chromeUnit == nil {
|
||||
return errors.New("cannot handle HTML print request as there is no chromeUnit")
|
||||
}
|
||||
URL := fmt.Sprintf("file://%s", fpath)
|
||||
p := chromePrinter{
|
||||
logger: logger,
|
||||
url: URL,
|
||||
opts: opts,
|
||||
}
|
||||
req := request{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
printer: p,
|
||||
dest: dest,
|
||||
result: make(chan error),
|
||||
}
|
||||
m.chromeUnit.dispatch(req)
|
||||
err := <-req.result
|
||||
return err
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) URL(
|
||||
ctx context.Context,
|
||||
logger xlog.Logger, dest, URL string,
|
||||
opts ChromePrintOptions,
|
||||
) error {
|
||||
const op string = "prinery.pm2.URL"
|
||||
resolver := func() error {
|
||||
if m.chromeUnit == nil {
|
||||
return errors.New("cannot handle URL print request as there is no chromeUnit")
|
||||
}
|
||||
p := chromePrinter{
|
||||
logger: logger,
|
||||
url: URL,
|
||||
opts: opts,
|
||||
}
|
||||
req := request{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
printer: p,
|
||||
dest: dest,
|
||||
result: make(chan error),
|
||||
}
|
||||
m.chromeUnit.dispatch(req)
|
||||
err := <-req.result
|
||||
return err
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) Markdown(
|
||||
ctx context.Context,
|
||||
logger xlog.Logger,
|
||||
dest, fpath string,
|
||||
opts ChromePrintOptions,
|
||||
) error {
|
||||
const op string = "prinery.pm2.Markdown"
|
||||
resolver := func() error {
|
||||
if m.chromeUnit == nil {
|
||||
return errors.New("cannot handle Markdown print request as there is no chromeUnit")
|
||||
}
|
||||
p, err := newMarkdownPrinter(logger, fpath, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := request{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
printer: p,
|
||||
dest: dest,
|
||||
result: make(chan error),
|
||||
}
|
||||
m.chromeUnit.dispatch(req)
|
||||
err = <-req.result
|
||||
return err
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) Office(
|
||||
ctx context.Context,
|
||||
logger xlog.Logger,
|
||||
dest string,
|
||||
fpaths []string,
|
||||
opts UnoconvPrintOptions,
|
||||
) error {
|
||||
const op string = "prinery.pm2.Office"
|
||||
resolver := func() error {
|
||||
if m.sofficeUnit == nil {
|
||||
return errors.New("cannot handle Office print request as there is no sofficeUnit")
|
||||
}
|
||||
p := unoconvPrinter{
|
||||
logger: logger,
|
||||
fpaths: fpaths,
|
||||
opts: opts,
|
||||
}
|
||||
req := request{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
printer: p,
|
||||
dest: dest,
|
||||
result: make(chan error),
|
||||
}
|
||||
m.sofficeUnit.dispatch(req)
|
||||
err := <-req.result
|
||||
return err
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) Merge(
|
||||
ctx context.Context,
|
||||
logger xlog.Logger,
|
||||
dest string,
|
||||
fpaths []string,
|
||||
) error {
|
||||
const op string = "prinery.pm2.Merge"
|
||||
p := mergePrinter{
|
||||
logger: logger,
|
||||
fpaths: fpaths,
|
||||
}
|
||||
if err := p.print(ctx, nil, dest); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) start(p process) error {
|
||||
const op string = "prinery.pm2.start"
|
||||
resolver := func() error {
|
||||
// first, we try to start the process.
|
||||
if err := m.run(startCommand, p); err != nil {
|
||||
return err
|
||||
}
|
||||
// we wait the process to be ready.
|
||||
m.warmup(p)
|
||||
// if the process failed to start correctly,
|
||||
// we have to restart it.
|
||||
if !m.isViable(p) && maximumRestartAttempts > 0 {
|
||||
return m.restart(p)
|
||||
}
|
||||
// the process is viable, let's log its
|
||||
// output.
|
||||
return m.logs(p)
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) restart(p process) error {
|
||||
const op string = "prinery.pm2.restart"
|
||||
resolver := func() error {
|
||||
var attempts uint
|
||||
for attempts < maximumRestartAttempts {
|
||||
// we restart the process.
|
||||
if err := m.run(restartCommand, p); err != nil {
|
||||
return err
|
||||
}
|
||||
// we wait the process to be ready.
|
||||
m.warmup(p)
|
||||
attempts++
|
||||
// if the process is viable, we
|
||||
// leave.
|
||||
if m.isViable(p) {
|
||||
return m.logs(p)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to start '%s'", p.spec().id())
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) isViable(p process) bool {
|
||||
if !p.viabilityFunc()(m.logger) {
|
||||
return false
|
||||
}
|
||||
/*m.listLock.Lock()
|
||||
defer m.listLock.Unlock()
|
||||
return m.list.isOnline(p)*/
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *pm2) warmup(p process) {
|
||||
const op string = "prinery.pm2.warmup"
|
||||
warmupTime := p.warmupTime()
|
||||
m.logger.DebugfOp(
|
||||
op,
|
||||
"waiting '%v' for allowing '%s' to warmup",
|
||||
warmupTime,
|
||||
p.spec().id(),
|
||||
)
|
||||
time.Sleep(warmupTime)
|
||||
}
|
||||
|
||||
func (m *pm2) logs(p process) error {
|
||||
const op string = "prinery.pm2.logs"
|
||||
if m.config.LogLevel() == xlog.DebugLevel {
|
||||
if err := m.run(logsCommand, p); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pm2) run(pm2Cmd command, p process) error {
|
||||
const op string = "prinery.pm2.run"
|
||||
resolver := func() error {
|
||||
args := []string{
|
||||
string(pm2Cmd),
|
||||
}
|
||||
if pm2Cmd == startCommand {
|
||||
args = append(args, p.binary())
|
||||
args = append(args, fmt.Sprintf("--name=%s", p.spec().id()))
|
||||
args = append(args, "--interpreter=none", "--")
|
||||
args = append(args, p.args()...)
|
||||
} else {
|
||||
args = append(args, p.spec().id())
|
||||
}
|
||||
cmd, err := xexec.Command(m.logger, "pm2", args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xexec.LogBeforeExecute(m.logger, cmd)
|
||||
return cmd.Start()
|
||||
}
|
||||
if err := resolver(); err != nil {
|
||||
return xerror.New(op, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = Prinery(new(pm2))
|
||||
)
|
||||
Reference in New Issue
Block a user