Repository: google/guava (Java)
Date: 2020-07-22 16:03:19+00:00
Task type: Feature

Issue:
Add awaitTerminationUninterruptibly(..) for ExecutorService
_[Original issue](https://code.google.com/p/guava-libraries/issues/detail?id=1315) created by **chriss.dev** on 2013-03-02 at 12:05 PM_

---

The interface ExecutorService provides the method awaitTermination(long,TimeUnit), it would be nice to have "uninterruptibly" pendats in the Uninterruptibles-class like:

boolean awaitTerminationUninterruptibly(ExecutorService executor,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;long timeout,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TimeUnit unit)

//await the termination till the end of all days
void awaitTerminationUninterruptibly(ExecutorService executor)



Issue discussion:
_[Original comment](https://code.google.com/p/guava-libraries/issues/detail?id=1315#c1) posted by **wasserman.louis** on 2013-11-29 at 05:08 PM_

---

_Issue #1595 has been merged into this issue._

I would also like this. Let's add votes to this as more people want it.
Hey,
I would like to work on this.. So based on the original issue, we need 2 methods:

- `boolean awaitTerminationUninterruptibly(ExecutorService executor, long timeout, TimeUnit unit)`
- `void awaitTerminationUninterruptibly(ExecutorService executor)`

And the 1st one will be similar to `boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit)` but for the ExecutorService while the 2nd one will call the first one but for a very long time as timeout.

Does this make sense?

Thanks!
Thanks!

For the second one, we implement it separately to avoid the calls to `System.nanoTime()` entirely. (Those calls aren't _too_ expensive, but it's straightforward enough to implement both the timed and untimed versions.) As you've probably seen, we have some [existing examples of untimed implementations](https://github.com/google/guava/blob/45958948bea8f7aaf38c1a3dd200ce0263c0bcfa/guava/src/com/google/common/util/concurrent/Uninterruptibles.java#L53).
@cpovirk 
The `CountDownLatch` has 2 methods for await (with and without timeout) which is why we can leverage it to avoid the call to `System.nanoTime()`. But for ExecutorService, there is just one `awaitTermination` which needs the timeout. So we will require the call. 
I'll see if I can find a way to bring these 2 methods as close as I can to the current method designs though.

Thanks,
Nisarg
Oops, now I understand. I guess you can still avoid calling nanoTime by repeatedly calling awaitTermination with the same large timeout, but I see now why it's not the same as CountDownLatch.

We could also consider not providing the infinite-wait version.
Yeah.. I don't see any harm in keeping the method for anyone to use if really need to. I will handle the case where I will set a large timeout and not call `System.nanoTime()` when infinite time method is called. 
What do you think?

Also, along with this code and it's corresponding testcases in the test file, is there anything else that will be needed (documentation, etc)?
Thanks, that all sounds great. I don't think we need anything else.
@cpovirk Any update on my pull request?

Solution patch:
diff --git a/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java b/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
--- a/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
+++ b/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
@@ -14,8 +14,10 @@
 
 package com.google.common.util.concurrent;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
+import com.google.common.annotations.Beta;
 import com.google.common.annotations.GwtCompatible;
 import com.google.common.annotations.GwtIncompatible;
 import com.google.common.base.Preconditions;
@@ -24,6 +26,7 @@
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -396,6 +399,50 @@ public static boolean tryLockUninterruptibly(Lock lock, long timeout, TimeUnit u
     }
   }
 
+  /**
+   * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
+   * awaitTermination(long, TimeUnit)} uninterruptibly with no timeout.
+   *
+   * @since NEXT
+   */
+  @Beta
+  @GwtIncompatible // concurrency
+  public static void awaitTerminationUninterruptibly(ExecutorService executor) {
+    // TODO(cpovirk): We could optimize this to avoid calling nanoTime() at all.
+    verify(awaitTerminationUninterruptibly(executor, Long.MAX_VALUE, NANOSECONDS));
+  }
+
+  /**
+   * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
+   * awaitTermination(long, TimeUnit)} uninterruptibly.
+   *
+   * @since NEXT
+   */
+  @Beta
+  @GwtIncompatible // concurrency
+  @SuppressWarnings("GoodTime")
+  public static boolean awaitTerminationUninterruptibly(
+      ExecutorService executor, long timeout, TimeUnit unit) {
+    boolean interrupted = false;
+    try {
+      long remainingNanos = unit.toNanos(timeout);
+      long end = System.nanoTime() + remainingNanos;
+
+      while (true) {
+        try {
+          return executor.awaitTermination(remainingNanos, NANOSECONDS);
+        } catch (InterruptedException e) {
+          interrupted = true;
+          remainingNanos = end - System.nanoTime();
+        }
+      }
+    } finally {
+      if (interrupted) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+
   // TODO(user): Add support for waitUninterruptibly.
 
   private Uninterruptibles() {}
diff --git a/guava/src/com/google/common/util/concurrent/Uninterruptibles.java b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
--- a/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
+++ b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java
@@ -14,6 +14,7 @@
 
 package com.google.common.util.concurrent;
 
+import static com.google.common.base.Verify.verify;
 import static com.google.common.util.concurrent.Internal.toNanosSaturated;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
@@ -27,6 +28,7 @@
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -513,6 +515,63 @@ public static boolean tryLockUninterruptibly(Lock lock, long timeout, TimeUnit u
     }
   }
 
+  /**
+   * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
+   * awaitTermination(long, TimeUnit)} uninterruptibly with no timeout.
+   *
+   * @since NEXT
+   */
+  @Beta
+  @GwtIncompatible // concurrency
+  public static void awaitTerminationUninterruptibly(ExecutorService executor) {
+    // TODO(cpovirk): We could optimize this to avoid calling nanoTime() at all.
+    verify(awaitTerminationUninterruptibly(executor, Long.MAX_VALUE, NANOSECONDS));
+  }
+
+  /**
+   * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
+   * awaitTermination(long, TimeUnit)} uninterruptibly.
+   *
+   * @since NEXT
+   */
+  @Beta
+  @GwtIncompatible // concurrency
+  public static boolean awaitTerminationUninterruptibly(
+      ExecutorService executor, Duration timeout) {
+    return awaitTerminationUninterruptibly(executor, toNanosSaturated(timeout), NANOSECONDS);
+  }
+
+  /**
+   * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
+   * awaitTermination(long, TimeUnit)} uninterruptibly.
+   *
+   * @since NEXT
+   */
+  @Beta
+  @GwtIncompatible // concurrency
+  @SuppressWarnings("GoodTime")
+  public static boolean awaitTerminationUninterruptibly(
+      ExecutorService executor, long timeout, TimeUnit unit) {
+    boolean interrupted = false;
+    try {
+      long remainingNanos = unit.toNanos(timeout);
+      long end = System.nanoTime() + remainingNanos;
+
+      while (true) {
+        try {
+          return executor.awaitTermination(remainingNanos, NANOSECONDS);
+        } catch (InterruptedException e) {
+          interrupted = true;
+          remainingNanos = end - System.nanoTime();
+        }
+      }
+    } finally {
+      if (interrupted) {
+        Thread.currentThread().interrupt();
+      }
+    }
+  }
+
   // TODO(user): Add support for waitUninterruptibly.
 
   private Uninterruptibles() {}


Test patch:
diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
--- a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
+++ b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
@@ -17,12 +17,14 @@
 package com.google.common.util.concurrent;
 
 import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread;
+import static com.google.common.util.concurrent.Uninterruptibles.awaitTerminationUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.putUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.takeUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.tryAcquireUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.tryLockUninterruptibly;
+import static java.util.concurrent.Executors.newFixedThreadPool;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
@@ -36,6 +38,7 @@
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
@@ -465,6 +468,37 @@ public void testTryAcquireTimeoutMultiInterruptExpiredMultiPermit() {
     assertInterrupted();
   }
 
+  // executor.awaitTermination Testcases
+  public void testTryAwaitTerminationUninterruptiblyLongTimeUnit_success() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(1000));
+    executor.shutdown();
+    assertTrue(awaitTerminationUninterruptibly(executor, LONG_DELAY_MS, MILLISECONDS));
+    assertTrue(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationUninterruptiblyLongTimeUnit_failure() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(10000));
+    executor.shutdown();
+    assertFalse(awaitTerminationUninterruptibly(executor, 1000, MILLISECONDS));
+    assertFalse(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationInfiniteTimeout() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(1000));
+    executor.shutdown();
+    awaitTerminationUninterruptibly(executor);
+    assertTrue(executor.isTerminated());
+    assertInterrupted();
+  }
+
   /**
    * Wrapper around {@link Stopwatch} which also contains an "expected completion time." Creating a
    * {@code Completion} starts the underlying stopwatch.
@@ -754,6 +788,15 @@ protected void doAction() {
     }
   }
 
+  private static final class SleepTask extends DelayedActionRunnable {
+    SleepTask(long tMinus) {
+      super(tMinus);
+    }
+
+    @Override
+    protected void doAction() {}
+  }
+
   private static void sleepSuccessfully(long sleepMillis) {
     Completion completed = new Completion(sleepMillis - SLEEP_SLACK);
     Uninterruptibles.sleepUninterruptibly(sleepMillis, MILLISECONDS);
diff --git a/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java b/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
--- a/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java
@@ -17,12 +17,14 @@
 package com.google.common.util.concurrent;
 
 import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread;
+import static com.google.common.util.concurrent.Uninterruptibles.awaitTerminationUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.putUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.takeUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.tryAcquireUninterruptibly;
 import static com.google.common.util.concurrent.Uninterruptibles.tryLockUninterruptibly;
+import static java.util.concurrent.Executors.newFixedThreadPool;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
@@ -32,10 +34,12 @@
 import com.google.common.testing.TearDown;
 import com.google.common.testing.TearDownStack;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.time.Duration;
 import java.util.Date;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
@@ -465,6 +469,57 @@ public void testTryAcquireTimeoutMultiInterruptExpiredMultiPermit() {
     assertInterrupted();
   }
 
+  // executor.awaitTermination Testcases
+  public void testTryAwaitTerminationUninterruptiblyDuration_success() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(1000));
+    executor.shutdown();
+    assertTrue(awaitTerminationUninterruptibly(executor, Duration.ofMillis(LONG_DELAY_MS)));
+    assertTrue(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationUninterruptiblyDuration_failure() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(10000));
+    executor.shutdown();
+    assertFalse(awaitTerminationUninterruptibly(executor, Duration.ofMillis(1000)));
+    assertFalse(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationUninterruptiblyLongTimeUnit_success() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(1000));
+    executor.shutdown();
+    assertTrue(awaitTerminationUninterruptibly(executor, LONG_DELAY_MS, MILLISECONDS));
+    assertTrue(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationUninterruptiblyLongTimeUnit_failure() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(10000));
+    executor.shutdown();
+    assertFalse(awaitTerminationUninterruptibly(executor, 1000, MILLISECONDS));
+    assertFalse(executor.isTerminated());
+    assertInterrupted();
+  }
+
+  public void testTryAwaitTerminationInfiniteTimeout() {
+    ExecutorService executor = newFixedThreadPool(1);
+    requestInterruptIn(500);
+    executor.execute(new SleepTask(1000));
+    executor.shutdown();
+    awaitTerminationUninterruptibly(executor);
+    assertTrue(executor.isTerminated());
+    assertInterrupted();
+  }
+
   /**
    * Wrapper around {@link Stopwatch} which also contains an "expected completion time." Creating a
    * {@code Completion} starts the underlying stopwatch.
@@ -754,6 +809,15 @@ protected void doAction() {
     }
   }
 
+  private static final class SleepTask extends DelayedActionRunnable {
+    SleepTask(long tMinus) {
+      super(tMinus);
+    }
+
+    @Override
+    protected void doAction() {}
+  }
+
   private static void sleepSuccessfully(long sleepMillis) {
     Completion completed = new Completion(sleepMillis - SLEEP_SLACK);
     Uninterruptibles.sleepUninterruptibly(sleepMillis, MILLISECONDS);