Go func 运行外部命令,有时会出现错误:文件已关闭

问题描述 投票:0回答:1

我的功能

func RunCommand(cmdPath string, cmdArgs string) (error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    cmd := exec.CommandContext(ctx, cmdPath, cmdArgs)
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return err
    }
    defer stdout.Close()
    scanner := bufio.NewScanner(stdout)
    err := cmd.Start()
    if err != nil {
        return err
    }

    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()
        for scanner.Scan() {
            doSomething(scanner.Text())
        }
        if scanner.Err() != nil {
            log.Errf("Error reading stdout, %v", scanner.Err())
        }
    }()

    done := make(chan error, 1)
    go func() {
        done <- cmd.Wait()
    }()
    select {
    case <-ctx.Done():
        return ctx.Err()
    case err := <-done:
        if err != nil {
            return err
        }
    }
    wg.Wait()

    return nil
}

我只能在出现此错误时重现此问题几次:读取标准输出时出错,读取 |0:文件已关闭。我的理解是我在检查 Scanner.Err() 之前关闭标准输出,但为什么会发生这种情况。我在 go 例程中使用 cmd.Wait() ,它应该等待 Scanner.Err() 然后关闭标准输出,对吗?

go
1个回答
0
投票

该错误很可能是由子进程意外终止引起的。

考虑这个例子。 我们每四分之一秒开始一次 bash 循环打印。 但一秒钟后,一个 goroutine 通过 pid 杀死了该进程。 其输出如下。

package main

import (
    "bufio"
    "context"
    "log"
    "os"
    "os/exec"
    "sync"
    "syscall"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    cmd := exec.CommandContext(ctx, "sh", "-c", "for i in $(seq 1 10); do echo $i; sleep 0.25; done")

    cmd.Stderr = os.Stderr
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    defer stdout.Close()
    scanner := bufio.NewScanner(stdout)
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }
    go func() { time.Sleep(1 * time.Second); syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) }()
    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()
        for scanner.Scan() {
            log.Print(scanner.Text())
        }
        if scanner.Err() != nil {
            log.Printf("Error reading stdout, %v", scanner.Err())
        }
    }()

    done := make(chan error, 1)
    go func() {
        done <- cmd.Wait()
    }()
    select {
    case <-ctx.Done():
        log.Fatal(ctx.Err())
    case err := <-done:
        if err != nil {
            log.Fatal(err)
        }
    }
    wg.Wait()

}
2024/09/18 22:34:44 1
2024/09/18 22:34:45 2
2024/09/18 22:34:45 3
2024/09/18 22:34:45 4
2024/09/18 22:34:45 Error reading stdout, read |0: file already closed
2024/09/18 22:34:45 signal: killed
exit status 1
© www.soinside.com 2019 - 2024. All rights reserved.