package engine import ( "context" "io" "os/exec" "strings" "time" "github.com/nalgeon/codapi/internal/execy" "github.com/nalgeon/codapi/internal/logx" ) // A Program is an executable program. type Program struct { timeout time.Duration nOutput int64 } // NewProgram creates a new program. func NewProgram(timeoutSec int, nOutput int64) *Program { return &Program{ timeout: time.Duration(timeoutSec) * time.Second, nOutput: nOutput, } } // Run starts the program and waits for it to complete (or timeout). func (p *Program) Run(id, name string, arg ...string) (stdout string, stderr string, err error) { return p.RunStdin(nil, id, name, arg...) } // RunStdin starts the program with data from stdin // and waits for it to complete (or timeout). func (p *Program) RunStdin(stdin io.Reader, id, name string, arg ...string) (stdout string, stderr string, err error) { ctx, cancel := context.WithTimeout(context.Background(), p.timeout) defer cancel() var cmdout, cmderr strings.Builder cmd := exec.CommandContext(ctx, name, arg...) cmd.Cancel = func() error { err := cmd.Process.Kill() logx.Debug("%s: execution timeout, killed process=%d, err=%v", id, cmd.Process.Pid, err) if arg[0] == actionRun { // we have to "docker kill" the container here, because the process // inside the container is not related to the "docker run" process, // and will hang forever after the "docker run" process is killed go func() { err = dockerKill(id) if err == nil { logx.Debug("%s: docker kill ok", id) } else { logx.Log("%s: docker kill failed: %v", id, err) } }() } return err } cmd.Stdin = stdin cmd.Stdout = LimitWriter(&cmdout, p.nOutput) cmd.Stderr = LimitWriter(&cmderr, p.nOutput) err = execy.Run(cmd) stdout = strings.TrimSpace(cmdout.String()) stderr = strings.TrimSpace(cmderr.String()) return }