【问题标题】:How to understand if exec.cmd was canceled如何了解 exec.cmd 是否被取消
【发布时间】:2020-04-02 11:13:43
【问题描述】:

当命令被上下文取消时,我试图返回特定错误。 在调查 ProcessState 后了解到,如果在 exitCode 中得到 -1,则进程得到终止信号 https://golang.org/pkg/os/#ProcessState.ExitCode 但也许我们有更优雅的方式? 也许我可以从取消功能中删除这个错误? 也许 exitCode 不足以理解命令是否被取消?

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
}

【问题讨论】:

    标签: go exec


    【解决方案1】:

    exec.ExitError 没有提供任何退出代码的原因(没有相关的 struct 字段,也没有 Unwrap 方法),因此您必须直接检查上下文:

    if ctx.Err() != nil {
        log.Println("canceled")
    }   
    

    请注意,这是一场轻微的比赛,因为在命令因其他原因失败后可能会取消上下文,但您对此无能为力。

    【讨论】:

      【解决方案2】:

      没有直接或优雅的方法来确定进程是否因为上下文被取消而被终止。你能来的最接近的是:

      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
      }
      

      这里的问题是可能存在竞争条件,因此返回 ctx.Err() 可能会产生误导。例如,想象以下场景:

      1. 进程开始。
      2. 进程被外部参与者杀死。
      3. 上下文被取消。
      4. 您检查上下文。

      此时,上面的函数会返回ctx.Err(),但这可能会产生误导,因为进程被杀死的原因并不是因为上下文被取消了。如果您决定使用类似于上述函数的代码,请记住这个近似值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-18
        • 2020-02-24
        • 2016-08-29
        • 2021-10-07
        • 1970-01-01
        相关资源
        最近更新 更多