Maintainer implements Runnable, AutoCloseable { static Duration staggeredDelay(Duration interval, Instant now, String hostname, List<String> clusterHostnames) { Objects.requireNonNull(clusterHostnames); if ( ! clusterHostnames.contains(hostname)) return interval; long offset = clusterHostnames.indexOf(hostname) * interval.toMillis() / clusterHostnames.size(); return Duration.ofMillis(Math.floorMod(offset - now.toEpochMilli(), interval.toMillis())); } Maintainer(String name, Duration interval, Instant startedAt, JobControl jobControl, JobMetrics jobMetrics, List<String> clusterHostnames);  Maintainer(String name, Duration interval, Duration initialDelay, JobControl jobControl, JobMetrics jobMetrics); @Override void run(); @Override void close(); @Override final String toString(); @SuppressWarnings("unused") final void lockAndMaintain(); final String name();  }
@Test public void staggering() { List<String> cluster = List.of("cfg1", "cfg2", "cfg3"); Duration interval = Duration.ofMillis(300); Instant now = Instant.ofEpochMilli(1000); assertEquals(200, Maintainer.staggeredDelay(interval, now, "cfg1", cluster).toMillis()); assertEquals(0, Maintainer.staggeredDelay(interval, now, "cfg2", cluster).toMillis()); assertEquals(100, Maintainer.staggeredDelay(interval, now, "cfg3", cluster).toMillis()); now = Instant.ofEpochMilli(1001); assertEquals(199, Maintainer.staggeredDelay(interval, now, "cfg1", cluster).toMillis()); assertEquals(299, Maintainer.staggeredDelay(interval, now, "cfg2", cluster).toMillis()); assertEquals(99, Maintainer.staggeredDelay(interval, now, "cfg3", cluster).toMillis()); now = Instant.ofEpochMilli(1101); assertEquals(99, Maintainer.staggeredDelay(interval, now, "cfg1", cluster).toMillis()); assertEquals(199, Maintainer.staggeredDelay(interval, now, "cfg2", cluster).toMillis()); assertEquals(299, Maintainer.staggeredDelay(interval, now, "cfg3", cluster).toMillis()); assertEquals(300, Maintainer.staggeredDelay(interval, now, "cfg0", cluster).toMillis()); }