Skip to content

Instantly share code, notes, and snippets.

@Fornax96
Created February 27, 2020 15:10
Show Gist options
  • Save Fornax96/da690190de5e1b678b6a377b9b09e98d to your computer and use it in GitHub Desktop.
Save Fornax96/da690190de5e1b678b6a377b9b09e98d to your computer and use it in GitHub Desktop.
go zero downtime restarts
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"time"
)
func main() {
fd, _ := strconv.Atoi(os.Getenv("HTTP_LISTEN_FD"))
serverID, _ := strconv.Atoi(os.Getenv("HTTP_SERVER_ID"))
fmt.Println("Starting server", serverID)
// Get or create the listener file
var err error
var file *os.File
if fd == 0 {
// There is no file descriptor, create a new listener
listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: []byte{0, 0, 0, 0}, Port: 12345})
if err != nil {
panic(err)
}
if file, err = listener.File(); err != nil {
panic(err)
}
} else {
// Use the file descriptor which our parent passed to us
file = os.NewFile(uintptr(fd), "apifd")
}
// Set up a simple HTTP server to listen on the socket
mux := http.NewServeMux()
var i int
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
i++
fmt.Println("server " + strconv.Itoa(serverID) + " req " + strconv.Itoa(i))
time.Sleep(time.Second * 2)
w.Write([]byte("server " + strconv.Itoa(serverID) + " req " + strconv.Itoa(i) + "\n"))
})
listener, err := net.FileListener(file)
if err != nil {
panic(err)
}
server := &http.Server{Handler: mux}
go server.Serve(listener)
// Keep the server alive for 10 seconds
time.Sleep(time.Second * 10)
// Restart the process and pass our file descriptor to the child process. fd
// 0, 1 and 2 are occupied by stdin, stdout and stderr, so our fd will be 3
executable, _ := os.Executable()
cmd := exec.Command(executable)
cmd.Env = []string{"HTTP_LISTEN_FD=3", "HTTP_SERVER_ID=" + strconv.Itoa(serverID+1)}
cmd.ExtraFiles = []*os.File{file}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Start(); err != nil {
panic(err)
}
// Gracefully shut down this server
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
server.Shutdown(ctx)
cancel()
if serverID == 0 {
// This is the parent server. So we'll keep running to listen for OS
// signals
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGTERM)
select {
case sig := <-sigChan:
fmt.Println("Caught", sig, "signal, stopping...")
os.Exit(0)
}
}
fmt.Println("Stopping server", serverID)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment