gpt4 book ai didi

go - 使用同一 channel 的不同通话

转载 作者:行者123 更新时间:2023-12-01 21:23:45 25 4
gpt4 key购买 nike

您正在使用哪个版本的Go(go version)?

$ go version 1.13.1

Does this issue reproduce with the latest release?

I'm not sure.

What operating system and processor architecture are you using (go env)?

$ go env
GO111MODULE="auto"
GOARCH="amd64"
GOBIN="/usr/local/go/bin"
GOCACHE="/data/xieyixin/.cache/go-build"
GOENV="/data/xieyixin/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/data/xieyixin/go"
GOPRIVATE=""
GOPROXY="http://10.0.12.201:8989/"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/data/xieyixin/hxagent/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build474907248=/tmp/go-build"

你做了什么?

我编写了一个函数来执行exec命令,并自行控制超时情况。
并且我像这样测试
package utils

import (
"bytes"
"context"
"log"
"os/exec"
"syscall"
"time"
)

func ExecCommand(command string, timeout time.Duration) (string, error) {
log.Printf("command:%v, timeout:%v", command, timeout)
var (
cmd *exec.Cmd
stdout bytes.Buffer
stderr bytes.Buffer
result string
err error
//timeouterr error
)
ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
defer cancelFn()

cmd = exec.Command("bash", "-c", "--", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

var waitDone = make(chan struct{})
defer func() {
log.Printf("waitDone addr:%v\n", &waitDone)
log.Printf("close waitdone channel\n")
close(waitDone)
}()
go func() {
err = cmd.Run()
log.Printf("waitDone addr:%v\n", &waitDone)
waitDone <- struct{}{}
}()

select {
case <-ctx.Done():
log.Printf("timeout to kill process, %v", cmd.Process.Pid)
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
result = convertStr(stdout)
err = ctx.Err()
case <-waitDone:
if err != nil {
result = convertStr(stderr)
} else {
result = convertStr(stdout)
}
}

log.Printf("result:%v,err:%v", result, err)

return result, err
}

func convertStr(buffer bytes.Buffer) string {
data := buffer.String()
return data
}
package utils

import (
"context"
"testing"
"time"
)

func TestExecCommand(t *testing.T) {
tests := []struct {
command string
timeout time.Duration
wantErr string
want string
}{
{
command: "sleep 10",
timeout: time.Second * 5,
wantErr: context.DeadlineExceeded.Error(),
},
{
command: "watch -n 1 date +%s",
timeout: time.Second * 10,
wantErr: context.DeadlineExceeded.Error(),
want: "timeout, but still have result.",
},
{
command: "hostname",
timeout: time.Second * 5,
wantErr: "",
want: "anything result would be fine.",
},
}

for _, tt := range tests {
// got panic here.
// send on closed channel.
got, gotErr := ExecCommand(tt.command, tt.timeout)
if gotErr == nil {
if tt.wantErr == "" {
t.Logf("succeed")
} else {
t.Errorf("failed case: %+v, got:%v, gotErr:%v\n", tt, got, gotErr)
}
} else if gotErr.Error() == tt.wantErr {
t.Logf("succeed")
} else {
t.Errorf("failed case: %+v, got:%v, gotErr:%v\n", tt, got, gotErr)
}

}
}


您期望看到什么?

测试确定。

您看到了什么?

panic :在封闭 channel 上发送。

edit1:我自己控制上下文的原因
[ https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773]

edit2:这里更加困惑。

对我来说似乎有点清楚。但我仍然有一些疑问。
[ https://golang.org/src/os/exec/exec.go?s=11462:11489#L440]
if c.ctx != nil {
c.waitDone = make(chan struct{}) // here
go func() {
select {
case <-c.ctx.Done():
c.Process.Kill()
case <-c.waitDone: // and here
}
}()
}

如您所见,它类似于@CeriseLimón的代码。我们为什么要写这个 channel 。需要吗?

最佳答案

考虑使用exec.CommandContext而不是自己编写此代码。

在上下文在命令完成之前超时的情况下,ExecCommand函数可以在Run goroutine发送到 channel 之前关闭该 channel 。这引起了 panic 。

由于在执行waitDone之后应用程序未在close(waitDone)上接收,因此关闭 channel 毫无意义。

如果删除了关闭 channel 的代码,则会暴露出另一个问题。由于waitDone是未缓冲的 channel ,因此在超时情况下,运行goroutine将在发送到waitDone时永远阻止。

调用cmd.Run()会启动goroutine将数据复制到stdoutstderr。无法保证这些goroutine在ExecCommand调用convertStr(stdout)convertStr(stderr)之前完成执行。

这是所有这些的一种解决方法:

func ExecCommand(command string, timeout time.Duration) (string, error) {
log.Printf("command:%v, timeout:%v", command, timeout)
ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
defer cancelFn()

var stdout, stderr bytes.Buffer

cmd := exec.Command("bash", "-c", "--", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

err := cmd.Start()
if err != nil {
return "", err
}

go func() {
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded {
log.Printf("timeout to kill process, %v", cmd.Process.Pid)
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
}()

err = cmd.Wait()
var result string
if err != nil {
result = stderr.String()
} else {
result = stdout.String()
}
}

关于go - 使用同一 channel 的不同通话,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58514776/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com