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
- Construct all comparable DeadHostState instances with the same timeSupplier instance (use DEFAULT_TIME_SUPPLIER for production, one shared mock for tests).
- Avoid sorting heterogeneous collections; segregate test states from production states.
- 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
- Share one timeSupplier instance across all DeadHostState objects that may be compared.
- In tests, inject the same mock clock used by the RestClient; don't mix with DEFAULT_TIME_SUPPLIER.
- Treat DeadHostState as comparable only within a single RestClient's dead-host pool.
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
- bufferLimit must be greater than 0
- host cannot be null
- url parameter [{}] has already been set to [{}]
- cloudId {} must begin with a human readable identifier follo
- cloudId {} did not decode to a cluster identifier correctly
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/b72e73b4a56b578d.
Report an issue: GitHub.