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.Notify
After running our service in
main
function as a new goroutine, we usesignal.Notify
to listen to the system signal to terminate the process. (e.g.Ctrl+C
)This function relays incoming signals to the channel
quitSignal
provided 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.WithTimeout
After receiving the
quitSignal
, we call theStop
method of our service to do the cleanup work.It’s good to use
context.WithTimeout
to 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