I'm trying to return specific error when the command was canceled by context. After investigating ProcessState understood that if got -1 in exitCode the process got terminate signal https://golang.org/pkg/os/#ProcessState.ExitCode but maybe we have more elegant way? Maybe I can put this error from cancel function? Maybe it isn't good enough exitCode for understanding if the command was canceled?
var (
CmdParamsErr = errors.New("failed to get params for execution command")
ExecutionCanceled = errors.New("command canceled")
)
func execute(m My) error {
filePath, args, err := cmdParams(m)
err = nil
if err != nil {
log.Infof("cmdParams: err: %v\n, m: %v\n", err, m)
return CmdParamsErr
}
var out bytes.Buffer
var errStd bytes.Buffer
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, filePath, args...)
cmd.Stdout = &out
cmd.Stderr = &errStd
err = cmd.Run()
if err != nil {
if cmd.ProcessState.ExitCode() == -1 {
log.Warnf("execution was canceled by signal, err: %v\n", err)
err = ExecutionCanceled
return err
} else {
log.Errorf("run failed, err: %v, filePath: %v, args: %v\n", err, filePath, args)
return err
}
}
return err
}
exec.ExitError doesn't provide any reason for the exit code (there is no relevant struct field nor an Unwrap method), so you have to check the context directly:
if ctx.Err() != nil {
log.Println("canceled")
}
Note that this is a slight race because the context may be canceled just after the command failed for a different reason, but there is nothing you can do about that.
There is no straightforward or elegant way to figure out if a process was killed because a context was canceled. The closest you can come is this:
func run() error {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-c", "exit 1")
// Start() returns an error if the process can't be started. It will return
// ctx.Err() if the context is expired before starting the process.
if err := cmd.Start(); err != nil {
return err
}
if err := cmd.Wait(); err != nil {
if e, ok := err.(*exec.ExitError); ok {
// If the process exited by itself, just return the error to the
// caller.
if e.Exited() {
return e
}
// We know now that the process could be started, but didn't exit
// by itself. Something must have killed it. If the context is done,
// we can *assume* that it has been killed by the exec.Command.
// Let's return ctx.Err() so our user knows that this *might* be
// the case.
select {
case <-ctx.Done():
return ctx.Err()
default:
return e
}
}
return err
}
return nil
}
The problem here is that there might be a race condition, so returning ctx.Err()
might be misleading. For example, imagine the following scenario:
At this point, the function above would return ctx.Err()
, but this might be misleading because the reason why the process was killed is not because the context was canceled. If you decide to use a code similar to the function above, keep in mind this approximation.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.