mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 13:22:09 +08:00
feat: added prefork feature into gin
This commit is contained in:
parent
2c9e5fe47a
commit
aeec874c09
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.10.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
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.2
|
||||
github.com/stretchr/testify v1.8.0
|
||||
|
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