Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

A background

After the deployment of the Web project written by us, we often restart the service for configuration change or function iteration. The simple kill -9 PID method will force the process to shut down, which will lead to the failure of the server currently processing requests. Is there a more elegant way to shut down or restart the service?

To read this article, you need to understand some of the concepts of signals in UNIX systems.

Ii. Implementation Scheme

The 2.1 Linux signal

2.1.1 Signal name and number

Each signal has a name and a number that starts with “SIG”, such as “SIGIO”, “SIGCHLD”, and so on. The signals are defined in the signal.h header file, and the signal names are all defined as positive integers. You can run the kill -l command to check the name and serial number of the signal. The signal is numbered from 1 and no signal 0 exists. Kill has a special application for signal 0.

root@1204nStrive:~# kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62)  SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAXCopy the code

2.1.2 Signal processing

Signal processing has three methods, respectively: ignore, capture and default action

  • Ignore signals. Most signals can be processed in this way, but there are two types of signals that cannot be ignored (respectivelySIGKILLandSIGSTOP). Because they provide the kernel and the superuser with a reliable way to terminate and stop a process, if ignored, the process becomes an unmanageable process, which the kernel designers clearly do not want to see
  • To capture a signal, you need to tell the kernel how the user wants to process a particular signal. In other words, you need to write a signal processing function and then tell the kernel that function. When the signal is generated, the kernel calls a user-defined function to realize some signal processing.
  • System default action, for each signal, the system corresponds to the default processing action, when the signal occurs, the system will automatically execute. However, most of the solutions to the system are crude: kill the process. Specific signal default actions can be usedman 7 signalTo see the specific definition of the system. Here, I will not expand in detail, need to view, you can view. Also refer to the P251 — P256 section of Advanced Programming for UNIX Environments (Part 3) for detailed instructions on each signal.

2.2 Graceful Shutdown

2.2.1 What is an elegant shutdown

Graceful shutdown means that the server does not shut down immediately after the shutdown command is issued, but exits the program after all the requests that are still being processed are finished. It is a kind of client-friendly shutdown mode. Pressing Ctrl+C to shut down the server will force the process to end, causing problems with ongoing requests.

2.2.2 Elegant shutdown

After Go 1.8, the Shutdown() method built into HTTP.server supports elegant Shutdown, as follows

Three practical

3.1 Http.server built-in Shutdown() method


package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

// @title Docker monitoring service
/ / @ version 1.0
// @description gin shutdown

// @contact.name API Support
// @contact.url http://www.swagger.io/support

/ / @ license. Name the Apache 2.0
/ / @ license. The url http://www.apache.org/licenses/LICENSE-2.0.html

/ / @ host 127.0.0.1:9009
// @BasePath
func main(a) {
	r := gin.Default()
	r.GET("/".func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "gin %s"."ok")
	})
	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	server := http.Server{
		Addr:    ": 8080",
		Handler: r,
	}

	go func(a) {
		iferr := server.ListenAndServe(); err ! =nil&& err ! = http.ErrServerClosed { log.Fatal("server listen err:%s", err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	// block here
	<-quit

	ctx, channel := context.WithTimeout(context.Background(), 5*time.Second)

	defer channel()
	iferr := server.Shutdown(ctx); err ! =nil {
		log.Fatal("server shutdown error")
	}
	log.Println("server exiting...")}Copy the code

How to verify the effect of an elegant shutdown?

The above code will run to start a Web service on port 8080, which registers only one route /, and the back-end service will sleep for 5 seconds before returning a response.

When we press Ctrl+C, we send syscall.sigint to tell the program to gracefully shut down, as follows:

  1. Open the terminal, compile and execute the code above
  2. Open a browser and visit127.0.0.1:8080 /“, the browser displays a blank screen and waits for a response from the server.
  3. In the endquicklyperformCtrl+CCommands are sent to the programsyscall.SIGINTsignal
  4. Instead of exiting immediately, the program will wait for our step 2 response to return and then exit, thus gracefully shutting down.

3.2 Graceful Restart

Elegant shutdown is implemented, so how to implement elegant restart?

We can do this by using fvbock/ Endless to replace the default ListenAndServe startup service with the following example code:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/fvbock/endless"
	"github.com/gin-gonic/gin"
)

func main(a) {
	router := gin.Default()
	router.GET("/".func(c *gin.Context) {
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "hello gin!")})// By default, the Endless server listens for the following signals:
	SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGTERM and syscall.SIGTSTP
	// SIGHUP signal will trigger 'fork/restart' for an elegant reboot (kill -1 PID will send SIGHUP signal)
	// Receiving syscall.SIGINT or syscall.SIGTERM triggers a graceful shutdown
	// Receiving SIGUSR2 triggers HammerTime
	// SIGUSR1 and SIGTSTP are used to trigger some user-defined hook functions
	if err := endless.ListenAndServe(": 8080", router); err! =nil{
		log.Fatalf("listen: %s\n", err)
	}

	log.Println("Server exiting")}Copy the code

How do you verify an elegant reboot?

We send syscall.SIGINT by executing the kill -1 pid command to tell the program to restart gracefully as follows:

  1. Open the terminal,go build -o graceful_restartCompile and execute./graceful_restart, the terminal outputs the current PID (assume 43682)
  2. Returns the function that handles the request in the codehello gin!Modified tohello q1mi!, compile againgo build -o graceful_restart
  3. Open a browser and visit127.0.0.1:8080 /“, the browser displays a blank screen and waits for a response from the server.
  4. In the endquicklyperformkill -1 43682Commands are sent to the programsyscall.SIGHUPsignal
  5. Wait for step 3 for the browser to receive the response messagehello gin!Later visit127.0.0.1:8080 /Will receivehello q1mi!The response.
  6. The program code is replaced without affecting the current pending request, enabling an elegant restart.

However, it should be noted that the PID of the application changes, because Endless restarts gracefully by forking new requests and exiting after the original process finishes processing the current request. This is not the case when your project uses software like Supervisor to manage processes.

Pay attention to the point

  1. To start the GO coroutine, an error other than HTTP.ErrServerClosed is required
iferr := rsv.ListenAndServe(); err ! =nil&& err ! = http.ErrServerClosed {Copy the code
  1. Define signal, block
	// Kill sends the syscall.sigterm signal by default
	// kill -2 send syscall.SIGINT signal
	// kill -9 sends the syscall.SIGKILL signal, but it cannot be captured, so there is no need to add it
	// signal.Notify Forwards the received syscall.SIGINT or syscall.SIGTERM signal to quit
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)  // There is no blocking here
	<-quit  // Block here, and proceed only when the above two signals are received
	log.Println("Shutdown Server ...")
Copy the code

conclusion

Both graceful shutdown and graceful restart are ultimately by listening to specific system signals, and then performing certain logical processing to ensure that the current system is processing requests properly before shutting down the current process. Using graceful shutdown or graceful restart, and how to achieve this, depends on the actual situation of the project.