package fiber

import (
	
	
	
	
	
	
	
	
	
	

	

	
)

const (
	envPreforkChildKey = "FIBER_PREFORK_CHILD"
	envPreforkChildVal = "1"
)

var (
	testPreforkMaster = false
	testOnPrefork     = false
)

// IsChild determines if the current process is a child of Prefork
func () bool {
	return os.Getenv(envPreforkChildKey) == envPreforkChildVal
}

// prefork manages child processes to make use of the OS REUSEPORT or REUSEADDR feature
func ( *App) (,  string,  *tls.Config) error {
	// 👶 child process 👶
	if IsChild() {
		// use 1 cpu core per child process
		runtime.GOMAXPROCS(1)
		// Linux will use SO_REUSEPORT and Windows falls back to SO_REUSEADDR
		// Only tcp4 or tcp6 is supported when preforking, both are not supported
		,  := reuseport.Listen(, )
		if  != nil {
			if !.config.DisableStartupMessage {
				const  = 100 * time.Millisecond
				time.Sleep() // avoid colliding with startup message
			}
			return fmt.Errorf("prefork: %w", )
		}
		// wrap a tls config around the listener if provided
		if  != nil {
			 = tls.NewListener(, )
		}

		// kill current child proc when master exits
		go watchMaster()

		// prepare the server for the start
		.startupProcess()

		// listen for incoming connections
		return .server.Serve()
	}

	// 👮 master process 👮
	type  struct {
		 int
		 error
	}
	// create variables
	 := runtime.GOMAXPROCS(0)
	 := make(map[int]*exec.Cmd)
	 := make(chan , )

	// kill child procs when master exits
	defer func() {
		for ,  := range  {
			if  := .Process.Kill();  != nil {
				if !errors.Is(, os.ErrProcessDone) {
					log.Errorf("prefork: failed to kill child: %v", )
				}
			}
		}
	}()

	// collect child pids
	var  []string

	// launch child procs
	for  := 0;  < ; ++ {
		 := exec.Command(os.Args[0], os.Args[1:]...) //nolint:gosec // It's fine to launch the same process again
		if testPreforkMaster {
			// When test prefork master,
			// just start the child process with a dummy cmd,
			// which will exit soon
			 = dummyCmd()
		}
		.Stdout = os.Stdout
		.Stderr = os.Stderr

		// add fiber prefork child flag into child proc env
		.Env = append(os.Environ(),
			fmt.Sprintf("%s=%s", envPreforkChildKey, envPreforkChildVal),
		)
		if  := .Start();  != nil {
			return fmt.Errorf("failed to start a child prefork process, error: %w", )
		}

		// store child process
		 := .Process.Pid
		[] = 
		 = append(, strconv.Itoa())

		// execute fork hook
		if .hooks != nil {
			if testOnPrefork {
				.hooks.executeOnForkHooks(dummyPid)
			} else {
				.hooks.executeOnForkHooks()
			}
		}

		// notify master if child crashes
		go func() {
			 <- {, .Wait()}
		}()
	}

	// Run onListen hooks
	// Hooks have to be run here as different as non-prefork mode due to they should run as child or master
	.runOnListenHooks(.prepareListenData(,  != nil))

	// Print startup message
	if !.config.DisableStartupMessage {
		.startupMessage(,  != nil, ","+strings.Join(, ","))
	}

	// return error if child crashes
	return (<-).
}

// watchMaster watches child procs
func watchMaster() {
	if runtime.GOOS == "windows" {
		// finds parent process,
		// and waits for it to exit
		,  := os.FindProcess(os.Getppid())
		if  == nil {
			_, _ = .Wait() //nolint:errcheck // It is fine to ignore the error here
		}
		os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork
	}
	// if it is equal to 1 (init process ID),
	// it indicates that the master process has exited
	const  = 500 * time.Millisecond
	for range time.NewTicker().C {
		if os.Getppid() == 1 {
			os.Exit(1) //nolint:revive // Calling os.Exit is fine here in the prefork
		}
	}
}

var (
	dummyPid      = 1
	dummyChildCmd atomic.Value
)

// dummyCmd is for internal prefork testing
func dummyCmd() *exec.Cmd {
	 := "go"
	if  := dummyChildCmd.Load();  != nil &&  != "" {
		 = .(string) //nolint:forcetypeassert,errcheck // We always store a string in here
	}
	if runtime.GOOS == "windows" {
		return exec.Command("cmd", "/C", , "version")
	}
	return exec.Command(, "version")
}