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 2023-09-26 23:00  ZhangZhihuiAAA  阅读(2)  评论(0编辑  收藏  举报