github.com/gofiber/fiber/v2@v2.47.0/prefork.go (about) 1 package fiber 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "log" 8 "os" 9 "os/exec" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync/atomic" 14 "time" 15 16 "github.com/valyala/fasthttp/reuseport" 17 ) 18 19 const ( 20 envPreforkChildKey = "FIBER_PREFORK_CHILD" 21 envPreforkChildVal = "1" 22 ) 23 24 var ( 25 testPreforkMaster = false 26 testOnPrefork = false 27 ) 28 29 // IsChild determines if the current process is a child of Prefork 30 func IsChild() bool { 31 return os.Getenv(envPreforkChildKey) == envPreforkChildVal 32 } 33 34 // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature 35 func (app *App) prefork(network, addr string, tlsConfig *tls.Config) error { 36 // 👶 child process 👶 37 if IsChild() { 38 // use 1 cpu core per child process 39 runtime.GOMAXPROCS(1) 40 // Linux will use SO_REUSEPORT and Windows falls back to SO_REUSEADDR 41 // Only tcp4 or tcp6 is supported when preforking, both are not supported 42 ln, err := reuseport.Listen(network, addr) 43 if err != nil { 44 if !app.config.DisableStartupMessage { 45 const sleepDuration = 100 * time.Millisecond 46 time.Sleep(sleepDuration) // avoid colliding with startup message 47 } 48 return fmt.Errorf("prefork: %w", err) 49 } 50 // wrap a tls config around the listener if provided 51 if tlsConfig != nil { 52 ln = tls.NewListener(ln, tlsConfig) 53 } 54 55 // kill current child proc when master exits 56 go watchMaster() 57 58 // prepare the server for the start 59 app.startupProcess() 60 61 // listen for incoming connections 62 return app.server.Serve(ln) 63 } 64 65 // 👮 master process 👮 66 type child struct { 67 pid int 68 err error 69 } 70 // create variables 71 max := runtime.GOMAXPROCS(0) 72 childs := make(map[int]*exec.Cmd) 73 channel := make(chan child, max) 74 75 // kill child procs when master exits 76 defer func() { 77 for _, proc := range childs { 78 if err := proc.Process.Kill(); err != nil { 79 if !errors.Is(err, os.ErrProcessDone) { 80 log.Printf("prefork: failed to kill child: %v\n", err) 81 } 82 } 83 } 84 }() 85 86 // collect child pids 87 var pids []string 88 89 // launch child procs 90 for i := 0; i < max; i++ { 91 cmd := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec // It's fine to launch the same process again 92 if testPreforkMaster { 93 // When test prefork master, 94 // just start the child process with a dummy cmd, 95 // which will exit soon 96 cmd = dummyCmd() 97 } 98 cmd.Stdout = os.Stdout 99 cmd.Stderr = os.Stderr 100 101 // add fiber prefork child flag into child proc env 102 cmd.Env = append(os.Environ(), 103 fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal), 104 ) 105 if err := cmd.Start(); err != nil { 106 return fmt.Errorf("failed to start a child prefork process, error: %w", err) 107 } 108 109 // store child process 110 pid := cmd.Process.Pid 111 childs[pid] = cmd 112 pids = append(pids, strconv.Itoa(pid)) 113 114 // execute fork hook 115 if app.hooks != nil { 116 if testOnPrefork { 117 app.hooks.executeOnForkHooks(dummyPid) 118 } else { 119 app.hooks.executeOnForkHooks(pid) 120 } 121 } 122 123 // notify master if child crashes 124 go func() { 125 channel <- child{pid, cmd.Wait()} 126 }() 127 } 128 129 // Run onListen hooks 130 // Hooks have to be run here as different as non-prefork mode due to they should run as child or master 131 app.runOnListenHooks() 132 133 // Print startup message 134 if !app.config.DisableStartupMessage { 135 app.startupMessage(addr, tlsConfig != nil, ","+strings.Join(pids, ",")) 136 } 137 138 // return error if child crashes 139 return (<-channel).err 140 } 141 142 // watchMaster watches child procs 143 func watchMaster() { 144 if runtime.GOOS == "windows" { 145 // finds parent process, 146 // and waits for it to exit 147 p, err := os.FindProcess(os.Getppid()) 148 if err == nil { 149 _, _ = p.Wait() //nolint:errcheck // It is fine to ignore the error here 150 } 151 os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork 152 } 153 // if it is equal to 1 (init process ID), 154 // it indicates that the master process has exited 155 const watchInterval = 500 * time.Millisecond 156 for range time.NewTicker(watchInterval).C { 157 if os.Getppid() == 1 { 158 os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork 159 } 160 } 161 } 162 163 var ( 164 dummyPid = 1 165 dummyChildCmd atomic.Value 166 ) 167 168 // dummyCmd is for internal prefork testing 169 func dummyCmd() *exec.Cmd { 170 command := "go" 171 if storeCommand := dummyChildCmd.Load(); storeCommand != nil && storeCommand != "" { 172 command = storeCommand.(string) //nolint:forcetypeassert,errcheck // We always store a string in here 173 } 174 if runtime.GOOS == "windows" { 175 return exec.Command("cmd", "/C", command, "version") 176 } 177 return exec.Command(command, "version") 178 }