s3: keep the gateway up when an auxiliary listener fails to bind (#11768)

* s3: keep the gateway up when the Iceberg or Lance listener fails to bind

The optional catalog servers ran glog.Fatalf on a bind error, so an
occupied port (e.g. the default Lance port 9101 taken by another process)
killed the entire S3 server. Log the error and run without the auxiliary
listener instead; the S3 API itself still fails hard on bind errors.

* mini: only probe and advertise catalogs whose listeners bound

With aux listener failures no longer fatal, the configured catalog port may
answer as an unrelated process; mini's readiness probe accepts any HTTP
response and would announce that foreign service as the catalog. Record the
bind outcome on S3Options (final by the time the S3 port serves, since the
aux binds happen first) and gate mini's readiness wait and admin UI
advertisement on it.

* s3: test that a failed aux listener bind is non-fatal

Hold the Iceberg and Lance ports open and assert both starters return no
server and leave the bound flags false, covering the new nonfatal path.

* command: range the endpoint test table by index

S3Options now holds atomic.Bool flags, so copying a test case trips
go vet's lock-copy check.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris LuandDevin authored and GitHub committed 2026-10-16 23:10:09 -07:00
1 parent cabaf9c38d
commit 5a0be275cc
4 files changed
+107 -11

No files matched your search

+21 -4
View File
@@ -1627,13 +1627,30 @@ func startMiniServices(miniWhiteList []string, allServicesReady chan struct{}) {
if miniProgressBoard != nil {
miniProgressBoard.starting("Iceberg")
}
waitForServiceReady("Iceberg", *miniS3Options.portIceberg, bindIp)
// Probe only when the catalog listener actually bound; otherwise the
// port may answer as an unrelated process and get advertised as the
// catalog.
if miniS3Options.icebergBound.Load() {
waitForServiceReady("Iceberg", *miniS3Options.portIceberg, bindIp)
} else {
if miniProgressBoard != nil {
miniProgressBoard.failed("Iceberg")
}
glog.Warningf("Iceberg catalog on port %d did not start; see S3 startup log", *miniS3Options.portIceberg)
}
}
if miniS3Options.portLance != nil && *miniS3Options.portLance > 0 {
if miniProgressBoard != nil {
miniProgressBoard.starting("Lance")
}
waitForServiceReady("Lance", *miniS3Options.portLance, bindIp)
if miniS3Options.lanceBound.Load() {
waitForServiceReady("Lance", *miniS3Options.portLance, bindIp)
} else {
if miniProgressBoard != nil {
miniProgressBoard.failed("Lance")
}
glog.Warningf("Lance namespace server on port %d did not start; see S3 startup log", *miniS3Options.portLance)
}
}
}
if *miniEnableWebDAV {
@@ -1848,10 +1865,10 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
// the admin UI would otherwise print an endpoint nothing is listening on.
var icebergPort, lancePort int
if miniEnableS3 != nil && *miniEnableS3 {
if miniS3Options.portIceberg != nil {
if miniS3Options.portIceberg != nil && *miniS3Options.portIceberg > 0 && miniS3Options.icebergBound.Load() {
icebergPort = *miniS3Options.portIceberg
}
if miniS3Options.portLance != nil {
if miniS3Options.portLance != nil && *miniS3Options.portLance > 0 && miniS3Options.lanceBound.Load() {
lancePort = *miniS3Options.portLance
}
}