elastic/elasticsearch · error · IllegalArgumentException

can't compare DeadHostStates holding different time supplier

Error message

can't compare DeadHostStates holding different time suppliers as they may be based on different clocks

What it means

DeadHostState implements Comparable and its compareTo compares deadUntilNanos values. Because the dead-until timestamp is computed relative to a captured timeSupplier, comparing two states built with different suppliers would mix clocks and yield a meaningless ordering; compareTo therefore requires both states to share the identical timeSupplier instance.

Source

Thrown at client/rest/src/main/java/org/elasticsearch/client/DeadHostState.java:94

        return timeSupplier.get() - deadUntilNanos > 0;
    }

    /**
     * Returns the timestamp (nanos) till the host is supposed to stay dead without being retried.
     * After that the host should be retried.
     */
    long getDeadUntilNanos() {
        return deadUntilNanos;
    }

    int getFailedAttempts() {
        return failedAttempts;
    }

    @Override
    public int compareTo(DeadHostState other) {
        if (timeSupplier != other.timeSupplier) {
            throw new IllegalArgumentException(
                "can't compare DeadHostStates holding different time suppliers as they may be based on different clocks"
            );
        }
        return Long.compare(deadUntilNanos, other.deadUntilNanos);
    }

    @Override
    public String toString() {
        return "DeadHostState{"
            + "failedAttempts="
            + failedAttempts
            + ", deadUntilNanos="
            + deadUntilNanos
            + ", timeSupplier="
            + timeSupplier
            + '}';
    }
}

View on GitHub (pinned to db6a809a66)

Solutions

  1. Construct all comparable DeadHostState instances with the same timeSupplier instance (use DEFAULT_TIME_SUPPLIER for production, one shared mock for tests).
  2. Avoid sorting heterogeneous collections; segregate test states from production states.
  3. If you control the RestClient, expose its timeSupplier so external code can reuse it.

Example fix

// before
DeadHostState a = new DeadHostState(() -> 0L);
DeadHostState b = new DeadHostState(DeadHostState.DEFAULT_TIME_SUPPLIER);
a.compareTo(b); // throws
// after
Supplier<Long> clock = DeadHostState.DEFAULT_TIME_SUPPLIER;
DeadHostState a = new DeadHostState(clock);
DeadHostState b = new DeadHostState(clock);
Defensive patterns

Strategy: validation

Validate before calling

Supplier<Long> sharedClock = DeadHostState.DEFAULT_TIME_SUPPLIER;
DeadHostState a = new DeadHostState(sharedClock);
DeadHostState b = new DeadHostState(sharedClock);
assert a.compareTo(b) >= 0 || b.compareTo(a) >= 0; // safe: same supplier

Prevention

When it happens

Trigger: Sorting or inserting DeadHostState objects into a collection where some were created via new DeadHostState(timeSupplier) with a custom/test supplier and others via the default System::nanoTime supplier; mixing a real RestClient's internal dead list with test-fabricated states.

Common situations: Unit tests construct DeadHostState with a mock/fixed clock and then compare against a state the RestClient created internally with DEFAULT_TIME_SUPPLIER; refactoring that creates fresh suppliers per call instead of sharing one.

Related errors


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