Home  >  Article  >  Backend Development  >  What is the correct way to achieve graceful shutdown of background processes in Uber FX?

What is the correct way to achieve graceful shutdown of background processes in Uber FX?

WBOY
WBOYforward
2024-02-08 23:27:081101browse

在 Uber FX 中实现后台进程正常关闭的正确方法是什么?

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.

Question content

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:

  • The service is connected to the application lifecycle by using the fx.Lifecycle hook.
  • I can't rely on and use the context in the OnStart/OnStop methods because they are different contexts and correspond to start/stop activities, not the app lifecycle context.

Concerns and Questions:

  • The example given is quite heavy on tracking background tasks. Additionally, storing context in a struct is an anti-pattern. Is there any way to simplify this?
  • Should I wait for the goroutine to complete if there are no resources to release?

Workaround

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!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete