mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 13:22:09 +08:00
Merge afce7f1a923dbdea9888aa3bedb5fc328f5758fd into 8659ab573cf7d26b2fa2a41e90075d84606188f1
This commit is contained in:
commit
717bd30a3c
43
README.md
43
README.md
@ -79,6 +79,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [http2 server push](#http2-server-push)
|
||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||
- [Prefork](#prefork)
|
||||
- [Don't trust all proxies](#dont-trust-all-proxies)
|
||||
- [Testing](#testing)
|
||||
- [Users](#users)
|
||||
@ -2248,6 +2249,47 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Prefork
|
||||
|
||||
Prefork is a functionality which makes use of SO_REUSEPORT and SO_REUSEADDR
|
||||
socket option feature (which is available on most operation systems) available
|
||||
for our router.
|
||||
|
||||
Benefits:
|
||||
- Reduces lock contention between workers accepting new connections.
|
||||
- Improve performance on multicore systems
|
||||
|
||||
Disadvantages:
|
||||
- when a worker is stalled by a blocking operation, the block affects not only
|
||||
connections that the worker has already accepted, but also connection requests
|
||||
that the kernel has assigned to the worker since it became blocked.
|
||||
|
||||
For actual definition and information about what this feature does and why
|
||||
it exists in gin, have a look at just the description part of [this](socket-nginx)
|
||||
release note article which made understanding of this feature a lot easier.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Creates a gin router with default middleware:
|
||||
// logger and recovery (crash-free) middleware
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "pong")
|
||||
})
|
||||
|
||||
// A number bigger than 1 or no number(in cpus with more
|
||||
// than one core) should be passed so that this functionality
|
||||
// actually applies on your api functionality.
|
||||
// In this case that I did not pass any number, if we assume
|
||||
// my cpu has 4 cores, so 4 processes will get created to
|
||||
// serve my api.
|
||||
router.Prefork()
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
## Don't trust all proxies
|
||||
|
||||
Gin lets you specify which headers to hold the real client IP (if any),
|
||||
@ -2380,3 +2422,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||
* [socket-nginx](https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1): SO_REUSEPORT and SO_REUSEADDR article
|
2
debug.go
2
debug.go
@ -17,7 +17,7 @@ const ginSupportMinGoVer = 16
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||
func IsDebugging() bool {
|
||||
return ginMode == debugCode
|
||||
return ginMode == debugCode && !IsChild()
|
||||
}
|
||||
|
||||
// DebugPrintRouteFunc indicates debug log output format.
|
||||
|
37
gin.go
37
gin.go
@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -150,6 +151,9 @@ type Engine struct {
|
||||
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
|
||||
ContextWithFallback bool
|
||||
|
||||
// Determines if master should create child processes or not
|
||||
doPrefork bool
|
||||
|
||||
delims render.Delims
|
||||
secureJSONPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
@ -196,6 +200,7 @@ func New() *Engine {
|
||||
UnescapePathValues: true,
|
||||
MaxMultipartMemory: defaultMultipartMemory,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
doPrefork: false,
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJSONPrefix: "while(1);",
|
||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||
@ -379,6 +384,12 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
||||
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
|
||||
if engine.doPrefork || IsChild() {
|
||||
err = engine.prefork(address)
|
||||
return
|
||||
}
|
||||
|
||||
err = http.ListenAndServe(address, engine.Handler())
|
||||
return
|
||||
}
|
||||
@ -645,6 +656,32 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
serveError(c, http.StatusNotFound, default404Body)
|
||||
}
|
||||
|
||||
func (engine *Engine) prefork(addr string) (err error) {
|
||||
return prefork(addr, engine)
|
||||
}
|
||||
|
||||
// Prefork will create 'number' amount of child processes
|
||||
// which will listen to the same address and port as other
|
||||
// child processes if 'number' is bigger than 1.
|
||||
//
|
||||
// This is a feature which enables use of SO_REUSEPORT
|
||||
// and SO_REUSEADDR socket option which is available in
|
||||
// most operation systems out there.
|
||||
//
|
||||
// More information:
|
||||
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/#
|
||||
func (engine *Engine) Prefork(number ...int) {
|
||||
n := runtime.GOMAXPROCS(0)
|
||||
if len(number) > 0 {
|
||||
n = number[0]
|
||||
}
|
||||
|
||||
if n > 1 {
|
||||
runtime.GOMAXPROCS(n)
|
||||
engine.doPrefork = true
|
||||
}
|
||||
}
|
||||
|
||||
var mimePlain = []string{MIMEPlain}
|
||||
|
||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.1
|
||||
github.com/goccy/go-json v0.10.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/libp2p/go-reuseport v0.2.0
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/pelletier/go-toml/v2 v2.0.6
|
||||
github.com/stretchr/testify v1.8.1
|
||||
|
2
go.sum
2
go.sum
@ -39,6 +39,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560=
|
||||
github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
|
135
prefork.go
Normal file
135
prefork.go
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019-present Fenny and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Code Sources In Here Come From: https://github.com/gofiber/fiber/blob/master/prefork.go
|
||||
*/
|
||||
package gin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
reuseport "github.com/libp2p/go-reuseport"
|
||||
)
|
||||
|
||||
const (
|
||||
envPreforkChildKey = "GIN_PREFORK_CHILD"
|
||||
envPreforkChildVal = "1"
|
||||
)
|
||||
|
||||
// Holds process of childs
|
||||
var children = map[int]*exec.Cmd{}
|
||||
|
||||
// IsChild determines if the current process is a child of Prefork
|
||||
func IsChild() bool {
|
||||
return os.Getenv(envPreforkChildKey) == envPreforkChildVal
|
||||
}
|
||||
|
||||
// watchMaster watches child procs
|
||||
func watchMaster() {
|
||||
if runtime.GOOS == "windows" {
|
||||
// finds parent process,
|
||||
// and waits for it to exit
|
||||
p, err := os.FindProcess(os.Getppid())
|
||||
if err == nil {
|
||||
_, _ = p.Wait()
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
// if it is equal to 1 (init process ID),
|
||||
// it indicates that the master process has exited
|
||||
for range time.NewTicker(time.Millisecond * 500).C {
|
||||
if os.Getppid() == 1 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature
|
||||
func prefork(addr string, engine *Engine) (err error) {
|
||||
// 👶 child process 👶
|
||||
if IsChild() {
|
||||
// use 1 cpu core per child process
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
// kill current child proc when master exits
|
||||
go watchMaster()
|
||||
|
||||
// Run child
|
||||
listener, err := reuseport.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
return http.Serve(listener, engine.Handler())
|
||||
}
|
||||
|
||||
// child structure to be used in error returning
|
||||
type child struct {
|
||||
pid int
|
||||
err error
|
||||
}
|
||||
// create variables
|
||||
max := runtime.GOMAXPROCS(0)
|
||||
channel := make(chan child, max)
|
||||
|
||||
// kill child procs when master exits
|
||||
defer func() {
|
||||
for _, proc := range children {
|
||||
_ = proc.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
// launch child procs
|
||||
for i := 0; i < max; i++ {
|
||||
cmd := exec.Command(os.Args[0], os.Args[1:]...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Add gin prefork child flag into child proc env
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal),
|
||||
)
|
||||
|
||||
if err = cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start a child prefork process, error: %v", err)
|
||||
}
|
||||
|
||||
// Store child process ids
|
||||
pid := cmd.Process.Pid
|
||||
children[pid] = cmd
|
||||
|
||||
// notify master if child crashes
|
||||
go func() {
|
||||
channel <- child{pid, cmd.Wait()}
|
||||
}()
|
||||
}
|
||||
|
||||
// return error if child crashes
|
||||
return (<-channel).err
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user