refactoring process management: no more orphan processes and quicker chrome startup

This commit is contained in:
Julien Neuhart
2019-10-02 10:38:47 +02:00
parent c47dc5ecdb
commit 7ac104c3c6
8 changed files with 187 additions and 137 deletions

View File

@@ -8,6 +8,7 @@ import (
"io"
"os/exec"
"strings"
"syscall"
"github.com/thecodingmachine/gotenberg/internal/pkg/xerror"
"github.com/thecodingmachine/gotenberg/internal/pkg/xlog"
@@ -43,6 +44,61 @@ func CommandContext(ctx context.Context, logger xlog.Logger, binary string, args
return cmd, nil
}
/*
Run runs a command.
If command finishes or fails to finish
before context.Context deadline, kill the
corresponding process in a way which does
not leak orphan processes.
*/
func Run(ctx context.Context, logger xlog.Logger, binary string, args ...string) error {
const op string = "xexec.Run"
resolver := func() error {
cmd, err := Command(
logger,
binary,
args...,
)
if err != nil {
return err
}
LogBeforeExecute(logger, cmd)
// see https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773.
kill := func() {
err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
if err == nil {
return
}
if !strings.Contains(err.Error(), "no such process") {
logger.ErrorOp(op, err)
}
}
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err := cmd.Start(); err != nil {
return err
}
result := make(chan error, 1)
go func() {
result <- cmd.Wait()
}()
select {
case err := <-result:
logger.DebugfOp(op, "command '%s' finished", strings.Join(cmd.Args, " "))
kill()
return err
case <-ctx.Done():
logger.DebugfOp(op, "command '%s' failed to finish before context.Context deadline", strings.Join(cmd.Args, " "))
kill()
return ctx.Err()
}
}
if err := resolver(); err != nil {
return xerror.New(op, err)
}
return nil
}
// LogBeforeExecute logs a command before its execution.
func LogBeforeExecute(logger xlog.Logger, cmd *exec.Cmd) {
const op string = "xexec.LogBeforeExecute"
@@ -87,7 +143,7 @@ func logCommandOutput(logger xlog.Logger, reader io.ReadCloser, outputType strin
for {
line, _, err := r.ReadLine()
if err != nil {
if err != io.EOF {
if err != io.EOF && !strings.Contains(err.Error(), "file already closed") {
logger.ErrorOp(op, err)
}
break