@@ -83,6 +83,7 @@ type Filebeat struct {
8383 pipeline beat.PipelineConnector
8484 logger * logp.Logger
8585 otelStatusFactoryWrapper func (cfgfile.RunnerFactory ) cfgfile.RunnerFactory
86+ runReady * closeOnce
8687}
8788
8889type PluginFactory func (beat.Info , statestore.States ) []v2.Plugin
@@ -174,6 +175,7 @@ func newBeater(b *beat.Beat, plugins PluginFactory, rawConfig *conf.C) (beat.Bea
174175
175176 fb := & Filebeat {
176177 done : make (chan struct {}),
178+ runReady : & closeOnce {ch : make (chan struct {})},
177179 config : & config ,
178180 moduleRegistry : moduleRegistry ,
179181 pluginFactory : plugins ,
@@ -270,6 +272,9 @@ func (fb *Filebeat) loadModulesPipelines(b *beat.Beat) error {
270272func (fb * Filebeat ) Run (b * beat.Beat ) error {
271273 var err error
272274 config := fb .config
275+ // Close runReady so that Shutdown doesn't have to wait no
276+ // matter how we exit from Run.
277+ defer fb .runReady .Close ()
273278
274279 if b .Manager != nil {
275280 b .Manager .RegisterDiagnosticHook ("input_metrics" , "Metrics from active inputs." ,
@@ -530,6 +535,8 @@ func (fb *Filebeat) Run(b *beat.Beat) error {
530535
531536 // Add done channel to wait for shutdown signal
532537 waitFinished .AddChan (fb .done )
538+ // Safe for Shutdown to be called
539+ fb .runReady .Close ()
533540 waitFinished .Wait ()
534541
535542 // Stop reloadable lists, autodiscover -> Stop crawler -> stop inputs -> stop harvesters.
@@ -588,9 +595,24 @@ func (fb *Filebeat) Run(b *beat.Beat) error {
588595
589596// Stop is called on exit to stop the crawling, spooling and registration processes.
590597func (fb * Filebeat ) Stop () {
598+ fb .StopWithContext (context .Background ())
599+ }
600+
601+ // StopWithContext is like Stop but respects ctx when waiting for Run to reach
602+ // its ready state, so the caller's deadline is not consumed by the wait.
603+ func (fb * Filebeat ) StopWithContext (ctx context.Context ) {
591604 fb .logger .Info ("Stopping filebeat" )
592605
593- // Stop Filebeat
606+ // Wait for Run to reach waitFinished.Wait() before closing done, so that
607+ // Stop is never delivered before the beater is ready to handle it.
608+ select {
609+ case <- fb .runReady .ch :
610+ case <- ctx .Done ():
611+ fb .logger .Warn ("Context cancelled waiting for Run to reach ready state; stopping anyway" )
612+ case <- time .After (5 * time .Second ):
613+ fb .logger .Warn ("Timed out waiting for Run to reach ready state; stopping anyway" )
614+ }
615+
594616 fb .stopOnce .Do (func () { close (fb .done ) })
595617}
596618
@@ -605,3 +627,14 @@ func newPipelineLoaderFactory(ctx context.Context, esConfig *conf.C, info beat.I
605627 }
606628 return pipelineLoaderFactory
607629}
630+
631+ type closeOnce struct {
632+ ch chan struct {}
633+ once sync.Once
634+ }
635+
636+ func (coc * closeOnce ) Close () {
637+ coc .once .Do (func () {
638+ close (coc .ch )
639+ })
640+ }
0 commit comments