You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ji...@apache.org on 2021/04/09 15:17:24 UTC
[geode] branch support/1.12 updated: GEODE-8623: Retry getting
local host if it fails. (#5743)
This is an automated email from the ASF dual-hosted git repository.
jinmeiliao pushed a commit to branch support/1.12
in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/support/1.12 by this push:
new 0cbf412 GEODE-8623: Retry getting local host if it fails. (#5743)
0cbf412 is described below
commit 0cbf41293b49b4b3af7eca0a132f494190bbac81
Author: Jinmei Liao <ji...@pivotal.io>
AuthorDate: Mon Nov 30 11:39:18 2020 -0800
GEODE-8623: Retry getting local host if it fails. (#5743)
Co-authored-by: Jacob Barrett <jb...@pivotal.io>
(cherry picked from commit 5deb409fe5498845b1365463b11f7a8d558c55f7)
# Conflicts:
# geode-common/build.gradle
# geode-core/src/main/java/org/apache/geode/internal/net/SocketCreator.java
# geode-tcp-server/src/distributedTest/java/org/apache/geode/distributed/internal/tcpserver/TcpServerProductVersionDUnitTest.java
---
geode-common/build.gradle | 1 +
.../main/java/org/apache/geode/internal/Retry.java | 101 +++++++++++++++++++++
.../apache/geode/internal/inet/LocalHostUtil.java | 31 +++++--
.../java/org/apache/geode/internal/RetryTest.java | 93 +++++++++++++++++++
4 files changed, 219 insertions(+), 7 deletions(-)
diff --git a/geode-common/build.gradle b/geode-common/build.gradle
index c083f01..ced5435 100755
--- a/geode-common/build.gradle
+++ b/geode-common/build.gradle
@@ -24,4 +24,5 @@ dependencies {
compile('com.fasterxml.jackson.core:jackson-databind')
testCompile('junit:junit')
testCompile('org.assertj:assertj-core')
+ testCompile('org.mockito:mockito-core')
}
diff --git a/geode-common/src/main/java/org/apache/geode/internal/Retry.java b/geode-common/src/main/java/org/apache/geode/internal/Retry.java
new file mode 100644
index 0000000..6189c36
--- /dev/null
+++ b/geode-common/src/main/java/org/apache/geode/internal/Retry.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF 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.apache.geode.internal;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import org.apache.geode.annotations.VisibleForTesting;
+
+/**
+ * Utility class for retrying operations.
+ */
+public class Retry {
+
+ interface Timer {
+ long nanoTime();
+
+ void sleep(long sleepTimeInNano) throws InterruptedException;
+ }
+
+ static class SteadyTimer implements Timer {
+ @Override
+ public long nanoTime() {
+ return System.nanoTime();
+ }
+
+ @Override
+ public void sleep(long sleepTimeInNano) throws InterruptedException {
+ long millis = NANOSECONDS.toMillis(sleepTimeInNano);
+ // avoid throwing IllegalArgumentException
+ if (millis > 0) {
+ Thread.sleep(millis);
+ }
+ }
+ }
+
+ private static final SteadyTimer steadyClock = new SteadyTimer();
+
+ /**
+ * Try the supplier function until the predicate is true or timeout occurs.
+ *
+ * @param timeout to retry for
+ * @param timeoutUnit the unit for timeout
+ * @param interval time between each try
+ * @param intervalUnit the unit for interval
+ * @param supplier to execute until predicate is true or times out
+ * @param predicate to test for retry
+ * @param <T> type of return value
+ * @return value from supplier after it passes predicate or times out.
+ */
+ public static <T> T tryFor(long timeout, TimeUnit timeoutUnit,
+ long interval, TimeUnit intervalUnit,
+ Supplier<T> supplier,
+ Predicate<T> predicate) throws TimeoutException, InterruptedException {
+ return tryFor(timeout, timeoutUnit, interval, intervalUnit, supplier, predicate, steadyClock);
+ }
+
+ @VisibleForTesting
+ static <T> T tryFor(long timeout, TimeUnit timeoutUnit,
+ long interval, TimeUnit intervalUnit,
+ Supplier<T> supplier,
+ Predicate<T> predicate,
+ Timer timer) throws TimeoutException, InterruptedException {
+ long until = timer.nanoTime() + NANOSECONDS.convert(timeout, timeoutUnit);
+ long intervalNano = NANOSECONDS.convert(interval, intervalUnit);
+
+ T value;
+ for (;;) {
+ value = supplier.get();
+ if (predicate.test(value)) {
+ return value;
+ } else {
+ // if there is still more time left after we sleep for interval period, then sleep and retry
+ // otherwise break out and throw TimeoutException
+ if ((timer.nanoTime() + intervalNano) < until) {
+ timer.sleep(intervalNano);
+ } else {
+ break;
+ }
+ }
+ }
+
+ throw new TimeoutException();
+ }
+}
diff --git a/geode-common/src/main/java/org/apache/geode/internal/inet/LocalHostUtil.java b/geode-common/src/main/java/org/apache/geode/internal/inet/LocalHostUtil.java
index f904c55..b906b68 100644
--- a/geode-common/src/main/java/org/apache/geode/internal/inet/LocalHostUtil.java
+++ b/geode-common/src/main/java/org/apache/geode/internal/inet/LocalHostUtil.java
@@ -14,6 +14,9 @@
*/
package org.apache.geode.internal.inet;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.geode.internal.Retry.tryFor;
+
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -23,7 +26,9 @@ import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
+import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
@@ -54,18 +59,26 @@ public class LocalHostUtil {
Boolean.getBoolean(USE_LINK_LOCAL_ADDRESSES_PROPERTY);
/**
- * we cache localHost to avoid bug #40619, access-violation in native code
- */
- private static final InetAddress localHost;
-
- /**
* all classes should use this variable to determine whether to use IPv4 or IPv6 addresses
*/
@MakeNotStatic
private static boolean useIPv6Addresses = !Boolean.getBoolean("java.net.preferIPv4Stack")
&& Boolean.getBoolean("java.net.preferIPv6Addresses");
- static {
+ /**
+ * Resolves local host. Will retry if resolution fails.
+ *
+ * @return local host if resolved otherwise null.
+ */
+ private static InetAddress tryToResolveLocalHost() {
+ try {
+ return tryFor(60, SECONDS, 1, SECONDS, LocalHostUtil::resolveLocalHost, Objects::nonNull);
+ } catch (TimeoutException | InterruptedException ignored) {
+ }
+ return null;
+ }
+
+ private static InetAddress resolveLocalHost() {
InetAddress inetAddress = null;
try {
inetAddress = InetAddress.getByAddress(InetAddress.getLocalHost().getAddress());
@@ -114,9 +127,13 @@ public class LocalHostUtil {
}
} catch (UnknownHostException ignored) {
}
- localHost = inetAddress;
+ return inetAddress;
}
+ /**
+ * Cache local host to avoid lookup costs.
+ */
+ private static final InetAddress localHost = tryToResolveLocalHost();
/**
* returns a set of the non-loopback InetAddresses for this machine
diff --git a/geode-common/src/test/java/org/apache/geode/internal/RetryTest.java b/geode-common/src/test/java/org/apache/geode/internal/RetryTest.java
new file mode 100644
index 0000000..09f0892
--- /dev/null
+++ b/geode-common/src/test/java/org/apache/geode/internal/RetryTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF 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.apache.geode.internal;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Objects;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class RetryTest {
+ Retry.Timer timer;
+
+ @Before
+ public void before() throws Exception {
+ AtomicLong atomicLong = new AtomicLong();
+ timer = mock(Retry.Timer.class);
+ when(timer.nanoTime()).thenReturn(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L);
+ }
+
+ @Test
+ public void tryForReturnsImmediatelyOnPredicateMatch()
+ throws TimeoutException, InterruptedException {
+ final Integer value =
+ Retry.tryFor(1, NANOSECONDS, 1, NANOSECONDS, () -> 10, (v) -> v == 10, timer);
+ assertThat(value).isEqualTo(10);
+ // nanoTime is only called one time if predicate match immediately
+ verify(timer, times(1)).nanoTime();
+ // sleep is never called if predicate matches immediately
+ verify(timer, never()).sleep(anyLong());
+ }
+
+ @Test
+ public void tryForReturnsAfterRetries() throws TimeoutException, InterruptedException {
+ final AtomicInteger shared = new AtomicInteger();
+ final Integer value =
+ Retry.tryFor(10, NANOSECONDS, 1, NANOSECONDS, shared::getAndIncrement, (v) -> v == 3,
+ timer);
+ assertThat(value).isEqualTo(3);
+ verify(timer, times(4)).nanoTime();
+ verify(timer, times(3)).sleep(1L);
+ }
+
+ @Test
+ public void tryForThrowsAfterTimeout() throws InterruptedException {
+ assertThatThrownBy(
+ () -> Retry.tryFor(3, NANOSECONDS, 1, NANOSECONDS, () -> null, Objects::nonNull, timer))
+ .isInstanceOf(TimeoutException.class);
+ verify(timer, times(3)).nanoTime();
+ verify(timer, times(1)).sleep(1L);
+ }
+
+ @Test
+ public void timerSleepCanTakeNegativeArgument() throws Exception {
+ Retry.SteadyTimer steadyTimer = new Retry.SteadyTimer();
+ assertThatNoException().isThrownBy(() -> steadyTimer.sleep(-2));
+ }
+
+ @Test
+ public void lastIterationSleepForLessThanIntervalTime() throws Exception {
+ assertThatThrownBy(
+ () -> Retry.tryFor(2, NANOSECONDS, 3, NANOSECONDS, () -> null, Objects::nonNull, timer))
+ .isInstanceOf(TimeoutException.class);
+ verify(timer, times(2)).nanoTime();
+ verify(timer, never()).sleep(anyLong());
+ }
+}