github.com/Jeffail/benthos/v3@v3.65.0/public/service/stream.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/Jeffail/benthos/v3/internal/shutdown"
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/manager"
    12  	"github.com/Jeffail/benthos/v3/lib/metrics"
    13  	"github.com/Jeffail/benthos/v3/lib/stream"
    14  )
    15  
    16  // Stream executes a full Benthos stream and provides methods for performing
    17  // status checks, terminating the stream, and blocking until the stream ends.
    18  type Stream struct {
    19  	strm    *stream.Type
    20  	strmMut sync.Mutex
    21  	shutSig *shutdown.Signaller
    22  	onStart func()
    23  
    24  	conf   stream.Config
    25  	mgr    *manager.Type
    26  	stats  metrics.Type
    27  	logger log.Modular
    28  }
    29  
    30  func newStream(conf stream.Config, mgr *manager.Type, stats metrics.Type, logger log.Modular, onStart func()) *Stream {
    31  	return &Stream{
    32  		conf:    conf,
    33  		mgr:     mgr,
    34  		stats:   stats,
    35  		logger:  logger,
    36  		shutSig: shutdown.NewSignaller(),
    37  		onStart: onStart,
    38  	}
    39  }
    40  
    41  // Run attempts to start the stream pipeline and blocks until either the stream
    42  // has gracefully come to a stop, or the provided context is cancelled.
    43  func (s *Stream) Run(ctx context.Context) (err error) {
    44  	s.strmMut.Lock()
    45  	if s.strm != nil {
    46  		err = errors.New("stream has already been run")
    47  	} else {
    48  		s.strm, err = stream.New(s.conf,
    49  			stream.OptOnClose(func() {
    50  				s.shutSig.ShutdownComplete()
    51  			}),
    52  			stream.OptSetManager(s.mgr),
    53  			stream.OptSetLogger(s.logger),
    54  			stream.OptSetStats(s.stats))
    55  	}
    56  	s.strmMut.Unlock()
    57  	if err != nil {
    58  		return
    59  	}
    60  
    61  	go s.onStart()
    62  	select {
    63  	case <-s.shutSig.HasClosedChan():
    64  		for {
    65  			if err = s.StopWithin(time.Millisecond * 100); err == nil {
    66  				return nil
    67  			}
    68  			if ctx.Err() != nil {
    69  				return
    70  			}
    71  		}
    72  	case <-ctx.Done():
    73  	}
    74  	return ctx.Err()
    75  }
    76  
    77  // StopWithin attempts to close the stream within the specified timeout period.
    78  // Initially the attempt is graceful, but as the timeout draws close the attempt
    79  // becomes progressively less graceful.
    80  //
    81  // An ungraceful shutdown increases the likelihood of processing duplicate
    82  // messages on the next start up, but never results in dropped messages as long
    83  // as the input source supports at-least-once delivery.
    84  func (s *Stream) StopWithin(timeout time.Duration) error {
    85  	s.strmMut.Lock()
    86  	strm := s.strm
    87  	s.strmMut.Unlock()
    88  	if strm == nil {
    89  		return errors.New("stream has not been run yet")
    90  	}
    91  
    92  	stopAt := time.Now().Add(timeout)
    93  	if err := strm.Stop(timeout); err != nil {
    94  		// Still attempt to shut down other resources but do not block.
    95  		go func() {
    96  			s.mgr.CloseAsync()
    97  			s.stats.Close()
    98  		}()
    99  		return err
   100  	}
   101  
   102  	s.mgr.CloseAsync()
   103  	if err := s.mgr.WaitForClose(time.Until(stopAt)); err != nil {
   104  		// Same as above, attempt to shut down other resources but do not block.
   105  		go s.stats.Close()
   106  		return err
   107  	}
   108  
   109  	return s.stats.Close()
   110  }