66 "errors"
77 "fmt"
88 "io"
9- "os"
109 "sync/atomic"
1110)
1211
@@ -218,50 +217,6 @@ func (p *Pipeline) AddWithIgnoredError(em ErrorMatcher, stages ...Stage) {
218217 }
219218}
220219
221- type stageStarter struct {
222- requirements StageRequirements
223- stdin * InputStream
224- stdout * OutputStream
225- }
226-
227- func (requirement StreamRequirement ) validate () error {
228- switch requirement {
229- case StreamOptional , StreamForbidden :
230- return nil
231- default :
232- return fmt .Errorf ("invalid stream requirement %d" , requirement )
233- }
234- }
235-
236- func (requirements StageRequirements ) validate (s Stage , stdinConnected , stdoutConnected bool ) error {
237- if err := requirements .Stdin .validate (); err != nil {
238- return fmt .Errorf ("stdin: %w" , err )
239- }
240- if err := requirements .Stdout .validate (); err != nil {
241- return fmt .Errorf ("stdout: %w" , err )
242- }
243- if requirements .Stdin == StreamForbidden && stdinConnected {
244- return fmt .Errorf ("stage %q forbids stdin, but stdin is connected" , s .Name ())
245- }
246- if requirements .Stdout == StreamForbidden && stdoutConnected {
247- return fmt .Errorf ("stage %q forbids stdout, but stdout is connected" , s .Name ())
248- }
249- return nil
250- }
251-
252- func (p * Pipeline ) abortBeforeStart (s Stage , err error ) error {
253- _ = p .stdout .Close ()
254- p .cancel ()
255- p .eventHandler (& Event {
256- Command : s .Name (),
257- Msg : "failed to start pipeline stage" ,
258- Err : err ,
259- })
260- return fmt .Errorf (
261- "starting pipeline stage %q: %w" , s .Name (), err ,
262- )
263- }
264-
265220func (p * Pipeline ) stageOptions () StageOptions {
266221 return StageOptions {Env : p .env , PanicHandler : p .panicHandler }
267222}
@@ -309,50 +264,68 @@ func (p *Pipeline) Start(ctx context.Context) error {
309264 // We need to decide how to start the stages, especially what
310265 // pipes to use to connect adjacent stages (`os.Pipe()` vs.
311266 // `io.Pipe()`) based on the two stages' requirements.
312- stageStarters := make ([]stageStarter , len (p .stages ))
267+ stageJoiners := make ([]stageJoiner , len (p .stages )+ 1 )
268+
269+ // Arrange for the input of the 0th stage to come from `p.stdin`:
270+ stageJoiners [0 ].nextStdin = p .stdin
271+
272+ // Arrange for the output of the last stage to go to `p.stdout`:
273+ stageJoiners [len (p .stages )].prevStdout = p .stdout
274+
275+ // closePipes closes all of the streams that are currently stored
276+ // in the joiners. This should be called if startup fails. As we
277+ // call `Stage.Start()` and pass that method streams, we clear
278+ // them from the corresponding joiners to avoid closing them
279+ // twice.
280+ closePipes := func () {
281+ for _ , sj := range stageJoiners {
282+ _ = sj .closePipe ()
283+ }
284+ }
313285
314- // Collect information about each stage's type and requirements:
286+ // Store the stages in the joiners, and verify that the stages'
287+ // requirements are well-formed:
315288 for i , s := range p .stages {
316- stageStarters [i ].requirements = s .Requirements ()
317-
318- err := stageStarters [i ].requirements .validate (
319- s ,
320- i > 0 || p .stdin != nil ,
321- i < len (p .stages )- 1 || p .stdout != nil ,
322- )
323- if err != nil {
324- return p .abortBeforeStart (s , err )
289+ // Make sure that the stage's requirements are well-formed:
290+ requirements := s .Requirements ()
291+ if err := requirements .Stdin .Validate (); err != nil {
292+ return fmt .Errorf ("stdin: %w" , err )
293+ }
294+ if err := requirements .Stdout .Validate (); err != nil {
295+ return fmt .Errorf ("stdout: %w" , err )
325296 }
326- }
327297
328- if p . stdin != nil {
329- // Arrange for the input of the 0th stage to come from
330- // `p.stdin`:
331- stageStarters [ 0 ]. stdin = p . stdin
298+ stageJoiners [ i ]. nextStage = s
299+ stageJoiners [ i ]. nextStageReq = requirements
300+ stageJoiners [ i + 1 ]. prevStage = s
301+ stageJoiners [ i + 1 ]. prevStageReq = requirements
332302 }
333303
334- if p .stdout != nil {
335- // Arrange for the output of the last stage to go to
336- // `p.stdout`:
337- stageStarters [len (p .stages )- 1 ].stdout = p .stdout
304+ // Check that each of the stages' requirements are satisfiable:
305+ for i := range stageJoiners {
306+ if err := stageJoiners [i ].validate (); err != nil {
307+ closePipes ()
308+ return err
309+ }
338310 }
339311
340- // Clean up any processes and pipes that have been created. `i` is the
341- // index of the stage that failed to start. If the stage already received
342- // its streams, it owns any closing stream.
343- abort := func (i int , err error , closeFailedStageStdin bool ) error {
344- // If the failing stage never received its stdin, close the pipe that
345- // the previous stage was writing to. That should cause it to exit
346- // even if it's not minding its context.
347- if closeFailedStageStdin {
348- _ = stageStarters [i ].stdin .Close ()
312+ // Create the "inner" pipes (i.e, all but the first and last
313+ // `stageJoiners`):
314+ for i := 1 ; i < len (stageJoiners )- 1 ; i ++ {
315+ if err := stageJoiners [i ].createPipe (); err != nil {
316+ closePipes ()
317+ return err
349318 }
319+ }
350320
351- // If stdout was supplied with WithStdoutCloser but the final stage
352- // was never started, then the pipeline still owns that closer.
353- if i < len (p .stages )- 1 {
354- _ = p .stdout .Close ()
355- }
321+ // We're about to start up the stages, one by one. If something
322+ // goes wrong during that process, this function should be called
323+ // to kill any stages that have already been started and to close
324+ // any pipes that have not yet been passed to a stage. `i` is the
325+ // index of the stage that failed to start. If the stage already
326+ // received its streams, it is responsible for closing them.
327+ abort := func (i int , err error ) error {
328+ closePipes ()
356329
357330 // Kill and wait for any stages that have been started
358331 // already to finish:
@@ -370,51 +343,20 @@ func (p *Pipeline) Start(ctx context.Context) error {
370343 )
371344 }
372345
373- // Loop over all but the last stage, starting them. By the time we
374- // get to a stage, its stdin will have already been determined,
375- // but we still need to figure out its stdout and set the stdin
376- // that will be used for the subsequent stage.
377- for i , s := range p .stages [:len (p .stages )- 1 ] {
378- ss := & stageStarters [i ]
379- nextSS := & stageStarters [i + 1 ]
380-
381- // We need to generate a pipe pair for this stage to use
382- // to communicate with its successor:
383- if ss .requirements .StdoutNeedsFile || nextSS .requirements .StdinNeedsFile {
384- // Use an OS-level pipe for the communication:
385- nextStdin , stdout , err := os .Pipe ()
386- if err != nil {
387- return abort (i , err , true )
388- }
389- nextSS .stdin = ClosingInput (nextStdin )
390- ss .stdout = ClosingOutput (stdout )
391- } else {
392- nextStdin , stdout := io .Pipe ()
393- nextSS .stdin = ClosingInput (nextStdin )
394- ss .stdout = ClosingOutput (stdout )
395- }
396- if err := s .Start (
397- ctx , p .stageOptions (),
398- ss .stdin , ss .stdout ,
399- ); err != nil {
400- _ = nextSS .stdin .Close ()
401- return abort (i , err , false )
402- }
403- }
346+ // Loop over all of the stages, starting them in order.
347+ for i , s := range p .stages {
348+ prevSJ := & stageJoiners [i ]
349+ nextSJ := & stageJoiners [i + 1 ]
404350
405- // The last stage needs special handling, because its stdout
406- // doesn't need to flow into another stage (it's already set in
407- // `ss.stdout` if it's needed).
408- {
409- i := len (p .stages ) - 1
410- s := p .stages [i ]
411- ss := & stageStarters [i ]
351+ err := s .Start (ctx , p .stageOptions (), prevSJ .nextStdin , nextSJ .prevStdout )
412352
413- if err := s .Start (
414- ctx , p .stageOptions (),
415- ss .stdin , ss .stdout ,
416- ); err != nil {
417- return abort (i , err , false )
353+ // Even if that stage failed to start, we are no longer
354+ // responsible for closing its streams:
355+ prevSJ .nextStdin = nil
356+ nextSJ .prevStdout = nil
357+
358+ if err != nil {
359+ return abort (i , err )
418360 }
419361 }
420362
0 commit comments