feat: added prefork feature into gin

This commit is contained in:
Mahmood Heidari 2022-09-16 15:59:35 +04:30
parent 2c9e5fe47a
commit aeec874c09
No known key found for this signature in database
GPG Key ID: 063511BA9D839BD9
4 changed files with 175 additions and 0 deletions

37
gin.go
View File

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"runtime"
"strings" "strings"
"sync" "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 enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
ContextWithFallback bool ContextWithFallback bool
// Determines if master should create child processes or not
doPrefork bool
delims render.Delims delims render.Delims
secureJSONPrefix string secureJSONPrefix string
HTMLRender render.HTMLRender HTMLRender render.HTMLRender
@ -196,6 +200,7 @@ func New() *Engine {
UnescapePathValues: true, UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory, MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9), trees: make(methodTrees, 0, 9),
doPrefork: false,
delims: render.Delims{Left: "{{", Right: "}}"}, delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);", secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"}, trustedProxies: []string{"0.0.0.0/0", "::/0"},
@ -379,6 +384,12 @@ func (engine *Engine) Run(addr ...string) (err error) {
address := resolveAddress(addr) address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address) debugPrint("Listening and serving HTTP on %s\n", address)
if engine.doPrefork || IsChild() {
err = engine.prefork(address)
return
}
err = http.ListenAndServe(address, engine.Handler()) err = http.ListenAndServe(address, engine.Handler())
return return
} }
@ -645,6 +656,32 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
serveError(c, http.StatusNotFound, default404Body) 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} var mimePlain = []string{MIMEPlain}
func serveError(c *Context, code int, defaultMessage []byte) { func serveError(c *Context, code int, defaultMessage []byte) {

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/go-playground/validator/v10 v10.10.0 github.com/go-playground/validator/v10 v10.10.0
github.com/goccy/go-json v0.9.11 github.com/goccy/go-json v0.9.11
github.com/json-iterator/go v1.1.12 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/mattn/go-isatty v0.0.16
github.com/pelletier/go-toml/v2 v2.0.2 github.com/pelletier/go-toml/v2 v2.0.2
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0

2
go.sum
View File

@ -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/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 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=

135
prefork.go Normal file
View 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
}