github.com/Jeffail/benthos/v3@v3.65.0/lib/stream/manager/type.go (about) 1 package manager 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "sync" 8 "sync/atomic" 9 "time" 10 11 "github.com/Jeffail/benthos/v3/internal/interop" 12 "github.com/Jeffail/benthos/v3/lib/log" 13 "github.com/Jeffail/benthos/v3/lib/manager" 14 "github.com/Jeffail/benthos/v3/lib/metrics" 15 "github.com/Jeffail/benthos/v3/lib/stream" 16 "github.com/Jeffail/benthos/v3/lib/types" 17 ) 18 19 //------------------------------------------------------------------------------ 20 21 // StreamStatus tracks a stream along with information regarding its internals. 22 type StreamStatus struct { 23 stoppedAfter int64 24 config stream.Config 25 strm *stream.Type 26 logger log.Modular 27 metrics *metrics.Local 28 createdAt time.Time 29 } 30 31 // NewStreamStatus creates a new StreamStatus. 32 func NewStreamStatus( 33 conf stream.Config, 34 strm *stream.Type, 35 logger log.Modular, 36 stats *metrics.Local, 37 ) *StreamStatus { 38 return &StreamStatus{ 39 config: conf, 40 strm: strm, 41 logger: logger, 42 metrics: stats, 43 createdAt: time.Now(), 44 } 45 } 46 47 // IsRunning returns a boolean indicating whether the stream is currently 48 // running. 49 func (s *StreamStatus) IsRunning() bool { 50 return atomic.LoadInt64(&s.stoppedAfter) == 0 51 } 52 53 // IsReady returns a boolean indicating whether the stream is connected at both 54 // the input and output level. 55 func (s *StreamStatus) IsReady() bool { 56 return s.strm.IsReady() 57 } 58 59 // Uptime returns a time.Duration indicating the current uptime of the stream. 60 func (s *StreamStatus) Uptime() time.Duration { 61 if stoppedAfter := atomic.LoadInt64(&s.stoppedAfter); stoppedAfter > 0 { 62 return time.Duration(stoppedAfter) 63 } 64 return time.Since(s.createdAt) 65 } 66 67 // Config returns the configuration of the stream. 68 func (s *StreamStatus) Config() stream.Config { 69 return s.config 70 } 71 72 // Metrics returns a metrics aggregator of the stream. 73 func (s *StreamStatus) Metrics() *metrics.Local { 74 return s.metrics 75 } 76 77 // Logger returns the logger of the stream. 78 func (s *StreamStatus) Logger() log.Modular { 79 return s.logger 80 } 81 82 // setClosed sets the flag indicating that the stream is closed. 83 func (s *StreamStatus) setClosed() { 84 atomic.SwapInt64(&s.stoppedAfter, int64(time.Since(s.createdAt))) 85 } 86 87 //------------------------------------------------------------------------------ 88 89 // StreamProcConstructorFunc is a closure type that constructs a processor type 90 // for new streams, where the id of the stream is provided as an argument. 91 type StreamProcConstructorFunc func(streamID string) (types.Processor, error) 92 93 //------------------------------------------------------------------------------ 94 95 // Type manages a collection of streams, providing APIs for CRUD operations on 96 // the streams. 97 type Type struct { 98 closed bool 99 streams map[string]*StreamStatus 100 101 manager types.Manager 102 stats metrics.Type 103 logger log.Modular 104 apiTimeout time.Duration 105 apiEnabled bool 106 107 pipelineProcCtors []StreamProcConstructorFunc 108 109 lock sync.Mutex 110 } 111 112 // New creates a new stream manager.Type. 113 func New(opts ...func(*Type)) *Type { 114 t := &Type{ 115 streams: map[string]*StreamStatus{}, 116 manager: types.DudMgr{}, 117 stats: metrics.Noop(), 118 apiTimeout: time.Second * 5, 119 logger: log.Noop(), 120 apiEnabled: true, 121 } 122 for _, opt := range opts { 123 opt(t) 124 } 125 t.registerEndpoints(t.apiEnabled) 126 return t 127 } 128 129 //------------------------------------------------------------------------------ 130 131 // OptAPIEnabled sets whether the stream manager registers API endpoints for 132 // CRUD operations on streams. This is enabled by default. 133 func OptAPIEnabled(b bool) func(*Type) { 134 return func(t *Type) { 135 t.apiEnabled = b 136 } 137 } 138 139 // OptSetStats sets the metrics aggregator to be used by the manager and all 140 // child streams. 141 func OptSetStats(stats metrics.Type) func(*Type) { 142 return func(t *Type) { 143 t.stats = stats 144 t.manager = manager.SwapMetrics(t.manager, t.stats) 145 } 146 } 147 148 // OptSetLogger sets the logging output to be used by the manager and all child 149 // streams. 150 func OptSetLogger(log log.Modular) func(*Type) { 151 return func(t *Type) { 152 t.logger = log 153 } 154 } 155 156 // OptSetManager sets the service manager to be used by the stream manager and 157 // all child streams. 158 func OptSetManager(mgr types.Manager) func(*Type) { 159 return func(t *Type) { 160 t.manager = manager.SwapMetrics(mgr, t.stats) 161 } 162 } 163 164 // OptSetAPITimeout sets the default timeout for HTTP API requests. 165 func OptSetAPITimeout(tout time.Duration) func(*Type) { 166 return func(t *Type) { 167 t.apiTimeout = tout 168 } 169 } 170 171 // OptAddProcessors adds processor constructors that will be called for every 172 // new stream and attached to the processor pipelines. The constructor is given 173 // the name of the stream as an argument. 174 func OptAddProcessors(procs ...StreamProcConstructorFunc) func(*Type) { 175 return func(t *Type) { 176 t.pipelineProcCtors = append(t.pipelineProcCtors, procs...) 177 } 178 } 179 180 //------------------------------------------------------------------------------ 181 182 // Errors specifically returned by a stream manager. 183 var ( 184 ErrStreamExists = errors.New("stream already exists") 185 ErrStreamDoesNotExist = errors.New("stream does not exist") 186 ) 187 188 //------------------------------------------------------------------------------ 189 190 // Create attempts to construct and run a new stream under a unique ID. If the 191 // ID already exists an error is returned. 192 func (m *Type) Create(id string, conf stream.Config) error { 193 m.lock.Lock() 194 defer m.lock.Unlock() 195 196 if m.closed { 197 return types.ErrTypeClosed 198 } 199 200 if _, exists := m.streams[id]; exists { 201 return ErrStreamExists 202 } 203 204 var procCtors []types.ProcessorConstructorFunc 205 for _, ctor := range m.pipelineProcCtors { 206 func(c StreamProcConstructorFunc) { 207 procCtors = append(procCtors, func() (types.Processor, error) { 208 return c(id) 209 }) 210 }(ctor) 211 } 212 213 sMgr, sLog, sStats := interop.LabelStream(id, m.manager, m.logger, m.stats) 214 if u, ok := sStats.(interface { 215 Unwrap() metrics.Type 216 }); ok { 217 sStats = u.Unwrap() 218 } 219 220 strmFlatMetrics := metrics.NewLocal() 221 sStats = metrics.Combine(sStats, strmFlatMetrics) 222 sMgr = manager.SwapMetrics(sMgr, sStats) 223 224 var wrapper *StreamStatus 225 strm, err := stream.New( 226 conf, 227 stream.OptAddProcessors(procCtors...), 228 stream.OptSetLogger(sLog), 229 stream.OptSetStats(sStats), 230 stream.OptSetManager(sMgr), 231 stream.OptOnClose(func() { 232 wrapper.setClosed() 233 }), 234 ) 235 if err != nil { 236 return err 237 } 238 239 wrapper = NewStreamStatus(conf, strm, sLog, strmFlatMetrics) 240 m.streams[id] = wrapper 241 return nil 242 } 243 244 // Read attempts to obtain the status of a managed stream. Returns an error if 245 // the stream does not exist. 246 func (m *Type) Read(id string) (*StreamStatus, error) { 247 m.lock.Lock() 248 defer m.lock.Unlock() 249 250 if m.closed { 251 return nil, types.ErrTypeClosed 252 } 253 254 wrapper, exists := m.streams[id] 255 if !exists { 256 return nil, ErrStreamDoesNotExist 257 } 258 259 return wrapper, nil 260 } 261 262 // Update attempts to stop an existing stream and replace it with a new version 263 // of the same stream. 264 func (m *Type) Update(id string, conf stream.Config, timeout time.Duration) error { 265 m.lock.Lock() 266 wrapper, exists := m.streams[id] 267 closed := m.closed 268 m.lock.Unlock() 269 270 if closed { 271 return types.ErrTypeClosed 272 } 273 if !exists { 274 return ErrStreamDoesNotExist 275 } 276 277 if reflect.DeepEqual(wrapper.config, conf) { 278 return nil 279 } 280 281 if err := m.Delete(id, timeout); err != nil { 282 return err 283 } 284 return m.Create(id, conf) 285 } 286 287 // Delete attempts to stop and remove a stream by its ID. Returns an error if 288 // the stream was not found, or if clean shutdown fails in the specified period 289 // of time. 290 func (m *Type) Delete(id string, timeout time.Duration) error { 291 m.lock.Lock() 292 if m.closed { 293 m.lock.Unlock() 294 return types.ErrTypeClosed 295 } 296 297 wrapper, exists := m.streams[id] 298 m.lock.Unlock() 299 if !exists { 300 return ErrStreamDoesNotExist 301 } 302 303 if err := wrapper.strm.Stop(timeout); err != nil { 304 return err 305 } 306 307 m.lock.Lock() 308 delete(m.streams, id) 309 m.lock.Unlock() 310 311 return nil 312 } 313 314 //------------------------------------------------------------------------------ 315 316 // Stop attempts to gracefully shut down all active streams and close the 317 // stream manager. 318 func (m *Type) Stop(timeout time.Duration) error { 319 m.lock.Lock() 320 defer m.lock.Unlock() 321 322 resultChan := make(chan string) 323 324 for k, v := range m.streams { 325 go func(id string, strm *StreamStatus) { 326 if err := strm.strm.Stop(timeout); err != nil { 327 resultChan <- id 328 } else { 329 resultChan <- "" 330 } 331 }(k, v) 332 } 333 334 failedStreams := []string{} 335 for i := 0; i < len(m.streams); i++ { 336 if failedStrm := <-resultChan; len(failedStrm) > 0 { 337 failedStreams = append(failedStreams, failedStrm) 338 } 339 } 340 341 m.streams = map[string]*StreamStatus{} 342 m.closed = true 343 344 if len(failedStreams) > 0 { 345 return fmt.Errorf("failed to gracefully stop the following streams: %v", failedStreams) 346 } 347 return nil 348 } 349 350 //------------------------------------------------------------------------------