prefork.go 3.5 KB

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