An easy and graceful way to start the main function and
organize the entry point and closing method of your service in Go.
Introduction
-
signal.NotifyAfter running our service in
mainfunction as a new goroutine, we usesignal.Notifyto listen to the system signal to terminate the process. (e.g.Ctrl+C)This function relays incoming signals to the channel
quitSignalprovided by us, which blocks the main goroutine.func signal.Notify(c chan<- os.Signal, sig ...os.Signal)Notify causes package signal to relay incoming signals to c. If no signals are provided, all incoming signals will be relayed to c. Otherwise, just the provided signals will.
Package signal will not block sending to c: the caller must ensure that c has sufficient buffer space to keep up with the expected signal rate. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient
-
context.WithTimeoutAfter receiving the
quitSignal, we call theStopmethod of our service to do the cleanup work.It’s good to use
context.WithTimeoutto set a time limit for this quitting cleanup work, ensuring that the program will exit anyway after the time limit.
Template Code
File Structure
1
2
3
4
5
foo/
├─ go.mod
├─ myapp/
│ ├─ service.go
├─ main.go
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"foo/myapp"
)
func main() {
// Load your configs and init your loggers and other necessary modules, etc.
if err := InitModules(); err != nil {
panic(err)
}
// Entry point of your service
s := myapp.NewService()
s.Run()
// main goroutine waiting for shutdown
quitSignal := make(chan os.Signal, 1)
signal.Notify(quitSignal, os.Interrupt, syscall.SIGHUP, syscall.SIGINT,
syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGTSTP)
<-quitSignal
// Cleanup task with time limit before quitting
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := s.Stop(ctx); err != nil {
fmt.Println(err)
}
}
// Example: init your modules
func InitModules() error {
fmt.Println("preconditional modules init successfully")
return nil
}
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package myapp
import (
"context"
"fmt"
)
type Service struct{}
func NewService() *Service {
return &Service{}
}
func (s *Service) Run() {
go func() {
// Your service logic
fmt.Println("Service running...")
}()
}
func (s *Service) Stop(ctx context.Context) error {
done := make(chan struct{})
go func(done chan<- struct{}) {
// Your quitting procedures to do some cleanup work
fmt.Println("Goodbye :)")
close(done)
}(done)
select {
case <-done:
return nil
case <-ctx.Done():
return fmt.Errorf("forced termination: %v", ctx.Err())
}
}
Licensed under CC BY-SA 4.0
Comments