io.Pipe()在这里无法正常工作。我在这里做错了什么?

huangapple go评论91阅读模式
英文:

io.Pipe() not working as desired. What am I doing wrong here?

问题

我一直在使用client-go对Kubernetes Pod进行exec功能的测试。以下是使用os.Stdin完美运行的代码:

{
	// 准备用于在Pod内部执行另一个进程的API URL。在这种情况下,我们将运行一个远程shell。
	req := coreclient.RESTClient().
		Post().
		Namespace(pod.Namespace).
		Resource("pods").
		Name(pod.Name).
		SubResource("exec").
		VersionedParams(&corev1.PodExecOptions{
			Container: pod.Spec.Containers[0].Name,
			Command:   []string{"/bin/sh"},
			Stdin:     true,
			Stdout:    true,
			Stderr:    true,
			TTY:       true,
		}, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
	if err != nil {
		panic(err)
	}

	// 将终端设置为原始模式,以防止它重复回显字符。
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	defer terminal.Restore(0, oldState)

	// 将此进程的std{in,out,err}连接到远程shell进程。
	err = exec.Stream(remotecommand.StreamOptions{
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
		Tty:    true,
	})
	if err != nil {
		panic(err)
	}

	fmt.Println()
}

然后,我开始使用io.Pipe()进行测试,以便除了os.Stdin之外,还可以从变量或任何其他来源提供输入。修改后的代码可以在这里找到:

{
	// 准备用于在Pod内部执行另一个进程的API URL。在这种情况下,我们将运行一个远程shell。
	req := coreclient.RESTClient().
		Post().
		Namespace(pod.Namespace).
		Resource("pods").
		Name(pod.Name).
		SubResource("exec").
		VersionedParams(&corev1.PodExecOptions{
			Container: pod.Spec.Containers[0].Name,
			Command:   []string{"/bin/sh"},
			Stdin:     true,
			Stdout:    true,
			Stderr:    true,
			TTY:       true,
		}, scheme.ParameterCodec)

	exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
	if err != nil {
		panic(err)
	}

	// 将终端设置为原始模式,以防止它重复回显字符。
	oldState, err := terminal.MakeRaw(0)
	if err != nil {
		panic(err)
	}
	defer terminal.Restore(0, oldState)

    // 从os.Stdin扫描输入
	stdin, putStdin := io.Pipe()
	go func() {
		consolescanner := bufio.NewScanner(os.Stdin)
		for consolescanner.Scan() {
			input := consolescanner.Text()
			fmt.Println("input:", input)
			putStdin.Write([]byte(input))
		}
		if err := consolescanner.Err(); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}()

	// 将此进程的std{in,out,err}连接到远程shell进程。
	err = exec.Stream(remotecommand.StreamOptions{
		Stdin:  stdin,
		Stdout: os.Stdout,
		Stderr: os.Stdout,
		Tty:    true,
	})
	if err != nil {
		panic(err)
	}

	fmt.Println()
}

这似乎奇怪地挂起了终端,有人可以指出我做错了什么吗?

英文:

I have been testing exec functionality to a kubernetes pod with client-go. This is the code that works perfectly with os.Stdin

{
// Prepare the API URL used to execute another process within the Pod.  In
// this case, we'll run a remote shell.
req := coreclient.RESTClient().
Post().
Namespace(pod.Namespace).
Resource("pods").
Name(pod.Name).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: pod.Spec.Containers[0].Name,
Command:   []string{"/bin/sh"},
Stdin:     true,
Stdout:    true,
Stderr:    true,
TTY:       true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
if err != nil {
panic(err)
}
// Put the terminal into raw mode to prevent it echoing characters twice.
oldState, err := terminal.MakeRaw(0)
if err != nil {
panic(err)
}
defer terminal.Restore(0, oldState)
// Connect this process' std{in,out,err} to the remote shell process.
err = exec.Stream(remotecommand.StreamOptions{
Stdin:  os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty:    true,
})
if err != nil {
panic(err)
}
fmt.Println()
}

I then started to test with a io.Pipe() so that I can give it input apart from the os.Stdin, basically from a variable or any other source. The modified code can be found here

{
// Prepare the API URL used to execute another process within the Pod.  In
// this case, we'll run a remote shell.
req := coreclient.RESTClient().
Post().
Namespace(pod.Namespace).
Resource("pods").
Name(pod.Name).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: pod.Spec.Containers[0].Name,
Command:   []string{"/bin/sh"},
Stdin:     true,
Stdout:    true,
Stderr:    true,
TTY:       true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
if err != nil {
panic(err)
}
// Put the terminal into raw mode to prevent it echoing characters twice.
oldState, err := terminal.MakeRaw(0)
if err != nil {
panic(err)
}
defer terminal.Restore(0, oldState)
// Scanning for inputs from os.stdin
stdin, putStdin := io.Pipe()
go func() {
consolescanner := bufio.NewScanner(os.Stdin)
for consolescanner.Scan() {
input := consolescanner.Text()
fmt.Println("input:", input)
putStdin.Write([]byte(input))
}
if err := consolescanner.Err(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}()
// Connect this process' std{in,out,err} to the remote shell process.
err = exec.Stream(remotecommand.StreamOptions{
Stdin:  stdin,
Stdout: os.Stdout,
Stderr: os.Stdout,
Tty:    true,
})
if err != nil {
panic(err)
}
fmt.Println()
}

This oddly seems to be hanging the terminal, can someone point me out on what am I doing wrong?

答案1

得分: 1

我没有尝试理解你的所有代码,但是:在执行单独的进程时,你几乎总是要使用os.Pipe而不是io.Pipe

os.Pipe是由操作系统创建的管道。io.Pipe是一个完全存在于Go中的软件构造,它从io.Writer复制到io.Reader。在执行单独的进程时使用io.Pipe通常会通过创建一个os.Pipe并启动goroutine来在io.Pipeos.Pipe之间进行复制来实现。只需使用os.Pipe即可。

英文:

I didn't try to understand all of your code, but: when executing a separate process, you pretty much always want to use os.Pipe, not io.Pipe.

os.Pipe is a pipe created by the operating system. io.Pipe is a software construct that lives entirely in Go that copies from an io.Writer to an io.Reader. Using an io.Pipe when executing a separate process will generally be implemented by creating an os.Pipe and starting up goroutines to copy between the io.Pipe and the os.Pipe. Just use an os.Pipe.

答案2

得分: 1

我已经解决了我的问题。不幸的是,上述方法都没有帮助我,而是我做了以下工作。
我为我想要输入的字符串创建了一个单独的io.Reader,然后从该读取器复制到上面代码片段中的putStdin。之前我使用的是putStdin.Write(<string>),这没有起作用。

我希望这对一些人解决问题有所帮助。

英文:

I was able to resolve my issue. Unfortunately, none of the above methods helped me but rather I did the below work.
I created a separate io.Reader for the string I wanted to input, then did a io.Copy from the reader to putStdin from the above code snipper. Earlier I used putStdin.Write(<string>) which did not do the trick.

I hope this solves issues for some folks.

答案3

得分: 0

更新:
感谢 @bcmills 提醒我,os.Pipe 的缓冲区是与系统相关的。

让我们重新看一下 os.Pipe() 的返回值:

reader, writer, err := os.Pipe()

为了解决这个问题,我们应该有一个名为 MAX_WRITE_SIZE 的常量,用于指定写入 writer 的字节数组的长度。

MAX_WRITE_SIZE 的值也应该与系统相关。例如,在 Linux 中,缓冲区大小为 64k。因此,我们可以将 MAX_WRITE_SIZE 配置为小于 64k 的值。

如果要发送的数据长度大于 MAX_WRITE_SIZE,可以将其分成多个块按顺序发送。


终端挂起的原因是使用了 io.Pipe() 时发生了死锁。

关于 io.Pipe() 方法的文档:

> Pipe 创建一个同步的内存管道。

> 数据直接从写入到相应的读取(或读取)中,没有内部缓冲。

因此,在没有管道读取调用阻塞的情况下写入管道会导致死锁(类似地,在没有管道写入调用阻塞的情况下读取管道)。

要解决这个问题,你应该使用 os.Pipe,它类似于 Linux 的 pipe 命令。

因为数据将被缓冲,所以不需要读写阻塞。

> 写入管道的数据由内核缓冲,直到从管道的读取端读取为止。

英文:

UPDATE:
Thanks @bcmills for reminding me that the buffer of os.Pipe is system dependent.

Let's re-look at os.Pipe() return values

reader, writer, err := os.Pipe()

To resolve that, we should have a MAX_WRITE_SIZE constant for the length of the byte array written to the writer.

The value of MAX_WRITE_SIZE should be system-dependent also. For example, in linux, the buffer size is 64k. So we can configure MAX_WRITE_SIZE to a value < 64k.

If length of the data to send is greater than MAX_WRITE_SIZE, it can be broken in chunks to send sequentially.


The reason the terminal hang is because of deadlock when you use io.Pipe().

Document on the io.Pipe() method

> Pipe creates a synchronous in-memory pipe.

> The data is copied directly from the Write to the corresponding Read (or Reads); there is no internal buffering.

So writing to Pipe when there is no pipe read call blocking will cause deadlock (similar to reading from Pipe when there is no pipe write call blocking)

To solve the issue, you should use os.Pipe, which is similar to linux pipe command.

Because the data will be buffered, so no read/write blocking is required.

> Data written to the write end of
the pipe is buffered by the kernel until it is read from the read
end of the pipe

huangapple
  • 本文由 发表于 2021年6月23日 22:35:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/68101930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定