SoFunction
Updated on 2025-03-05

Detailed explanation of how to hot restart golang server

Server-side code often needs to be upgraded. The commonly used method for upgrading online systems is to ensure that at least one service is available during the upgrade through front-end load balancing (such as nginx) and upgrade in sequence (grayscale).

Another more convenient method is to do a hot restart on the application, directly upgrade the application without stopping service.

principle

The principle of hot restart is very simple, but it involves many details such as some system calls and the transfer of file handles between parent and child processes.
The processing process is divided into the following steps:

  • Monitoring signal (USR2)
  • When a signal is received, the fork child process (using the same startup command) passes the socket file descriptor that the service is listening to to the child process
  • The child process listens to the parent process's socket. At this time, both the parent and the child process can receive requests.
  • After the child process is successfully started, the parent process stops receiving the new connection and waits for the old connection processing to complete (or timeout)
  • The parent process exits and the upgrade is completed

detail

  • The parent process passes the socket file descriptor to the child process through the command line, or environment variables, etc.
  • The child process uses the same command line as the parent process when it starts. For golang, it overwrites the old program with an updated executable program.
  • ()The elegant closing method is a new feature of go1.8
  • (l) The method returns immediately when Shutdown, and the Shutdown method blocks until the context completes, so the Shutdown method must be written in the main goroutine

Code

package main
import (
  "context"
  "errors"
  "flag"
  "log"
  "net"
  "net/http"
  "os"
  "os/exec"
  "os/signal"
  "syscall"
  "time"
)
 
var (
  server  *
  listener 
  graceful = ("graceful", false, "listen on fd open 3 (internal use only)")
)
 
func handler(w , r *) {
  (20 * )
  ([]byte("hello world233333!!!!"))
}
 
func main() {
  ()
 
  ("/hello", handler)
  server = &{Addr: ":9999"}
 
  var err error
  if *graceful {
    ("main: Listening to existing file descriptor 3.")
    // : If non-nil, entry i becomes file descriptor 3+i.
    // when we put socket FD at the first entry, it will always be 3(0+3)
    f := (3, "")
    listener, err = (f)
  } else {
    ("main: Listening on a new file descriptor.")
    listener, err = ("tcp", )
  }
 
  if err != nil {
    ("listener error: %v", err)
  }
 
  go func() {
    // () stops Serve() immediately, thus () should not be in main goroutine
    err = (listener)
    (" err: %v\n", err)
  }()
  signalHandler()
  ("signal end")
}
 
func reload() error {
  tl, ok := listener.(*)
  if !ok {
    return ("listener is not tcp listener")
  }
 
  f, err := ()
  if err != nil {
    return err
  }
 
  args := []string{"-graceful"}
  cmd := ([0], args...)
   = 
   = 
  // put socket FD at the first entry
   = []*{f}
  return ()
}
 
func signalHandler() {
  ch := make(chan , 1)
  (ch, , , syscall.SIGUSR2)
  for {
    sig := <-ch
    ("signal: %v", sig)
 
    // timeout context for shutdown
    ctx, _ := ((), 20*)
    switch sig {
    case , :
      // stop
      ("stop")
      (ch)
      (ctx)
      ("graceful shutdown")
      return
    case syscall.SIGUSR2:
      // reload
      ("reload")
      err := reload()
      if err != nil {
        ("graceful restart error: %v", err)
      }
      (ctx)
      ("graceful reload")
      return
    }
  }
}

references

Graceful Restart in Golang

facebookgo/grace

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.