Как понять, если exe c .cmd был отменен - PullRequest
0 голосов
/ 02 апреля 2020

Я пытаюсь вернуть указанную ошибку c, когда команда была отменена контекстом. После исследования 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
}

Ответы [ 2 ]

1 голос
/ 02 апреля 2020

exe c .ExitError не предоставляет никаких причин для кода выхода (нет соответствующего структурного поля или метода Unwrap), поэтому вы должны проверить контекст напрямую:

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

Обратите внимание, что это небольшая гонка, поскольку контекст может быть отменен сразу после сбоя команды по другой причине, но с этим ничего не поделаешь.

0 голосов
/ 02 апреля 2020

Не существует простого или элегантного способа выяснить, был ли процесс уничтожен из-за отмены контекста. Самое близкое, что вы можете сделать, это:

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(), но это может вводить в заблуждение, поскольку причина, по которой процесс был убит, заключается не в том, что контекст был отменен , Если вы решили использовать код, аналогичный функции, описанной выше, помните об этом приближении.

...