@@ -7,89 +7,105 @@ import (
77// Stage is an element of a `Pipeline`. It reads from standard input
88// and writes to standard output.
99//
10- // Who closes stdin and stdout?
10+ // # Who closes stdin and stdout?
1111//
1212// A `Stage` as a whole is responsible for closing its end of stdin
1313// and stdout if the corresponding stream is closing. That
14- // responsibility transfers to the stage as soon as `Start()` is called
15- // and applies even if `Start()` returns an error. Before returning an
16- // error from `Start()`, the stage must close any closing stream that
17- // it has not already handed off to something else that will close it
18- // promptly. The caller must not close a closing stream after passing it
19- // to `Start()`.
14+ // responsibility transfers to the stage as soon as the stage's
15+ // `Start()` method is called and applies even if `Start()` returns an
16+ // error. Before returning an error from `Start()`, the stage must
17+ // close any closing stream that it has not already handed off to
18+ // something else that will close it promptly. The caller must not
19+ // close a closing stream after passing it to `Start()`.
2020//
21- // If the caller wants to retain ownership of stdin/stdout, it passes a
22- // non-closing stream. The stage must not close a non-closing stream,
23- // even if `Start()` returns an error .
21+ // If the caller wants to retain ownership of stdin/stdout, it passes
22+ // a non-closing stream. Calling `Close()` on a non-closing stream is
23+ // a NOP, so it isn't harmful but isn't required .
2424//
25- // Closing stdin/stdout tells the previous/next stage that this stage is
26- // done reading/writing data, which can affect their behavior. Therefore,
27- // after a successful start, a stage should close each one as soon as it
28- // is done with it.
25+ // Closing stdin/stdout tells the previous/next stage that this stage
26+ // is done reading/writing data, which can affect their behavior.
27+ // Therefore, after a successful start, a stage should close each one
28+ // as soon as it is done with it. Assume that this is done via the
29+ // following local function
2930//
30- // How this should be done depends on whether stdin/stdout are of type
31- // `*os.File`.
31+ // closeStreams := func() {
32+ // // Error handling omitted.
33+ // _ = stdin.Close()
34+ // _ = stdout.Close()
35+ // }
36+ //
37+ // From the point of view of the pipeline as a whole, if stdin is
38+ // provided by the user (`WithStdin()`), then we don't want the first
39+ // stage to close it at all, whether it's an `*os.File` or not. The
40+ // pipeline communicates this by passing a non-closing `InputStream`
41+ // when it starts that stage. For stdout, it depends on whether the
42+ // user supplied it using `WithStdout()` or `WithStdoutCloser()`. In
43+ // any case, this function can close the streams anyway, because
44+ // `InputStream.Close()` and `OutputStream.Close()` do nothing in
45+ // those cases.
46+ //
47+ // When these closes should happen depends on what kind of stage it is
48+ // and whether stdin/stdout are of type `*os.File`.
49+ //
50+ // ## A command stage
3251//
3352// If a stage is an external command, then the subprocess ultimately
3453// needs its own copies of `*os.File` file descriptors for its stdin
3554// and stdout. The external command will "always" [1] close those when
3655// it exits.
3756//
57+ // (It's theoretically possible for a command to pass the open file
58+ // descriptor to another, longer-lived process, in which case the file
59+ // descriptor wouldn't necessarily get closed when the command
60+ // finishes. But that's ill-behaved in a command that is being used in
61+ // a pipeline, so we'll ignore that possibility.)
62+ //
3863// If the stage is an external command and one of the arguments is an
3964// `*os.File`, then it can set the corresponding field of `exec.Cmd`
4065// to that argument directly. This has the result that `exec.Cmd`
4166// duplicates that file descriptor and passes the dup to the
4267// subprocess. Therefore, the stage must close its copy of that
4368// argument as soon as the external command has started, because the
4469// external command will keep its own copy open as long as necessary
45- // (and no longer!). It should use roughly the following sequence:
70+ // (and no longer!). Therefore, it should use roughly the following
71+ // sequence:
4672//
47- // cmd.Stdin = stdin.Reader() // Similarly for stdout
48- // cmd.Start(…)
49- // stdin.Close() // Close our copy
50- // cmd.Wait()
73+ // cmd.Stdin = stdin.Reader()
74+ // cmd.Stdout = stdout.Writer()
75+ // err := cmd.Start(…)
76+ // // Close our copies as soon as the command has started:
77+ // closeStreams()
78+ // if err != nil {
79+ // return err
80+ // }
81+ // return cmd.Wait()
5182//
5283// If the stage is an external command and its stdin is not an
5384// `*os.File`, then `exec.Cmd` will take care of creating an
5485// `os.Pipe()`, copying from the provided reader into the pipe, and
5586// eventually closing both ends of the pipe. The stage must close the
56- // provided stdin itself, but only _after_ the external command has
57- // finished, like so:
87+ // provided stdin itself, but only _after_ the external command and
88+ // the copy have finished, like so:
5889//
59- // cmd.Stdin = stdin.Reader() // Similarly for stdout
60- // cmd.Start(…)
61- // cmd.Wait()
62- // stdin.Close() // Close
90+ // defer closeStreams()
91+ // cmd.Stdin = stdin.Reader()
92+ // cmd.Stdout = stdout.Writer()
93+ // err := cmd.Start(…)
94+ // if err != nil {
95+ // return err
96+ // }
97+ // return cmd.Wait()
6398//
64- // If the stage is an external command and its stdout is not an
65- // `*os.File`, the stage creates a pipe, passes the write end to the
66- // command, and copies from the read end to the provided writer. The
67- // stage must close the provided stdout itself, but only _after_ the
68- // external command and the copy have finished.
99+ // ## A function stage
69100//
70101// If the stage is a Go function, then it holds the only copy of
71102// stdin/stdout, so it must wait until the function is done before
72103// closing them (regardless of their underlying type, like so:
73104//
74105// go func() {
75- // f(…, stdin, stdout)
76- // stdin.Close()
77- // stdout.Close()
106+ // defer closeStreams()
107+ // f(…, stdin.Reader(), stdout.Writer())
78108// }()
79- //
80- // From the point of view of the pipeline as a whole, if stdin is
81- // provided by the user (`WithStdin()`), then we don't want the first
82- // stage to close it at all, whether it's an `*os.File` or not. The
83- // pipeline communicates this by passing a non-closing `InputStream`
84- // when it starts that stage. For stdout, it depends on whether the
85- // user supplied it using `WithStdout()` or `WithStdoutCloser()`.
86- //
87- // [1] It's theoretically possible for a command to pass the open file
88- // descriptor to another, longer-lived process, in which case the
89- // file descriptor wouldn't necessarily get closed when the
90- // command finishes. But that's ill-behaved in a command that is
91- // being used in a pipeline, so we'll ignore that possibility.
92-
93109type Stage interface {
94110 // Name returns the name of the stage.
95111 Name () string
0 commit comments