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
}