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 }