What is the correct way to achieve graceful shutdown of background processes in Uber FX? This is a common problem that many people encounter when using Uber FX. As a powerful background task processing framework, Uber FX provides a simple and effective way to manage and process background tasks. In this article, PHP editor Zimo will introduce you how to correctly close the background process to ensure the stability and normal operation of the program.
Suppose I have a service in my Uber FX application that is supposed to perform some background activity, such as polling an external API. I can run background tasks by triggering a goroutine, but what is the correct way to stop them?
As a possible implementation, let us consider the following example:
package main import ( "context" "log" "sync" "time" "go.uber.org/fx" ) type AwesomeService struct { // context to use for background processes bg context.Context // to trigger background processes stopping cancel context.CancelFunc // to wait for background processes to gracefully finish wg *sync.WaitGroup } func New(lc fx.Lifecycle) *AwesomeService { bg, cancel := context.WithCancel(context.Background()) service := &AwesomeService{ bg: bg, cancel: cancel, wg: new(sync.WaitGroup), } lc.Append(fx.Hook{ OnStart: service.start, OnStop: service.stop, }) return service } func (s *AwesomeService) start(_ context.Context) error { s.runBackgroundProcess() log.Println("Start done") return nil } func (s *AwesomeService) stop(_ context.Context) error { s.cancel() s.wg.Wait() log.Println("Stop done") return nil } // runBackgroundProcess does some work till context is done. func (s *AwesomeService) runBackgroundProcess() { s.wg.Add(1) go func() { defer s.wg.Done() for { select { case <-s.bg.Done(): return case <-time.After(1 * time.Second): log.Println("Working...") } } }() } func main() { fx.New( fx.Provide(New), fx.Invoke(func(*AwesomeService) {}), ).Run() }
Some notes:
fx.Lifecycle
hook. OnStart
/OnStop
methods because they are different contexts and correspond to start/stop activities, not the app lifecycle context. Concerns and Questions:
In my opinion, using a context is just fine, but you can also communicate the shutdown signal via a channel to any Go routine you want. See sample code below.
Yes, you should also wait for the wait group count to return to zero before closing the application completely. So you first close the channel and then wait on the wait group.
package main import ( "context" "log" "sync" "time" "go.uber.org/fx" ) type AwesomeService struct { // channel to shutdown background processes shutdown chan struct{} // to wait for background processes to gracefully finish wg *sync.WaitGroup } func New(lc fx.Lifecycle) *AwesomeService { service := &AwesomeService{ shutdown: make(chan struct{}), wg: new(sync.WaitGroup), } lc.Append(fx.Hook{ OnStart: service.start, OnStop: service.stop, }) return service } func (s *AwesomeService) start(_ context.Context) error { s.runBackgroundProcess() log.Println("Start done") return nil } func (s *AwesomeService) stop(_ context.Context) error { close(s.shutdown) s.wg.Wait() log.Println("Stop done") return nil } // runBackgroundProcess does some work till context is done. func (s *AwesomeService) runBackgroundProcess() { s.wg.Add(1) go func() { defer s.wg.Done() for { select { case <-s.shutdown: return case <-time.After(1 * time.Second): log.Println("Working...") } } }() } func main() { fx.New( fx.Provide(New), fx.Invoke(func(*AwesomeService) {}), ).Run() }
The above is the detailed content of What is the correct way to achieve graceful shutdown of background processes in Uber FX?. For more information, please follow other related articles on the PHP Chinese website!