如何阻止goroutine被外部I / O阻止为进程启动?

时间:2017-04-16 11:29:44

标签: go pipe exec goroutine

我在这里遇到问题,我无法安全退出goroutine。

我正在使用exec.Command创建外部进程(存储进程的cm​​d,stdin管道和stdout管道):

exec.Command(args[0], args[1]...) // args[0] is a base command

每当需要启动我正在调用的过程时:

cmd.Start()

然后在开始和附加时我正在运行2个goroutines:

shutdown := make(chan struct{})
// Run the routine which will read from process and send the data to CmdIn channel
go pr.cmdInRoutine()
// Run the routine which will read from CmdOut and write to process
go pr.cmdOutRoutine()

cmdInRoutine:

func (pr *ExternalProcess) cmdInRoutine() {
    app.At(te, "cmdInRoutine")

    for {
        println("CMDINROUTINE")
        select {
        case <-pr.shutdown:
            println("!!! Shutting cmdInRoutine down !!!")
            return
        default:
            println("Inside the for loop of the CmdInRoutine")
            if pr.stdOutPipe == nil {
                println("!!! Standard output pipe is nil. Sending Exit Request !!!")
                pr.ProcessExit <- true
                close(pr.shutdown)
                return
            }

            buf := make([]byte, 2048)

            size, err := pr.stdOutPipe.Read(buf)
            if err != nil {
                println("!!! Sending exit request from cmdInRoutine !!!")
                pr.ProcessExit <- true
                close(pr.shutdown)
                return
            }

            println("--- Received data for sending to CmdIn:", string(buf[:size]))
            pr.CmdIn <- buf[:size]
        }

    }
}

cmdOutRoutine:

func (pr *ExternalProcess) cmdOutRoutine() {
    app.At(te, "cmdOutRoutine")

    for {
        select {
        case <-pr.shutdown:
            println("!!! Shutting cmdOutRoutine down !!!")
            return
        case data := <-pr.CmdOut:
            println("Received data for sending to Process: ", data)
            if pr.stdInPipe == nil {
                println("!!! Standard input pipe is nil. Sending Exit Request !!!")
                pr.ProcessExit <- true
                return
            }

            println("--- Received input to write to external process:", string(data))
            _, err := pr.stdInPipe.Write(append(data, '\n'))
            if err != nil {
                println("!!! Couldn't Write To the std in pipe of the process !!!")
                pr.ProcessExit <- true
                return
            }
        }
    }
}

有趣的案例:

1)当进程在中发送EOF(不介意 pr.ProcessExit&lt; - true 我使用通道来通知父处理程序停止并退出进程时) cmdInRoutine 我也关闭了关闭通道,这让 cmdOutRoutine 退出,因为在select语句中没有默认情况,因此它会阻塞并等待退出或数据然后写入使用存储的 stdInPipe 来运行流程。

2)当我想停止goroutine但是让进程保持运行,即暂停读写时我正在关闭关闭通道,希望这两个goroutine将结束。    
- cmdOutRoutine 打印 !!!关闭cmdOutRoutine !!! 因为select没有默认情况并且关闭关闭通道会导致几乎立即返回    
- cmdOutRoutine 没有打印任何东西,我有一种奇怪的感觉,它甚至没有返回,我认为因为它在默认情况下被阻止从stdInPipe读取

我考虑在for循环之前在 cmdOutRoutine 中运行另一个goroutine,并将进程的 stdIn 数据转换为通道然后我就可以消除默认值 cmdInRoutine 中的情况,但这会产生另一个问题,即必须停止新的goroutine,它仍将被正在运行的进程的 stdIn 的读取阻止。

任何想法如何解决这个问题(修改逻辑)以满足随时关闭和启动goroutine(进程I / O)的需要,而不是运行进程本身?或者有没有办法避免阻止读写的调用,我还不知道呢?

非常感谢。

1 个答案:

答案 0 :(得分:2)

它可能会在pr.stdOutPipe.Read(buf)被屏蔽。您可以尝试关闭pr.stdOutPipe,这应该会中断Read。

您也可以关闭pr.stdInPipe,以确保写入不会阻止。

编辑:这不允许您重新连接,但没有其他方法可以中断该读取。在整个过程中保持这两个goroutine运行可能会更好,并暂停堆栈中的其他位置(例如,如果您不想在暂停状态下接收命令的输出,不要bufpr.CmdIn - 但要小心避免竞争条件。)

关闭当前版本的go:issue 6817可能会出错。

编辑结束

另外,请注意pr.CmdIn。如果结束stdOutPipe没有导致Read返回错误,cmdInRoutine将尝试写入该频道。如果没有任何内容读取,cmdInRoutine将永远阻止。我会将pr.stdOutPipe.Read(buf)移出选区,然后将pr.CmdIn <- buf[:size]作为另一个案例添加到选择中:

func (pr *ExternalProcess) cmdInRoutine() {
    app.At(te, "cmdInRoutine")

    // this check should probably only happen once.
    // if stdOutPipe can change during the loop,
    // then that's a race condition.
    if pr.stdOutPipe == nil {
        println("!!! Standard output pipe is nil. Sending Exit Request !!!")
        pr.ProcessExit <- true
        close(pr.shutdown)
        return
    }

    for {
        println("CMDINROUTINE")
        // we need to allocate a new buffer in each iteration,
        // because when we pass it through the channel,
        // we can no longer safely overwrite the data in it,
        // since the other goroutine might still be using it.
        buf := make([]byte, 2048)
        size, err := pr.stdOutPipe.Read(buf)
        if err != nil {
            println("!!! Sending exit request from cmdInRoutine !!!")
            // Be careful with this, if you also closed pr.shutdown when you closed stdOutPipe, then this is going to panic (closing a closed channel).
            pr.ProcessExit <- true
            close(pr.shutdown)
            return
        }

        // now that we have some data, try to send it,
        // unless we're done.
        select {
        case <-pr.shutdown:
            println("!!! Shutting cmdInRoutine down !!!")
            return
        case pr.CmdIn <- buf[:size]:
            println("--- Received data for sending to CmdIn:", string(buf[:size]))
        }
    }
}