prefork.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package fiber
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "net"
  6. "os"
  7. "os/exec"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "github.com/valyala/fasthttp/reuseport"
  13. )
  14. const (
  15. envPreforkChildKey = "FIBER_PREFORK_CHILD"
  16. envPreforkChildVal = "1"
  17. )
  18. var testPreforkMaster = false
  19. var testOnPrefork = false
  20. // IsChild determines if the current process is a child of Prefork
  21. func IsChild() bool {
  22. return os.Getenv(envPreforkChildKey) == envPreforkChildVal
  23. }
  24. // prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature
  25. func (app *App) prefork(network, addr string, tlsConfig *tls.Config) (err error) {
  26. // 👶 child process 👶
  27. if IsChild() {
  28. // use 1 cpu core per child process
  29. runtime.GOMAXPROCS(1)
  30. var ln net.Listener
  31. // Linux will use SO_REUSEPORT and Windows falls back to SO_REUSEADDR
  32. // Only tcp4 or tcp6 is supported when preforking, both are not supported
  33. if ln, err = reuseport.Listen(network, addr); err != nil {
  34. if !app.config.DisableStartupMessage {
  35. time.Sleep(100 * time.Millisecond) // avoid colliding with startup message
  36. }
  37. return fmt.Errorf("prefork: %v", err)
  38. }
  39. // wrap a tls config around the listener if provided
  40. if tlsConfig != nil {
  41. ln = tls.NewListener(ln, tlsConfig)
  42. }
  43. // kill current child proc when master exits
  44. go watchMaster()
  45. // prepare the server for the start
  46. app.startupProcess()
  47. // listen for incoming connections
  48. return app.server.Serve(ln)
  49. }
  50. // 👮 master process 👮
  51. type child struct {
  52. pid int
  53. err error
  54. }
  55. // create variables
  56. max := runtime.GOMAXPROCS(0)
  57. childs := make(map[int]*exec.Cmd)
  58. channel := make(chan child, max)
  59. // kill child procs when master exits
  60. defer func() {
  61. for _, proc := range childs {
  62. _ = proc.Process.Kill()
  63. }
  64. }()
  65. // collect child pids
  66. var pids []string
  67. // launch child procs
  68. for i := 0; i < max; i++ {
  69. /* #nosec G204 */
  70. cmd := exec.Command(os.Args[0], os.Args[1:]...) // #nosec G204
  71. if testPreforkMaster {
  72. // When test prefork master,
  73. // just start the child process with a dummy cmd,
  74. // which will exit soon
  75. cmd = dummyCmd()
  76. }
  77. cmd.Stdout = os.Stdout
  78. cmd.Stderr = os.Stderr
  79. // add fiber prefork child flag into child proc env
  80. cmd.Env = append(os.Environ(),
  81. fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal),
  82. )
  83. if err = cmd.Start(); err != nil {
  84. return fmt.Errorf("failed to start a child prefork process, error: %v", err)
  85. }
  86. // store child process
  87. pid := cmd.Process.Pid
  88. childs[pid] = cmd
  89. pids = append(pids, strconv.Itoa(pid))
  90. // execute fork hook
  91. if app.hooks != nil {
  92. if testOnPrefork {
  93. app.hooks.executeOnForkHooks(dummyPid)
  94. } else {
  95. app.hooks.executeOnForkHooks(pid)
  96. }
  97. }
  98. // notify master if child crashes
  99. go func() {
  100. channel <- child{pid, cmd.Wait()}
  101. }()
  102. }
  103. // Print startup message
  104. if !app.config.DisableStartupMessage {
  105. app.startupMessage(addr, tlsConfig != nil, ","+strings.Join(pids, ","))
  106. }
  107. // return error if child crashes
  108. return (<-channel).err
  109. }
  110. // watchMaster watches child procs
  111. func watchMaster() {
  112. if runtime.GOOS == "windows" {
  113. // finds parent process,
  114. // and waits for it to exit
  115. p, err := os.FindProcess(os.Getppid())
  116. if err == nil {
  117. _, _ = p.Wait()
  118. }
  119. os.Exit(1)
  120. }
  121. // if it is equal to 1 (init process ID),
  122. // it indicates that the master process has exited
  123. for range time.NewTicker(time.Millisecond * 500).C {
  124. if os.Getppid() == 1 {
  125. os.Exit(1)
  126. }
  127. }
  128. }
  129. var dummyChildCmd = "go"
  130. // dummyCmd is for internal prefork testing
  131. func dummyCmd() *exec.Cmd {
  132. if runtime.GOOS == "windows" {
  133. return exec.Command("cmd", "/C", dummyChildCmd, "version")
  134. }
  135. return exec.Command(dummyChildCmd, "version")
  136. }
  137. var dummyPid = 1