测试通过,并写了中文自述文件

master
会PS的小码农 4 years ago
commit 4519b7ee56

1
.gitignore vendored

@ -0,0 +1 @@
/go-graceful-restart-example

@ -0,0 +1,24 @@
Copyright (c) 2022, EdisonLiu
Copyright (c) 2014, Scalingo
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,37 @@
# Server graceful restart with Go
## 安装并运行服务器(打开终端运行一下命令)
```
$ git clone
$ go mod tidy
$ go run build //不打包第二次以后无法正常重启,会报 Fail to fork1 no such file or directory
$ go-graceful-restart-example
2014/12/14 20:26:42 [Server - 4301] Listen on [::]:12345
[...]
```
## 测试客户端(新开一个终端)
```
$ cd /client
$ go run pong.go
```
## 优雅重启服务(再开一个终端)
```
# 服务器pid包含在其日志中例如:[Server - 1204933] 1204933即是当前pid
$ go run build //修改服务端代码后重新打包,再运行才能看到效果
$ kill -HUP <server pid>
```
## 延时停止服务
让当前请求等待10秒后强制完成并停止服务。
```
$ kill -TERM <server pid>
```
## Gist of output
https://gist.github.com/Soulou/7ca6a2d4f475f8e2345e

@ -0,0 +1,37 @@
# Server graceful restart with Go
## Install and run the server
```
$ go get github.com/Scalingo/go-graceful-restart-example
$ go-graceful-restart-example
2014/12/14 20:26:42 [Server - 4301] Listen on [::]:12345
[...]
```
## Connect with the client
```
$ cd $GOPATH/src/github.com/Scalingo/go-graceful-restart-example/client
$ go run pong.go
```
## Graceful restart
```
# The server pid is included in its log, in the example: 4301
$ kill -HUP <server pid>
```
## Stop with timeout
Let 10 seconds for the current requests to finish.
```
$ kill -TERM <server pid>
```
## Gist of output
https://gist.github.com/Soulou/7ca6a2d4f475f8e2345e

@ -0,0 +1,30 @@
package main
import (
"fmt"
"log"
"net"
)
func main() {
sock, err := net.Dial("tcp", ":12345")
if err != nil {
log.Fatalln("fail to dial ':12345':", err)
}
buffer := make([]byte, 64)
for {
n, err := sock.Read(buffer)
if err != nil {
log.Fatalln("fail to read socket:", err)
}
fmt.Printf(string(buffer))
fmt.Printf("[Client] Received %d bytes, '%s'\n", n, string(buffer[:n]))
_, err = sock.Write([]byte("pong"))
if err != nil {
log.Fatalln("fail to write 'pong' to the socket:", err)
}
fmt.Printf("[Client] Sent 'ping'\n")
}
}

@ -0,0 +1,3 @@
module github.com/Scalingo/go-graceful-restart-example
go 1.17

@ -0,0 +1,32 @@
package logger
import (
"fmt"
"log"
"os"
)
type Logger struct {
logger *log.Logger
prefix string
pid int
}
func New(prefix string) *Logger {
l := &Logger{logger: log.New(os.Stdout, "", log.LstdFlags)}
l.prefix = fmt.Sprintf("[%s - %d]", prefix, os.Getpid())
return l
}
func (l *Logger) Println(args ...interface{}) {
l.logger.Println(append([]interface{}{l.prefix}, args...)...)
}
func (l *Logger) Printf(format string, args ...interface{}) {
l.logger.Printf(l.prefix+" "+format, args...)
}
func (l *Logger) Fatalln(args ...interface{}) {
l.Println(args...)
os.Exit(-1)
}

@ -0,0 +1,73 @@
package main
import (
"os"
"os/signal"
"syscall"
"time"
"github.com/Scalingo/go-graceful-restart-example/logger"
"github.com/Scalingo/go-graceful-restart-example/server"
)
func main() {
log := logger.New("Server")
var s *server.Server
var err error
if os.Getenv("_GRACEFUL_RESTART") == "true" {
s, err = server.NewFromFD(log, 4)
} else {
s, err = server.New(log, 12345)
}
if err != nil {
log.Fatalln("fail to init server:", err)
}
log.Println("Listen on", s.Addr())
go s.StartAcceptLoop()
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGHUP, syscall.SIGTERM)
for sig := range signals {
if sig == syscall.SIGTERM {
// Stop accepting new connections
s.Stop()
// Wait a maximum of 10 seconds for existing connections to finish
err := s.WaitWithTimeout(10 * time.Second)
if err == server.WaitTimeoutError {
log.Printf("Timeout when stopping server, %d active connections will be cut.\n", s.ConnectionsCounter())
os.Exit(-127)
}
// Then the program exists
log.Println("Server shutdown successful")
os.Exit(0)
} else if sig == syscall.SIGHUP {
// Stop accepting requests
s.Stop()
// Get socket file descriptor to pass it to fork
listenerFD, err := s.ListenerFD()
if err != nil {
log.Fatalln("Fail to get socket file descriptor:", err)
}
// Set a flag for the new process start process
os.Setenv("_GRACEFUL_RESTART", "true")
execSpec := &syscall.ProcAttr{
Env: os.Environ(),
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFD},
}
// Fork exec the new version of your server
fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)
if err != nil {
log.Fatalln("Fail to fork1", err)
}
log.Println("SIGHUP received: fork-exec to", fork)
// Wait for all conections to be finished
s.Wait()
log.Println(os.Getpid(), "Server gracefully shutdown")
// Stop the old server, all the connections have been closed and the new one is running
os.Exit(0)
}
}
}

@ -0,0 +1,24 @@
package server
import "sync"
type ConnectionManager struct {
*sync.WaitGroup
Counter int
}
func NewConnectionManager() *ConnectionManager {
cm := &ConnectionManager{}
cm.WaitGroup = &sync.WaitGroup{}
return cm
}
func (cm *ConnectionManager) Add(delta int) {
cm.Counter += delta
cm.WaitGroup.Add(delta)
}
func (cm *ConnectionManager) Done() {
cm.Counter--
cm.WaitGroup.Done()
}

@ -0,0 +1,137 @@
package server
import (
"errors"
"fmt"
"net"
"os"
"time"
"github.com/Scalingo/go-graceful-restart-example/logger"
)
type Server struct {
cm *ConnectionManager
socket *net.TCPListener
logger *logger.Logger
}
func New(logger *logger.Logger, port int) (*Server, error) {
s := &Server{cm: NewConnectionManager(), logger: logger}
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return nil, fmt.Errorf("fail to resolve addr: %v", err)
}
sock, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, fmt.Errorf("fail to listen tcp: %v", err)
}
s.socket = sock
return s, nil
}
func NewFromFD(logger *logger.Logger, fd uintptr) (*Server, error) {
s := &Server{cm: NewConnectionManager(), logger: logger}
file := os.NewFile(3, "")
// file := os.NewFile(fd, "/tmp/sock-go-graceful-restart")
listener, err := net.FileListener(file)
if err != nil {
return nil, errors.New("File to recover socket from file descriptor: " + err.Error())
}
listenerTCP, ok := listener.(*net.TCPListener)
if !ok {
return nil, fmt.Errorf("File descriptor %d is not a valid TCP socket", fd)
}
s.socket = listenerTCP
return s, nil
}
func (s *Server) Stop() {
// Accept will instantly return a timeout error
s.socket.SetDeadline(time.Now())
}
func (s *Server) ListenerFD() (uintptr, error) {
file, err := s.socket.File()
if err != nil {
return 0, err
}
return file.Fd(), nil
}
func (s *Server) Wait() {
s.cm.Wait()
}
var WaitTimeoutError = errors.New("timeout")
func (s *Server) WaitWithTimeout(duration time.Duration) error {
timeout := time.NewTimer(duration)
wait := make(chan struct{})
go func() {
s.Wait()
wait <- struct{}{}
}()
select {
case <-timeout.C:
return WaitTimeoutError
case <-wait:
return nil
}
}
func (s *Server) StartAcceptLoop() {
for {
conn, err := s.socket.Accept()
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
s.logger.Println("Stop accepting connections")
return
}
s.logger.Println("[Error] fail to accept:", err)
}
go func() {
s.cm.Add(1)
s.handleConn(conn)
s.cm.Done()
}()
}
}
func (s *Server) handleConn(conn net.Conn) {
tick := time.NewTicker(time.Second)
buffer := make([]byte, 64)
for {
select {
case <-tick.C:
_, err := conn.Write([]byte("ping6"))
if err != nil {
s.logger.Println("[Error] fail to write 'ping':", err)
conn.Close()
return
}
s.logger.Printf("[Server] Sent 'ping'\n")
n, err := conn.Read(buffer)
if err != nil {
s.logger.Println("[Error] fail to read from socket:", err)
conn.Close()
return
}
s.logger.Printf("[Server] OK: read %d bytes: '%s'\n", n, string(buffer[:n]))
}
}
}
func (s *Server) Addr() net.Addr {
return s.socket.Addr()
}
func (s *Server) ConnectionsCounter() int {
return s.cm.Counter
}
Loading…
Cancel
Save