fiber icon indicating copy to clipboard operation
fiber copied to clipboard

๐Ÿ› (macOS) Prefork is not working as expected

Open ReneWerner87 opened this issue 3 years ago โ€ข 17 comments

Fiber version v2.5.0

OS Systemversion: macOS 11.2.1 (20D74) Kernel-Version: Darwin 20.3.0

Issue description In the prefork concept, one child process is started for each cpu core and the work of the handlers is divided between these processes, so when you output the process number of the child process in the handler, different ids should appear, which is not the case here.

The goal of the ticket is to check where the problem is, because with fasthttp it works.

Code snippet

package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New(fiber.Config{Prefork: true})
	app.Get("/test", func(c *fiber.Ctx) error {
		fmt.Printf("ProcessId: %v, %v, isChild: %v\t", os.Getpid(), os.Getppid(), fiber.IsChild())
		rand.Seed(time.Now().UnixNano())
		n := rand.Intn(10) // n will be between 0 and 10
		//fmt.Printf("Sleeping %d seconds...\n", n)
		time.Sleep(time.Duration(n)*time.Second)

		return c.SendStatus(200)
	})

	log.Fatal(app.Listen(":3000"))
}

Output:

$shell: go run main.go                                                                                                           

 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚                    Fiber v2.5.0                   โ”‚  โ”‚ Child PIDs ... 6635, 6636, 6637, 6638, 6639, 6640 โ”‚
 โ”‚               http://127.0.0.1:3000               โ”‚  โ”‚ 6641, 6642, 6643, 6644, 6645, 6646                โ”‚
 โ”‚                                                   โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
 โ”‚ Handlers ............. 2  Processes .......... 12 โ”‚
 โ”‚ Prefork ........ Enabled  PID .............. 6634 โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true
ProcessId: 6635, 6634, isChild: true

Working Fasthttp example:

package main

import (
	"fmt"
	"math/rand"
	"os"
	"time"

	"github.com/valyala/fasthttp"
	"github.com/valyala/fasthttp/prefork"
)

func main() {
	server := &fasthttp.Server{
		Handler: fastHTTPHandler,
	}
	// Wraps the server with prefork
	preforkServer := prefork.New(server)
	if err := preforkServer.ListenAndServe(":3000"); err != nil {
		panic(err)
	}

}

// request handler in fasthttp style, i.e. just plain function.
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
	fmt.Printf("ProcessId: %v, %v, isChild: %v\t", os.Getpid(), os.Getppid(), prefork.IsChild())
	rand.Seed(time.Now().UnixNano())
	n := rand.Intn(10) // n will be between 0 and 10
	//fmt.Printf("Sleeping %d seconds...\n", n)
	time.Sleep(time.Duration(n)*time.Second)
}

Output:

$shell: go run main.go

ProcessId: 6733, 6728, isChild: true
ProcessId: 6734, 6728, isChild: true
ProcessId: 6729, 6728, isChild: true
ProcessId: 6734, 6728, isChild: true
ProcessId: 6732, 6728, isChild: true
ProcessId: 6731, 6728, isChild: true
ProcessId: 6736, 6728, isChild: true
ProcessId: 6735, 6728, isChild: true
ProcessId: 6730, 6728, isChild: true

ReneWerner87 avatar Mar 08 '21 07:03 ReneWerner87

Thanks for opening your first issue here! ๐ŸŽ‰ Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

welcome[bot] avatar Mar 08 '21 07:03 welcome[bot]

$shell: docker run --rm -p 3000:3000 -it -v ${PWD}:/usr/app/src -w /usr/app/src golang:alpine sh image

The same code base works in the linux container, i.e. it should be a problem specific to the operating system.

ReneWerner87 avatar Mar 08 '21 07:03 ReneWerner87

Thank you for creating my issue

guaychou avatar Mar 08 '21 08:03 guaychou

@ReneWerner87 I have macOS, do you need help? Can I investigate too?

renanbastos93 avatar Mar 08 '21 22:03 renanbastos93

@ReneWerner87 I have macOS, do you need help? Can I investigate too?

Really appreciate it if you can help!

kiyonlin avatar Mar 09 '21 00:03 kiyonlin

I don't know if it is related, but on my AWS Linux server I get panic: listen tcp :6443: bind: address already in use when Prefork is set to true. If Prefork is set to false, it runs perfectly fine. I built the app with Fiber 2.7.1 . The exception is triggered on the line ln, err := tls.Listen("tcp", fmt.Sprintf(":%v", models.SKFRONTWS_Port), tlsCfg) The app is run with sudo for testing.

Interestingly enough, same binary will accept to run with Prefork set to true on my Linux laptop.

softexpert avatar Mar 31 '21 14:03 softexpert

Should not be related

ReneWerner87 avatar Mar 31 '21 14:03 ReneWerner87

This means opening a separate issue, right ?

softexpert avatar Mar 31 '21 14:03 softexpert

Depends on whether you take the adapter package, then you make there an issue on

The error message says in any case, that somehow the port is already occupied, you must look at your implemtation, maybe this is not possible in the aws

ReneWerner87 avatar Mar 31 '21 17:03 ReneWerner87

preforkServer.Reuseport = true makes fasthttp demo get the same issue.

kiyonlin avatar Dec 02 '21 14:12 kiyonlin

pls check https://github.com/kavu/go_reuseport

ReneWerner87 avatar May 01 '22 09:05 ReneWerner87

Is there any update on this?

hitrop avatar Dec 29 '23 17:12 hitrop

Sorry everyone, I never looked at this issue again. Are there any updates?

renanbastos93 avatar Dec 29 '23 19:12 renanbastos93

@ReneWerner87 this still seems to be an issue, I'll have a look this week and see if I can fix:

~/prefork ยป go run main.go                      sixcolors@Jason-McNeils-Mac-Pro

 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚                   Fiber v2.52.0                   โ”‚  โ”‚ Child PIDs ... 80628, 80629, 80630, 80631, 80632  โ”‚
 โ”‚               http://127.0.0.1:3000               โ”‚  โ”‚ 80633, 80634, 80635, 80636, 80637, 80638, 80639   โ”‚
 โ”‚       (bound on host 0.0.0.0 and port 3000)       โ”‚  โ”‚ 80640, 80641, 80642, 80643, 80644, 80645, 80646   โ”‚
 โ”‚                                                   โ”‚  โ”‚ 80647, 80648, 80649, 80650, 80651                 โ”‚
 โ”‚ Handlers ............. 2  Processes .......... 24 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
 โ”‚ Prefork ........ Enabled  PID ............. 80627 โ”‚ 
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 

ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true	ProcessId: 80628, 80627, isChild: true

sixcolors avatar Jan 31 '24 00:01 sixcolors

Preliminary investigation: Fasthttp prefork.go Reuseport = false by default (except on windows, where it must be true). If Reuseport = true we get the same results in fasthttp example from description:

~/prefork ยป go run fast.go                      sixcolors@Jason-McNeils-Mac-Pro
ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true	ProcessId: 81649, 81648, isChild: true

This is due to the differences in how macOS and Linux handle the SO_REUSEPORT socket option. On Linux, the kernel will evenly distribute incoming connections among all the processes listening on the same port. However, on macOS, the kernel will not distribute the connections, which results in one process (in this case, the first child process) handling all the connections. See https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ

Fasthttp uses a file Listener when Reuseport = false, so that's why it's working.

sixcolors avatar Jan 31 '24 01:01 sixcolors

I am unsure about using a file listener-based approach on macOS. I thought a lightweight load balancer that uses ports greater than 1024 might be a better option. What do you think?

sixcolors avatar Feb 11 '24 19:02 sixcolors

Sounds good

ReneWerner87 avatar Feb 11 '24 20:02 ReneWerner87