elastic/elasticsearch · error · IllegalStateException

Available port ranges have exceeded Range from {} to {}

Error message

Available port ranges have exceeded Range from {} to {}

What it means

AvailablePortAllocator hands out fixed-size (DEFAULT_RANGE_SIZE=10) port ranges starting at MIN_PRIVATE_PORT=10300. getNextPortRange computes startPort = 10300 + rangeNumber*10 and rejects any range whose endPort exceeds MAX_PRIVATE_PORT=13300. With 10-port ranges this caps the allocator at 300 concurrent reservations; exceeding it is treated as a leak/exhaustion defect, not a recoverable condition.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/util/ports/AvailablePortAllocator.java:30

import org.gradle.internal.Pair;

import java.util.ArrayList;
import java.util.List;

public class AvailablePortAllocator {
    public static final int MIN_PRIVATE_PORT = 10300;
    public static final int MAX_PRIVATE_PORT = 13300;
    public static final int DEFAULT_RANGE_SIZE = 10;

    private final List<ReservedPortRange> reservations = new ArrayList<ReservedPortRange>();

    private ReservedPortRangeFactory portRangeFactory = new DefaultReservedPortRangeFactory();

    protected Pair<Integer, Integer> getNextPortRange(int rangeNumber) {
        int startPort = MIN_PRIVATE_PORT + (rangeNumber * DEFAULT_RANGE_SIZE);
        int endPort = startPort + DEFAULT_RANGE_SIZE - 1;
        if (endPort > MAX_PRIVATE_PORT) {
            throw new IllegalStateException(
                "Available port ranges have exceeded Range from " + MIN_PRIVATE_PORT + " to " + MAX_PRIVATE_PORT
            );
        }
        return Pair.of(startPort, endPort);
    }

    public ReservedPortRange reservePortRange() {
        Pair<Integer, Integer> portRange = getNextPortRange(reservations.size());
        ReservedPortRange range = portRangeFactory.getReservedPortRange(portRange.getLeft(), portRange.getRight());
        reservations.add(range);
        return range;
    }
}

View on GitHub (pinned to db6a809a66)

Solutions

  1. Ensure every reservePortRange() has a matching release/close so reservations are reaped.
  2. Reduce the number of concurrently running clusters in the test matrix, or stagger them so ranges are reused.
  3. If the cap is genuinely too low for legitimate use, raise MAX_PRIVATE_PORT (and MIN if needed) in AvailablePortAllocator — but first confirm the leak is not the real bug.

Example fix

// before: ranges never released; reservations.size() climbs past 300
ReservedPortRange r = allocator.reservePortRange();
// ... used but never closed
// after: try-with-resources or explicit release
try (ReservedPortRange r = allocator.reservePortRange()) {
    // use r
}
Defensive patterns

Strategy: validation

Validate before calling

if (allocator.reservationCount() >= (AvailablePortAllocator.MAX_PRIVATE_PORT - AvailablePortAllocator.MIN_PRIVATE_PORT) / AvailablePortAllocator.DEFAULT_RANGE_SIZE) {
    // drain or refuse new reservations before calling reservePortRange()
}

Prevention

When it happens

Trigger: reservePortRange() is called more than 300 times without releasing ranges (reservations list grows unboundedly), so getNextPortRange(reservations.size()) returns an endPort > 13300. Typical in test-heavy Gradle runs spawning many clusters.

Common situations: A Gradle test orchestration layer leaks ReservedPortRange objects (never releases them); a very large parallel test matrix allocates more than 300 concurrent ranges; long-lived daemon accumulating reservations across builds.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/7cbf15d6bdcd9d4d. Report an issue: GitHub.