What is a good practice to wait for context cancel() to complete?

513 Views Asked by At

Given I have the program below, what is a good practice to make the method cancelled (service.Start()) complete (i.e. printing Done) before terminating by calling the cancel() function ? I only have a single Go routine so using a WaitGroup doesn't feel right. When I comment out the last line (time.Sleep) in the main() function it seems like the program finishes prematurely, as the service.Start() doesn't print Done. If I uncomment the 2 second sleep, then Done gets printed. I am just trying to find the best practice of achieving the canceled method to complete without terminating the program prematurely, but both using a WaitGroup or time.Sleep() seem to me as a bad practice for this specific program. The reason I am using a Go routine and an exit channel is because the service.Start() will contain logic that needs to run periodically until the program is stopped.

main:

func main() {
    log.Infoln("starting service..")
    ctx := context.Background()
    srv := service.Service{}

    exitCh := make(chan os.Signal, 1)
    signal.Notify(exitCh, syscall.SIGTERM, // terminate: stopped by `kill -9 PID`
        syscall.SIGINT, // interrupt: stopped by Ctrl + C
    )

    ctxCancel, cancel := context.WithCancel(ctx)
    go run(ctxCancel, srv, exitCh)

    <-exitCh // blocking until receive exit signal
    cancel()
    time.Sleep(time.Second * 2)
}

func run(ctx context.Context, srv service.Service, exitCh chan<- os.Signal) {
    defer func() {
        exitCh <- syscall.SIGTERM // send terminate signal when application stop naturally
    }()
    err := srv.Start(ctx)
    if err != nil {
        log.Warningf("canceling all operations: %s", err)
    }
}

service:

type Service struct {}

func (s *Service) Start(ctx context.Context) error {
    for i := 0; i < 5; i++ {
        select {
        case <-ctx.Done():
            log.Println("Done")
            return ctx.Err()
        default:
            log.Printf("i value: %d", i)
            time.Sleep(3 * time.Second)
        }
    }
    return nil
}
0

There are 0 best solutions below