A warm restart

Zero Downtime: Old and new processes can be switched seamlessly, and client services can be maintained during replacement.

The principle of

  • The parent process listens for a restart signal
  • On receiving a restart signal, the parent process callsforkAt the same timesocketDescriptor to child process
  • The child receives and listens for the parentsocketThe descriptor
  • After the child successfully starts, the parent stops receiving new connections while waiting for the old connection to complete (or time out)
  • The parent process exits, and the hot restart is complete

implementation

package main

import (
	"context"
	"errors"
	"flag"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
)

var (
	server   *http.Server
	listener net.Listener = nil

	graceful = flag.Bool("graceful".false."listen on fd open 3 (internal use only)")
	message  = flag.String("message"."Hello World"."message to send"))func handler(w http.ResponseWriter, r *http.Request) {
	time.Sleep(5 * time.Second)
	w.Write([]byte(*message))
}

func main(a) {
	var err error

	// Parse the parameters
	flag.Parse()

	http.HandleFunc("/test", handler)
	server = &http.Server{Addr: ": 3000"}

	// Set listener listener (new or existing socket descriptor)
	if *graceful {
		// The child listens for the socket descriptor passed by the parent
		log.Println("listening on the existing file descriptor 3")
		// The child process's 0, 1, 2 are reserved for standard input, standard output, and error output, so the socket descriptor passed
		// should be placed in 3 of the child process
		f := os.NewFile(3."")
		listener, err = net.FileListener(f)
	} else {
		// The parent process listens for the newly created socket descriptor
		log.Println("listening on a new file descriptor")
		listener, err = net.Listen("tcp", server.Addr)
	}
	iferr ! =nil {
		log.Fatalf("listener error: %v", err)
	}

	go func(a) {
		err = server.Serve(listener)
		log.Printf("server.Serve err: %v\n", err)
	}()
	// Listen for signals
	handleSignal()
	log.Println("signal end")}func handleSignal(a) {
	ch := make(chan os.Signal, 1)
	// Listen for signals
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
	for {
		sig := <-ch
		log.Printf("signal receive: %v\n", sig)
		ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)
		switch sig {
		case syscall.SIGINT, syscall.SIGTERM: // Terminates process execution
			log.Println("shutdown")
			signal.Stop(ch)
			server.Shutdown(ctx)
			log.Println("graceful shutdown")
			return
		case syscall.SIGUSR2: // The process restarts hot
			log.Println("reload")
			err := reload() // Execute the hot restart function
			iferr ! =nil {
				log.Fatalf("graceful reload error: %v", err)
			}
			server.Shutdown(ctx)
			log.Println("graceful reload")
			return}}}func reload(a) error {
	tl, ok := listener.(*net.TCPListener)
	if! ok {return errors.New("listener is not tcp listener")}// Get the socket descriptor
	f, err := tl.File()
	iferr ! =nil {
		return err
	}
	// Set the parameters to be passed to the child process (including socket descriptors)
	args := []string{"-graceful"}
	cmd := exec.Command(os.Args[0], args...)
	cmd.Stdout = os.Stdout         // Standard output
	cmd.Stderr = os.Stderr         // Error output
	cmd.ExtraFiles = []*os.File{f} // File descriptor
	// Create and execute a child process
	return cmd.Start()
}

Copy the code

We run cmd.extrafiles = []* os.file {f} in the parent process to pass the socket descriptor to the child process, which obtains the descriptor by executing f := os.newfile (3, “”). Note that 0, 1, and 2 of the child processes are reserved for standard input, standard output, and error output respectively, so the socket descriptor passed by the parent process starts at 3 in the child process order.

test

Compile the program as the main, execute. / the main – the message “Graceful” Reload, visit http://localhost:3000/test, wait 5 seconds later, we can see that the Graceful Reload response.

To perform a Graceful Reload test, run the kill -usr2 [PID] command.

Run the kill -int [PID] command to perform a Graceful Shutdown test.

The resources

  • Gracehttp: Gracefully restart the Go program
  • Golang server hot restart, hot upgrade, hot update details