Skip to content
Advertisement

Go pipe write end being closed, why?

I just read some Go code that does something along the following lines:

type someType struct {
    ...
    ...
    rpipe io.ReadCloser
    wpipe io.WriteCloser
}

var inst someType

inst.rpipe, inst.wpipe, _ := os.Pipe()
cmd := exec.Command("some_binary", args...)
cmd.Stdout = inst.wpipe
cmd.Stderr = inst.wpipe
if err := cmd.Start(); err != nil {
    ....
}
inst.wpipe.Close()
inst.wpipe = nil

some_binary is a long running process.

  1. Why is inst.wpipe closed and set to nil? What would happen if its not closed? Is it common/necessary to close inst.wpipe?
  2. Is dup2(pipe_fd[1], 1) the C analogue of cmd.Stdout = inst.wpipe; inst.wpipe.Close()?

Advertisement

Answer

That code is typical of a program that wants to read output generated by some other program. The os.Pipe() function returns a connected pair of os.File entities (or, on error—which should not be simply ignored—doesn’t) where a write on the second (w or wpipe) entity shows up as readable bytes on the first (r / rpipe) entity. But—this is the key to half the answer to your first question—how will a reader know when all writers are finished writing?

For a reader to get an EOF indication, all writers that have or had access to the write side of the pipe must call the close operation. By passing the write side of the pipe to a program that we start with cmd.Start(), we allow that command to access the write side of the pipe. When that command closes that pipe, one of the entities with access has closed the pipe. But another entity with access hasn’t closed it yet: we have write access.

To see an EOF, then, we must close off access to our wpipe, with wpipe.Close(). So that answer the first half of:

  1. Why is inst.wpipe closed and set to nil?

The set-to-nil part may or may not have any function; you should inspect the rest of the code to find out if it does.

  1. Is dup2(pipe_fd[1], 1) the C analogue of cmd.Stdout = inst.wpipe; inst.wpipe.Close()?

Not precisely. The dup2 level is down in the POSIX OS area, while cmd.Stdout is at a higher (OS-independent) level. The POSIX implementation of cmd.Start() will wind up calling dup2 (or something equivalent) like this after calling fork (or something equivalent). The POSIX equivalent of inst.wipe.Close() is close(wfd) where wfd is the POSIX file number in wpipe.

In C code that doesn’t have any higher level wrapping around it, we’d have something like:

int fds[2];

if (pipe(fds) < 0) ... handle error case ...
pid = fork();
switch (pid) {
case -1: ... handle error ...

case 0: /* child */
    if (dup2(fds[1], 1) < 0 || dup2(fds[1], 2) < 0) ... handle error ...
    if (execve(prog, args, env) < 0) ... handle error ...
    /* NOTREACHED */

default: /* parent */
    if (close(fds[1]) < 0) ... handle error ...
    ... read from fds[0] ...
}

(although if we’re careful enough to check for an error from close, we probably should be careful enough to check whether the pipe system call gave us back descriptors 0 and 1, or 1 and 2, or 2 and 3, here—though perhaps we handle this earlier by making sure that 0, 1, and 2 are at least open to /dev/null).

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement