SoFunction
Updated on 2025-03-05

Detailed explanation of golang's httpserver elegant restart method

Preface

When I was working on golangserver last year, the headache inside was that when online services were released, a large number of users' requests would be reconnected when they were released. At that time, I thought about many methods, and finally I fell into a project on github, a golang project on Facebook. At that time, I simply studied and tested it and could be used directly internally. During this period, I suddenly remembered it and wanted to study this project carefully again.

In principle, it is a process like this:

1) Publish new bin files to overwrite old bin files

2) Send a semaphore to tell the running process and restart it

3) After the running process receives the signal, it will start a new bin file in the form of a child process.

4) The new process accepts new requests and processes them

5) The old process no longer accepts the request, but it has to wait for the processing of the request being processed to be completed. After all the processed requests are processed, they will automatically exit.

6) After the old process exits, the new process will be adopted by the init process, but will continue to serve.

So step by step, the key is how to do it after starting from step 2, so let's first look at the implementation of step 2. This should be said to be very simple. Send the semaphore to a process and use the kill command. There are 3 semaphores sent in the Facebook project: SIGINT, SIGTERM, and SIGUSR2. The program will exit directly after receiving the first two signals, and the latter signal SIGUSR2 will perform the so-called elegant restart.

Step 3: After the running process receives the SIGUSR2 signal, it will start the new bin file in the form of a child process. First, go to the code and read it directly: /facebookgo/grace/blob/master/gracehttp/

func (a *app) signalHandler(wg *) {
 ch := make(chan , 10)
 (ch, , , syscall.SIGUSR2)
 for {
  sig := <-ch
  switch sig {
  case , : 
   // this ensures a subsequent INT/TERM will trigger standard go behaviour of
   // termination. Execute the standard go termination behavior and the program ends   (ch)
   (wg)
   return
  case syscall.SIGUSR2: // Start the elegant restart here   err := () 
   // This function does not have a specific implementation function in the source code, but a hook function is reserved. Users can register their own functions and can do some custom things before restarting.  In general, there is nothing to do unless there are some special service environments or state savings. At least, our server has not yet encountered it.   if err != nil {
     <- err
   }
   // we only return here if there's an error, otherwise the new process
   // will send us a TERM when it's ready to trigger the actual shutdown.
   if _, err := (); err != nil { // The official so-called elegant restart begins here     <- err
   }
  }
 }
}

Let’s take a look at the basic process:

func (n *Net) StartProcess() (int, error) {
 listeners, err := () // Get the currently listening port, this is also the key point. The following is the highlight if err != nil {
  return 0, err
 }
 
 // Extract the fds from the listeners. Extract the file descriptor from the listener port files := make([]*, len(listeners))
 for i, l := range listeners {
  files[i], err = l.(filer).File()
  if err != nil {
   return 0, err
  }
  defer files[i].Close()
 }
 
 // Use the original binary location. This works with symlinks such that if
 // the file it points to has been changed we will use the updated symlink.
 // Get the road force of executable bin file, or it can be a link road force, and the latest link path will be used as the starting file road force argv0, err := ([0])
 if err != nil {
  return 0, err
 }
 
 // Pass on the environment and replace the old count key with the new one.
 // Get LISTEN_FDS to swap into variable value var env []string
 for _, v := range () {
  if !(v, envCountKeyPrefix) {
   env = append(env, v)
  }
 }
 env = append(env, ("%s%d", envCountKeyPrefix, len(listeners)))
 
 allFiles := append([]*{, , }, files...)
 // Here we call a golang underlying process startup function to specify the parameters obtained above to start the process process, err := (argv0, , &{
  Dir: originalWD,
  Env: env,
  Files: allFiles,
 })
 if err != nil {
  return 0, err
 }
 // Return the new process id. return , nil 
}

The above is the process of starting a new process and taking over the listening port. Generally speaking, the port cannot be listened repeatedly, so a more special method is needed here. From the above code, it is to read the file descriptor of the listening port and pass the file descriptor of the listening port to the child process. The child process implements monitoring of the port from this file descriptor.

Another special thing is how to close the old interface. The closing must be done after the request has been processed. For this reason, Facebook students opened another project httpdown, inheriting the original httpserver, but there are more maintenance and processing of various link states, which will be analyzed later.

Summarize

The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.