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());
+  }
+}