environment/exec: Seperate Exec and Wait

This commit is contained in:
Tom Wiesing 2022-12-14 08:53:45 +01:00
parent 2a308ee03c
commit a590d93e76
No known key found for this signature in database
14 changed files with 90 additions and 117 deletions

View file

@ -4,15 +4,18 @@ import (
"context"
"os/exec"
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
"github.com/rs/zerolog"
"github.com/tkw1536/goprogram/stream"
)
// Exec executes a system command with the specified input/output streams, working directory, and arguments.
//
// If the command executes, it's exit code will be returned.
// If the command can not be executed, returns [ExecCommandError].
func (*Native) Exec(ctx context.Context, io stream.IOStream, workdir string, exe string, argv ...string) int {
// The command is started immediatly.
// The returned function is guaranteed to be non-nil and returns an exit code.
//
// If the command executes, the returns the exit code as soon as the process executes.
// If the command can not be executed, the returned function is [ExecCommandErrorFunc] and returns [ExecCommandError].
func (*Native) Exec(ctx context.Context, io stream.IOStream, workdir string, exe string, argv ...string) func() int {
// setup the command
cmd := exec.Command(exe, argv...)
cmd.Dir = workdir
@ -20,40 +23,53 @@ func (*Native) Exec(ctx context.Context, io stream.IOStream, workdir string, exe
cmd.Stdout = io.Stdout
cmd.Stderr = io.Stderr
// run the process in a cancelable fashion
err, cErr := cancel.WithContext(ctx, func(cancelable func()) error {
// start the process
err := cmd.Start()
if err != nil {
return err
}
// allow it to be cancellable
cancelable()
// and wait for the rest of the process
return cmd.Wait()
}, func() {
if cmd.Process != nil {
cmd.Process.Kill()
}
})
if err == nil {
err = cErr
// context is already cancelled, don't run it!
if err := ctx.Err(); err != nil {
return ExecCommandErrorFunc
}
// non-zero exit
if err, ok := err.(*exec.ExitError); ok {
return err.ExitCode()
}
// unknown error
// start the command, but if something happens, return nil
err := cmd.Start()
zerolog.Ctx(ctx).Debug().Str("exe", exe).Strs("argv", argv).Err(err).Msg("exec.Command.Start")
if err != nil {
return ExecCommandError
return ExecCommandErrorFunc
}
// everything is fine!
return 0
waitdone := make(chan struct{}) // closed once Wait() below returns
alldone := make(chan struct{}) // closed once the kill goroutine exits
go func() {
defer close(alldone)
select {
case <-ctx.Done():
err := cmd.Process.Kill()
zerolog.Ctx(ctx).Debug().Str("exe", exe).Strs("argv", argv).Err(err).Msg("exec.Command.Kill")
case <-waitdone:
}
}()
// create a new command
return func() int {
defer func() {
// wait for the goroutine to exit
close(waitdone)
<-alldone
}()
err := cmd.Wait()
zerolog.Ctx(ctx).Debug().Str("exe", exe).Strs("argv", argv).Err(err).Msg("exec.Command.Wait")
// non-zero exit
if err, ok := err.(*exec.ExitError); ok {
return err.ExitCode()
}
if err != nil {
return ExecCommandError
}
return 0
}
}
func (n *Native) LookPathAbs(file string) (string, error) {