Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 (macOS) Prefork is not working as expected #1208

Open
ReneWerner87 opened this issue Mar 8, 2021 · 17 comments
Open

🐛 (macOS) Prefork is not working as expected #1208

ReneWerner87 opened this issue Mar 8, 2021 · 17 comments

Comments

@ReneWerner87
Copy link
Member

ReneWerner87 commented Mar 8, 2021

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
@welcome
Copy link

welcome bot commented Mar 8, 2021

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

@ReneWerner87
Copy link
Member Author

$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 ReneWerner87 changed the title 🐛 Prefork is not working as expected 🐛 (macOS) Prefork is not working as expected Mar 8, 2021
@guaychou
Copy link

guaychou commented Mar 8, 2021

Thank you for creating my issue

@renanbastos93
Copy link
Member

renanbastos93 commented Mar 8, 2021

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

@kiyonlin
Copy link
Contributor

kiyonlin commented Mar 9, 2021

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

Really appreciate it if you can help!

@renanbastos93 renanbastos93 linked a pull request Mar 10, 2021 that will close this issue
@softexpert
Copy link

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.

@ReneWerner87
Copy link
Member Author

Should not be related

@softexpert
Copy link

This means opening a separate issue, right ?

@ReneWerner87
Copy link
Member Author

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

@kiyonlin
Copy link
Contributor

kiyonlin commented Dec 2, 2021

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

@ReneWerner87
Copy link
Member Author

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

@hitrop
Copy link

hitrop commented Dec 29, 2023

Is there any update on this?

@renanbastos93
Copy link
Member

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

@sixcolors
Copy link
Member

@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
Copy link
Member

sixcolors commented Jan 31, 2024

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
Copy link
Member

sixcolors commented Feb 11, 2024

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?

@ReneWerner87
Copy link
Member Author

Sounds good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants