Within the Kubernetes deployment environment, applications will actually be sent the SIGTERM signal first if it has been decided the pod holding the application needs to be stopped. The application may choose to respond to said signal or not. If the application does not respond, eventually, the SIGKILL signal will then be sent, and the application will be shut down accordingly.
package main import ( "bufio" "fmt" "math/rand" "os" "os/signal" "syscall" "time" ) func main() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) f, _ := os.OpenFile("test.txt", os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644) bw := bufio.NewWriter(f) iter := 0 expectedCounter := 0 for { time.Sleep(time.Second) select { case a := <-sigs: fmt.Printf("Received signal to end application :: %v\n", a) fmt.Printf("%v items left in buffer. Flushing it\n", iter) bw.Flush() iter = 0 fmt.Printf("expected number of lines: %v\n", expectedCounter) os.Exit(0) default: iter++ fmt.Println("generated new item") expectedCounter++ dataVal := fmt.Sprintf("generated val: %v\n", rand.Int()) bw.WriteString(dataVal) if iter >= 10 { bw.Flush() iter = 0 } } } }
In order to get our application to intercept and capture the OS signals, we would need to create a channel that would be able to capture the incoming signal. This is done in the first line after the definition of the main function.
The line signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) is the line to determine which signals the application should respond with. For testing purposes, we would set it up such that it would be able to respond and capture for the Termination signal as well Interrupt signal. SIGTERM might be slightly harder to test, but for the Interrupt signal can be easily tested by simply stopping the running on the application while it is running on the terminal. It should still be possible to send the terminate signal to the running application, but it depends on the environment that you are running this application in.
The most critical piece of this whole application when it comes to the setup of the capability to capture incoming OS signals is the use of the select keyword. Most of the time, we would use the select statement almost as if it can potentially be replaced with if and else if statements. The select statement is usually used here, and it would trigger the statement for doing the cleanup steps once the channel contains the incoming OS signal. We will be wrapping this with a for loop as we essentially want to run our default logic of generating and writing said data into a file most of the time, but then, the moment the OS signal is captured, we would immediately terminate it.
zzh@ZZHPC:/zdata/MyPrograms/Go/bbb$ go run main.go generated new item generated new item generated new item generated new item generated new item ^CReceived signal to end application :: interrupt 5 items left in buffer. Flushing it expected number of lines: 5
func main() { addr := flag.String("addr", ":4000", "HTTP network address") dbDriver := flag.String("driver", "mysql", "Database driver name") dsn := flag.String("dsn", "zzh:zzhpwd@tcp(localhost:3306)/zsnippetbox?parseTime=true", "Data source name") debug := flag.Bool("debug", false, "Enable debug mode") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) db, err := openDB(*dbDriver, *dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } defer db.Close() templateCache, err := newTemplateCache() if err != nil { logger.Error(err.Error()) os.Exit(1) } sessionManager := scs.New() sessionManager.Store = mysqlstore.New(db) sessionManager.Lifetime = 12 * time.Hour sessionManager.Cookie.Secure = true app := &application{ debug: *debug, logger: logger, templateCache: templateCache, sessionManager: sessionManager, models: models.NewModels(db), } srv := &http.Server{ Addr: *addr, Handler: app.routes(), ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), IdleTimeout: time.Minute, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } idleConnsClosed := make(chan struct{}) go func () { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) <-sigint // We received an interrupt or kill signal, shut down the HTTP server. if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: logger.Error(fmt.Sprintf("HTTP server Shutdown: %v", err.Error())) } close(idleConnsClosed) }() logger.Info("starting HTTP server", "addr", *addr) err = srv.ListenAndServe() if err != nil { if errors.Is(err, http.ErrServerClosed) { logger.Error("HTTP server closed") } else { // Error starting or closing listener: logger.Error(fmt.Sprintf("HTTP server ListenAndServe: %v", err.Error())) } } <-idleConnsClosed }
This way, the HTTP server can be shutdown gracefully and the 'defer db.Close()' will be executed when the program is interrupted.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律