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.
- Why is
inst.wpipe
closed and set to nil? What would happen if its not closed? Is it common/necessary to close inst.wpipe? - Is
dup2(pipe_fd[1], 1)
the C analogue ofcmd.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:
- 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.
- Is
dup2(pipe_fd[1], 1)
the C analogue ofcmd.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
).