{"record":{"id":"b72e73b4a56b578d","repo":"elastic/elasticsearch","slug":"can-t-compare-deadhoststates-holding-different-tim","errorCode":null,"errorMessage":"can't compare DeadHostStates holding different time suppliers as they may be based on different clocks","messagePattern":"can't compare DeadHostStates holding different time suppliers as they may be based on different clocks","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"client/rest/src/main/java/org/elasticsearch/client/DeadHostState.java","lineNumber":94,"sourceCode":"        return timeSupplier.get() - deadUntilNanos > 0;\n    }\n\n    /**\n     * Returns the timestamp (nanos) till the host is supposed to stay dead without being retried.\n     * After that the host should be retried.\n     */\n    long getDeadUntilNanos() {\n        return deadUntilNanos;\n    }\n\n    int getFailedAttempts() {\n        return failedAttempts;\n    }\n\n    @Override\n    public int compareTo(DeadHostState other) {\n        if (timeSupplier != other.timeSupplier) {\n            throw new IllegalArgumentException(\n                \"can't compare DeadHostStates holding different time suppliers as they may be based on different clocks\"\n            );\n        }\n        return Long.compare(deadUntilNanos, other.deadUntilNanos);\n    }\n\n    @Override\n    public String toString() {\n        return \"DeadHostState{\"\n            + \"failedAttempts=\"\n            + failedAttempts\n            + \", deadUntilNanos=\"\n            + deadUntilNanos\n            + \", timeSupplier=\"\n            + timeSupplier\n            + '}';\n    }\n}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/client/rest/src/main/java/org/elasticsearch/client/DeadHostState.java#L76-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nDeadHostState a = new DeadHostState(() -> 0L);\nDeadHostState b = new DeadHostState(DeadHostState.DEFAULT_TIME_SUPPLIER);\na.compareTo(b); // throws\n// after\nSupplier<Long> clock = DeadHostState.DEFAULT_TIME_SUPPLIER;\nDeadHostState a = new DeadHostState(clock);\nDeadHostState b = new DeadHostState(clock);","handlingStrategy":"validation","validationCode":"Supplier<Long> sharedClock = DeadHostState.DEFAULT_TIME_SUPPLIER;\nDeadHostState a = new DeadHostState(sharedClock);\nDeadHostState b = new DeadHostState(sharedClock);\nassert a.compareTo(b) >= 0 || b.compareTo(a) >= 0; // safe: same supplier","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rest-client","validation","concurrency","comparable"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}