fuse test: keep derived filer gRPC port below the ephemeral floor (#10334)

The FUSE harness picks the filer HTTP port and lets "weed mount" derive the
filer gRPC port as HTTP+10000. freePort kept the HTTP port under the Linux
ephemeral floor (32768) but not the derived gRPC port, which ranged up to
42000. When the gRPC port landed above the floor an outbound connection could
transiently hold it, so mini relocated its filer gRPC port while mount kept
dialing HTTP+10000, timing the mount out.

Cap the HTTP port at 22000 so the gRPC sibling stays at or below 32000, and
verify both ports are bindable before returning.
This commit is contained in:
Chris Lu
2026-07-14 12:46:54 -07:00
committed by GitHub
parent f3e4a73696
commit 68e6fc5f7c
+25 -13
View File
@@ -86,35 +86,47 @@ func NewFuseTestFramework(t *testing.T, config *TestConfig) *FuseTestFramework {
}
}
// freePort asks the OS for a free TCP port in a range where the gRPC
// offset (port + 10000) won't collide with well-known ports.
// Stay below the Linux ephemeral floor (32768) so the kernel does not
// reuse the chosen port for an outbound connection between close() here
// and re-bind in the child "weed mini" process.
// grpcPortOffset mirrors weed's HTTP->gRPC port convention (gRPC = HTTP + offset);
// "weed mount" derives the filer gRPC port from the filer HTTP address the same way.
const grpcPortOffset = 10000
// freePort returns a free filer HTTP port whose gRPC sibling (port+grpcPortOffset)
// is also free. Both must stay below the Linux ephemeral floor (32768): a gRPC port
// above it can be transiently grabbed by an outbound connection, forcing mini to
// relocate its filer gRPC port while "weed mount" keeps dialing HTTP+10000 — the
// mount then never connects and the test times out. Capping HTTP at 22000 keeps the
// gRPC port at or below 32000.
func freePort(t *testing.T) int {
t.Helper()
const (
minServicePort = 20000
maxServicePort = 32000
maxServicePort = 22000
)
portCount := maxServicePort - minServicePort + 1
start := minServicePort + int(time.Now().UnixNano()%int64(portCount))
for attempt := 0; attempt < 512; attempt++ {
for attempt := 0; attempt < portCount; attempt++ {
port := minServicePort + (start-minServicePort+attempt)%portCount
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
if err != nil {
continue
if portIsFree(port) && portIsFree(port+grpcPortOffset) {
return port
}
l.Close()
return port
}
t.Fatalf("failed to allocate port <= %d after repeated attempts", maxServicePort)
t.Fatalf("failed to allocate a free HTTP/gRPC port pair in [%d,%d]", minServicePort, maxServicePort)
return 0
}
// portIsFree reports whether a TCP port can currently be bound on 127.0.0.1.
func portIsFree(port int) bool {
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
if err != nil {
return false
}
l.Close()
return true
}
// Setup starts "weed mini" and mounts the FUSE filesystem.
func (f *FuseTestFramework) Setup(config *TestConfig) error {
if f.isSetup {