test: keep mini-allocated ports below the ephemeral floor (#10209)

* test: keep mini-allocated ports below the ephemeral floor

Allocated ports could land in 32768-55000, so a transient outbound dial
during mini startup (volume->master gRPC, etc.) could grab an allocated
port as its source port before the filer bound it, failing with
"bind: address already in use". Cap the range so port+GrpcPortOffset
stays under 32768.

* test: derive mini port cap from the ephemeral floor constant

Name the 32768 floor once and compute miniPortMax as floor-GrpcPortOffset
so the cap tracks the offset; reuse the constant in the regression test.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-07-02 15:11:56 -07:00
1 parent 2540141ee7
commit c5240944e4
2 files changed
+42 -4

No files matched your search

+21 -4
View File
@@ -11,6 +11,23 @@ import (
// GrpcPortOffset is the offset weed mini uses to derive gRPC ports from HTTP ports.
const GrpcPortOffset = 10000
// linuxEphemeralPortFloor is the Linux default ip_local_port_range lower bound.
// Ports at or above it can be claimed as the source port of a transient
// outbound connection during mini's own startup (e.g. volume dialing master
// gRPC) in the window between allocation and the listener bind, surfacing as
// "bind: address already in use".
const linuxEphemeralPortFloor = 32768
// miniPortMin/miniPortMax bound the random HTTP port range for weed mini.
// The cap keeps both the HTTP port and its +GrpcPortOffset gRPC counterpart
// below linuxEphemeralPortFloor, out of the range the kernel draws ephemeral
// source ports from, so the steal above cannot happen. Deriving the cap from
// GrpcPortOffset keeps the two in lockstep if the offset ever changes.
const (
miniPortMin = 10000
miniPortMax = linuxEphemeralPortFloor - GrpcPortOffset
)
// miniDefaultPorts are the weed mini flag defaults (see weed/command/mini.go).
// A test only overrides services it uses; unspecified services still bind
// these defaults, so allocation must avoid handing them out (or any value
@@ -83,8 +100,8 @@ func MustAllocatePorts(t *testing.T, count int) []int {
// wrong port and hang.
func AllocateMiniPorts(count int) ([]int, error) {
const (
minPort = 10000
maxPort = 55000
minPort = miniPortMin
maxPort = miniPortMax
)
reserved := reservedMiniPorts()
ports := make([]int, 0, count)
@@ -155,8 +172,8 @@ func MustFreeMiniPort(t *testing.T, name string) int {
// allocated, so a mini gRPC port cannot be recycled as a regular port mid-batch.
func AllocatePortSet(miniCount, regularCount int) (mini []int, regular []int, err error) {
const (
minPort = 10000
maxPort = 55000
minPort = miniPortMin
maxPort = miniPortMax
)
reserved := reservedMiniPorts()
mini = make([]int, 0, miniCount)
+21
View File
@@ -25,6 +25,27 @@ func TestAllocateMiniPortsAvoidsMiniDefaults(t *testing.T) {
}
}
// AllocateMiniPorts must keep every port it hands out, and that port's gRPC
// counterpart, below 32768 - the Linux default ip_local_port_range floor.
// A real failure: filer was allocated 44204, then lost the bind to a transient
// outbound connection that had grabbed 44204 as an ephemeral source port
// during mini startup ("bind: address already in use").
func TestAllocateMiniPortsBelowEphemeralFloor(t *testing.T) {
const ephemeralFloor = linuxEphemeralPortFloor
for iter := 0; iter < 200; iter++ {
ports, err := AllocateMiniPorts(4)
if err != nil {
t.Fatalf("iter %d: AllocateMiniPorts: %v", iter, err)
}
for _, p := range ports {
if p+GrpcPortOffset >= ephemeralFloor {
t.Fatalf("iter %d: port %d (gRPC %d) reaches the ephemeral range floor %d",
iter, p, p+GrpcPortOffset, ephemeralFloor)
}
}
}
}
func TestAllocatePortSetNoGrpcCollision(t *testing.T) {
// Run a few iterations to catch the OS-recycles-just-closed-port race
// that previously hit regular ports when the mini gRPC offset was freed