How Go Exec Works¶
It’s common to run and wish to get results. As the command is executed in a forked child process, how results are passed back? This blog investigates how output is passed to parent process in command.Output
.
A simple example is:
The Output
function uses bytes.Buffer
to provide an io.Writer
. As we know, when fork
, the children will inherit all file descriptors. but the buffer is not an fd and cannot use to communicate between processes.
To communicate, the parent process wraps the command stdout, stdin, and stderr by pipelines, which could be represented by file descriptors.
- Wrap the stdin, stdout and stderr:
// in method (c *Cmd) Start() error
for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
fd, err := setupFd(c)
// ignore error handling
c.childFiles = append(c.childFiles, fd)
}
// ignore the other variable setup
c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
// ignore some fields
Files: c.childFiles,
})
- cmd stdin method:
func (c *Cmd) stdin() (f *os.File, err error) {
if c.Stdin == nil {
f, err = os.Open(os.DevNull)
// ignore error handling
return
}
if f, ok := c.Stdin.(*os.File); ok {
return f, nil
}
pr, pw, err := os.Pipe()
// ignore error handling and some logic
// setup the goroutine to copy data.
c.goroutine = append(c.goroutine, func() error {
_, err := io.Copy(pw, c.Stdin)
// ignore error handling
})
return pr, nil
}
What happens in cmd Wait()
method?¶
The command Wait
method wraps the process.Wait
which returns until the process exit.
Wait
waits for the Process to exit, and then returns a ProcessState describing its status and an error, if any. Wait releases any resources associated with the Process.
After ending of process, it waits the done of c.goroutine
,and check the errors raised by functions inside it. And then, close file descriptors and return error if there is.