src/server/worker/api_server.go
func (a *APIServer) runUserErrorHandlingCode(ctx context.Context, logger *taggedLogger, environ []string, stats *pps.ProcessStats, rawDatumTimeout *types.Duration) (retErr error) {
	logger.Logf("beginning to run user error handling code")
	defer func(start time.Time) {
		if retErr != nil {
			logger.Logf("errored running user error handling code after %v: %v", time.Since(start), retErr)
		} else {
			logger.Logf("finished running user error handling code after %v", time.Since(start))
		}
	}(time.Now())

	cmd := exec.CommandContext(ctx, a.pipelineInfo.Transform.ErrCmd[0], a.pipelineInfo.Transform.ErrCmd[1:]...)
	if a.pipelineInfo.Transform.ErrStdin != nil {
		cmd.Stdin = strings.NewReader(strings.Join(a.pipelineInfo.Transform.ErrStdin, "\n") + "\n")
	}
	cmd.Stdout = logger.userLogger()
	cmd.Stderr = logger.userLogger()
	cmd.Env = environ
	if a.uid != nil && a.gid != nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Credential: &syscall.Credential{
				Uid: *a.uid,
				Gid: *a.gid,
			},
		}
	}
	cmd.Dir = a.pipelineInfo.Transform.WorkingDir
	err := cmd.Start()
	if err != nil {
		return fmt.Errorf("error cmd.Start: %v", err)
	}
	// A context w a deadline will successfully cancel/kill
	// the running process (minus zombies)
	state, err := cmd.Process.Wait()
	if err != nil {
		return fmt.Errorf("error cmd.Wait: %v", err)
	}
	if isDone(ctx) {
		if err = ctx.Err(); err != nil {
			return err
		}
	}
	// Because of this issue: https://github.com/golang/go/issues/18874
	// We forked os/exec so that we can call just the part of cmd.Wait() that
	// happens after blocking on the process. Unfortunately calling
	// cmd.Process.Wait() then cmd.Wait() will produce an error. So instead we
	// close the IO using this helper
	err = cmd.WaitIO(state, err)
	// We ignore broken pipe errors, these occur very occasionally if a user
	// specifies Stdin but their process doesn't actually read everything from
	// Stdin. This is a fairly common thing to do, bash by default ignores
	// broken pipe errors.
	if err != nil && !strings.Contains(err.Error(), "broken pipe") {
		// (if err is an acceptable return code, don't return err)
		if exiterr, ok := err.(*exec.ExitError); ok {
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				for _, returnCode := range a.pipelineInfo.Transform.AcceptReturnCode {
					if int(returnCode) == status.ExitStatus() {
						return nil
					}
				}
			}
		}
		return fmt.Errorf("error cmd.WaitIO: %v", err)
	}
	return nil
}
func (a *APIServer) runUserErrorHandlingCode(ctx context.Context, logger *taggedLogger, environ []string, stats *pps.ProcessStats, rawDatumTimeout *types.Duration) (retErr error) {
	logger.Logf("beginning to run user error handling code")
	defer func(start time.Time) {
		if retErr != nil {
			logger.Logf("errored running user error handling code after %v: %v", time.Since(start), retErr)
		} else {
			logger.Logf("finished running user error handling code after %v", time.Since(start))
		}
	}(time.Now())

	cmd := exec.CommandContext(ctx, a.pipelineInfo.Transform.ErrCmd[0], a.pipelineInfo.Transform.ErrCmd[1:]...)
	if a.pipelineInfo.Transform.ErrStdin != nil {
		cmd.Stdin = strings.NewReader(strings.Join(a.pipelineInfo.Transform.ErrStdin, "\n") + "\n")
	}
	cmd.Stdout = logger.userLogger()
	cmd.Stderr = logger.userLogger()
	cmd.Env = environ
	if a.uid != nil && a.gid != nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Credential: &syscall.Credential{
				Uid: *a.uid,
				Gid: *a.gid,
			},
		}
	}
	cmd.Dir = a.pipelineInfo.Transform.WorkingDir
	err := cmd.Start()
	if err != nil {
		return fmt.Errorf("error cmd.Start: %v", err)
	}
	// A context w a deadline will successfully cancel/kill
	// the running process (minus zombies)
	state, err := cmd.Process.Wait()
	if err != nil {
		return fmt.Errorf("error cmd.Wait: %v", err)
	}
	if isDone(ctx) {
		if err = ctx.Err(); err != nil {
			return err
		}
	}
	// Because of this issue: https://github.com/golang/go/issues/18874
	// We forked os/exec so that we can call just the part of cmd.Wait() that
	// happens after blocking on the process. Unfortunately calling
	// cmd.Process.Wait() then cmd.Wait() will produce an error. So instead we
	// close the IO using this helper
	err = cmd.WaitIO(state, err)
	// We ignore broken pipe errors, these occur very occasionally if a user
	// specifies Stdin but their process doesn't actually read everything from
	// Stdin. This is a fairly common thing to do, bash by default ignores
	// broken pipe errors.
	if err != nil && !strings.Contains(err.Error(), "broken pipe") {
		// (if err is an acceptable return code, don't return err)
		if exiterr, ok := err.(*exec.ExitError); ok {
			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
				for _, returnCode := range a.pipelineInfo.Transform.AcceptReturnCode {
					if int(returnCode) == status.ExitStatus() {
						return nil
					}
				}
			}
		}
		return fmt.Errorf("error cmd.WaitIO: %v", err)
	}
	return nil
}
func ( a * APIServer ) runUserErrorHandlingCode ( ctx context . Context , logger * taggedLogger , environ [ ] string , stats * pps . ProcessStats , rawDatumTimeout * types . Duration ) ( retErr error ) { logger . Logf ( " " ) defer func ( start time . Time ) { if retErr != nil { logger . Logf ( " " , time . Since ( start ) , retErr ) } else { logger . Logf ( " " , time . Since ( start ) ) } } ( time . Now ( ) ) cmd := exec . CommandContext ( ctx , a . pipelineInfo . Transform . ErrCmd [ 0 ] , a . pipelineInfo . Transform . ErrCmd [ 1 : ] ... ) if a . pipelineInfo . Transform . ErrStdin != nil { cmd . Stdin = strings . NewReader ( strings . Join ( a . pipelineInfo . Transform . ErrStdin , " \n " ) + " \n " ) } cmd . Stdout = logger . userLogger ( ) cmd . Stderr = logger . userLogger ( ) cmd . Env = environ if a . uid != nil && a . gid != nil { cmd . SysProcAttr = & syscall . SysProcAttr { Credential : & syscall . Credential { Uid : * a . uid , Gid : * a . gid , } , } } cmd . Dir = a . pipelineInfo . Transform . WorkingDir err := cmd . Start ( ) if err != nil { return fmt . Errorf ( " " , err ) } // A context w a deadline will successfully cancel/kill // the running process (minus zombies) state , err := cmd . Process . Wait ( ) if err != nil { return fmt . Errorf ( " " , err ) } if isDone ( ctx ) { if err = ctx . Err ( ) ; err != nil { return err } } // Because of this issue: https://github.com/golang/go/issues/18874 // We forked os/exec so that we can call just the part of cmd.Wait() that // happens after blocking on the process. Unfortunately calling // cmd.Process.Wait() then cmd.Wait() will produce an error. So instead we // close the IO using this helper err = cmd . WaitIO ( state , err ) // We ignore broken pipe errors, these occur very occasionally if a user // specifies Stdin but their process doesn't actually read everything from // Stdin. This is a fairly common thing to do, bash by default ignores // broken pipe errors. if err != nil && ! strings . Contains ( err . Error ( ) , " " ) { // (if err is an acceptable return code, don't return err) if exiterr , ok := err . ( * exec . ExitError ) ; ok { if status , ok := exiterr . Sys ( ) . ( syscall . WaitStatus ) ; ok { for _ , returnCode := range a . pipelineInfo . Transform . AcceptReturnCode { if int ( returnCode ) == status . ExitStatus ( ) { return nil } } } } return fmt . Errorf ( " " , err ) } return nil }
// Run user error code and return the combined output of stdout and stderr.
Run user error code and return the combined output of stdout and stderr .