ZhangZhihui's Blog  

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.

 

posted on   ZhangZhihuiAAA  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
 
点击右上角即可分享
微信分享提示