-
Notifications
You must be signed in to change notification settings - Fork 25.8k
REST client: hosts marked dead for the first time should not be immediately retried #29230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,19 +26,23 @@ | |
| * when the host should be retried (based on number of previous failed attempts). | ||
| * Class is immutable, a new copy of it should be created each time the state has to be changed. | ||
| */ | ||
| final class DeadHostState { | ||
| final class DeadHostState implements Comparable<DeadHostState> { | ||
|
|
||
| private static final long MIN_CONNECTION_TIMEOUT_NANOS = TimeUnit.MINUTES.toNanos(1); | ||
| private static final long MAX_CONNECTION_TIMEOUT_NANOS = TimeUnit.MINUTES.toNanos(30); | ||
|
|
||
| static final DeadHostState INITIAL_DEAD_STATE = new DeadHostState(); | ||
|
|
||
| private final int failedAttempts; | ||
| private final long deadUntilNanos; | ||
| private final TimeSupplier timeSupplier; | ||
|
|
||
| DeadHostState() { | ||
|
||
| this(TimeSupplier.DEFAULT); | ||
| } | ||
|
|
||
| private DeadHostState() { | ||
| DeadHostState(TimeSupplier timeSupplier) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is worth javadocs on this to communicate that it is for building the initial dead state. |
||
| this.failedAttempts = 1; | ||
| this.deadUntilNanos = System.nanoTime() + MIN_CONNECTION_TIMEOUT_NANOS; | ||
| this.deadUntilNanos = timeSupplier.getNanoTime() + MIN_CONNECTION_TIMEOUT_NANOS; | ||
| this.timeSupplier = timeSupplier; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -47,10 +51,19 @@ private DeadHostState() { | |
| * that failed many consecutive times). | ||
| */ | ||
| DeadHostState(DeadHostState previousDeadHostState) { | ||
| this(previousDeadHostState, TimeSupplier.DEFAULT); | ||
|
||
| } | ||
|
|
||
| DeadHostState(DeadHostState previousDeadHostState, TimeSupplier timeSupplier) { | ||
| long timeoutNanos = (long)Math.min(MIN_CONNECTION_TIMEOUT_NANOS * 2 * Math.pow(2, previousDeadHostState.failedAttempts * 0.5 - 1), | ||
| MAX_CONNECTION_TIMEOUT_NANOS); | ||
| this.deadUntilNanos = System.nanoTime() + timeoutNanos; | ||
| this.deadUntilNanos = timeSupplier.getNanoTime() + timeoutNanos; | ||
|
||
| this.failedAttempts = previousDeadHostState.failedAttempts + 1; | ||
| this.timeSupplier = timeSupplier; | ||
| } | ||
|
|
||
| boolean shallBeRetried() { | ||
| return timeSupplier.getNanoTime() - deadUntilNanos > 0; | ||
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -61,11 +74,28 @@ long getDeadUntilNanos() { | |
| return deadUntilNanos; | ||
| } | ||
|
|
||
| int getFailedAttempts() { | ||
| return failedAttempts; | ||
| } | ||
|
|
||
| @Override | ||
| public int compareTo(DeadHostState other) { | ||
| return Long.compare(deadUntilNanos, other.deadUntilNanos); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ++ |
||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "DeadHostState{" + | ||
| "failedAttempts=" + failedAttempts + | ||
| ", deadUntilNanos=" + deadUntilNanos + | ||
| '}'; | ||
| } | ||
|
|
||
| static class TimeSupplier { | ||
|
||
| private static final TimeSupplier DEFAULT = new TimeSupplier(); | ||
|
|
||
| long getNanoTime() { | ||
| return System.nanoTime(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| /* | ||
| * Licensed to Elasticsearch under one or more contributor | ||
| * license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright | ||
| * ownership. Elasticsearch licenses this file to you under | ||
| * the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
|
|
||
| package org.elasticsearch.client; | ||
|
|
||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| import static org.hamcrest.MatcherAssert.assertThat; | ||
| import static org.hamcrest.Matchers.equalTo; | ||
| import static org.hamcrest.Matchers.greaterThan; | ||
| import static org.hamcrest.Matchers.is; | ||
| import static org.hamcrest.Matchers.lessThan; | ||
|
|
||
| public class DeadHostStateTests extends RestClientTestCase { | ||
|
|
||
| private static long[] EXPECTED_TIMEOUTS_SECONDS = new long[]{60, 84, 120, 169, 240, 339, 480, 678, 960, 1357, 1800}; | ||
|
|
||
| public void testInitialDeadHostState() { | ||
| DeadHostState deadHostState = new DeadHostState(); | ||
| assertThat(deadHostState.getDeadUntilNanos(), greaterThan(System.nanoTime())); | ||
|
||
| assertThat(deadHostState.getFailedAttempts(), equalTo(1)); | ||
| } | ||
|
|
||
| public void testDeadHostStateFromPrevious() { | ||
| DeadHostState previous = new DeadHostState(); | ||
|
||
| int iters = randomIntBetween(5, 30); | ||
| for (int i = 0; i < iters; i++) { | ||
| DeadHostState deadHostState = new DeadHostState(previous); | ||
| assertThat(deadHostState.getDeadUntilNanos(), greaterThan(previous.getDeadUntilNanos())); | ||
| assertThat(deadHostState.getFailedAttempts(), equalTo(previous.getFailedAttempts() + 1)); | ||
| previous = deadHostState; | ||
| } | ||
| } | ||
|
|
||
| public void testShallBeRetried() { | ||
| ConfigurableTimeSupplier timeSupplier = new ConfigurableTimeSupplier(); | ||
| DeadHostState deadHostState = null; | ||
| for (int i = 0; i < EXPECTED_TIMEOUTS_SECONDS.length; i++) { | ||
| long expectedTimeoutSecond = EXPECTED_TIMEOUTS_SECONDS[i]; | ||
| timeSupplier.nanoTime = 0; | ||
| if (i == 0) { | ||
| deadHostState = new DeadHostState(timeSupplier); | ||
| } else { | ||
| deadHostState = new DeadHostState(deadHostState, timeSupplier); | ||
| } | ||
| for (int j = 0; j < expectedTimeoutSecond; j++) { | ||
| timeSupplier.nanoTime += TimeUnit.SECONDS.toNanos(1); | ||
| assertThat(deadHostState.shallBeRetried(), is(false)); | ||
| } | ||
| int iters = randomIntBetween(5, 30); | ||
| for (int j = 0; j < iters; j++) { | ||
| timeSupplier.nanoTime += TimeUnit.SECONDS.toNanos(1); | ||
| assertThat(deadHostState.shallBeRetried(), is(true)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void testDeadHostStateTimeouts() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels to me like this should be part of |
||
| ConfigurableTimeSupplier zeroTimeSupplier = new ConfigurableTimeSupplier(); | ||
| zeroTimeSupplier.nanoTime = 0L; | ||
| DeadHostState previous = new DeadHostState(zeroTimeSupplier); | ||
| for (long expectedTimeoutsSecond : EXPECTED_TIMEOUTS_SECONDS) { | ||
| assertThat(TimeUnit.NANOSECONDS.toSeconds(previous.getDeadUntilNanos()), equalTo(expectedTimeoutsSecond)); | ||
| previous = new DeadHostState(previous, zeroTimeSupplier); | ||
| } | ||
| //check that from here on the timeout does not increase | ||
| int iters = randomIntBetween(5, 30); | ||
| for (int i = 0; i < iters; i++) { | ||
| DeadHostState deadHostState = new DeadHostState(previous, zeroTimeSupplier); | ||
| assertThat(TimeUnit.NANOSECONDS.toSeconds(deadHostState.getDeadUntilNanos()), | ||
| equalTo(EXPECTED_TIMEOUTS_SECONDS[EXPECTED_TIMEOUTS_SECONDS.length - 1])); | ||
| previous = deadHostState; | ||
| } | ||
| } | ||
|
|
||
| public void testCompareTo() { | ||
| int numObjects = randomIntBetween(5, 30); | ||
| DeadHostState[] deadHostStates = new DeadHostState[numObjects]; | ||
| int failedAttempts = randomIntBetween(1, 5); | ||
| for (int i = 0; i < failedAttempts; i++) { | ||
| for (int j = 0; j < numObjects; j++) { | ||
| if (i == 0) { | ||
| deadHostStates[j] = new DeadHostState(); | ||
| } else { | ||
| deadHostStates[j] = new DeadHostState(deadHostStates[j]); | ||
| } | ||
| } | ||
| for (int k = 1; k < deadHostStates.length; k++) { | ||
| assertThat(deadHostStates[k - 1].getDeadUntilNanos(), lessThan(deadHostStates[k].getDeadUntilNanos())); | ||
| assertThat(deadHostStates[k - 1], lessThan(deadHostStates[k])); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static class ConfigurableTimeSupplier extends DeadHostState.TimeSupplier { | ||
|
|
||
| long nanoTime; | ||
|
|
||
| @Override | ||
| long getNanoTime() { | ||
| return nanoTime; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍