diff --git a/guava-tests/test/com/google/common/util/concurrent/ListeningExecutorServiceTest.java b/guava-tests/test/com/google/common/util/concurrent/ListeningExecutorServiceTest.java new file mode 100644 index 000000000000..1b1bcb6794bf --- /dev/null +++ b/guava-tests/test/com/google/common/util/concurrent/ListeningExecutorServiceTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed 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 com.google.common.util.concurrent; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.Futures.immediateFailedFuture; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import junit.framework.TestCase; + +public final class ListeningExecutorServiceTest extends TestCase { + + private Collection> recordedTasks; + private long recordedTimeout; + private TimeUnit recordedTimeUnit; + + private final ListeningExecutorService executorService = new FakeExecutorService(); + + public void testInvokeAny() throws Exception { + Set> tasks = Collections.singleton(() -> "invokeAny"); + + String result = executorService.invokeAny(tasks, Duration.ofSeconds(7)); + + assertThat(result).isEqualTo("invokeAny"); + assertThat(recordedTasks).isSameInstanceAs(tasks); + assertThat(recordedTimeUnit).isEqualTo(NANOSECONDS); + assertThat(Duration.ofNanos(recordedTimeout)).isEqualTo(Duration.ofSeconds(7)); + } + + public void testInvokeAll() throws Exception { + Set> tasks = Collections.singleton(() -> "invokeAll"); + + List> result = executorService.invokeAll(tasks, Duration.ofDays(365)); + + assertThat(result).hasSize(1); + assertThat(Futures.getDone(result.get(0))).isEqualTo("invokeAll"); + assertThat(recordedTasks).isSameInstanceAs(tasks); + assertThat(recordedTimeUnit).isEqualTo(NANOSECONDS); + assertThat(Duration.ofNanos(recordedTimeout)).isEqualTo(Duration.ofDays(365)); + } + + public void testAwaitTermination() throws Exception { + boolean result = executorService.awaitTermination(Duration.ofMinutes(144)); + + assertThat(result).isTrue(); + assertThat(recordedTimeUnit).isEqualTo(NANOSECONDS); + assertThat(Duration.ofNanos(recordedTimeout)).isEqualTo(Duration.ofMinutes(144)); + } + + private class FakeExecutorService extends AbstractListeningExecutorService { + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + recordedTasks = tasks; + recordedTimeout = timeout; + recordedTimeUnit = unit; + try { + return tasks.iterator().next().call(); + } catch (Exception e) { + throw new ExecutionException(e); + } + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + recordedTasks = tasks; + recordedTimeout = timeout; + recordedTimeUnit = unit; + try { + return Collections.singletonList(immediateFuture(tasks.iterator().next().call())); + } catch (Exception e) { + return Collections.singletonList(immediateFailedFuture(e)); + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + recordedTimeout = timeout; + recordedTimeUnit = unit; + return true; + } + + @Override + public void execute(Runnable runnable) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isTerminated() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java b/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java index 83ea759f8e74..e5089124c5d7 100644 --- a/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java +++ b/guava/src/com/google/common/util/concurrent/ListeningExecutorService.java @@ -14,15 +14,21 @@ package com.google.common.util.concurrent; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.errorprone.annotations.DoNotMock; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -106,4 +112,37 @@ public interface ListeningExecutorService extends ExecutorService { List> invokeAll( Collection> tasks, long timeout, TimeUnit unit) throws InterruptedException; + + /** + * Duration-based overload of {@link #invokeAll(Collection, long, TimeUnit)}. + * + * @since NEXT + */ + @J2ktIncompatible + default List> invokeAll( + Collection> tasks, Duration timeout) throws InterruptedException { + return invokeAll(tasks, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Duration-based overload of {@link #invokeAny(Collection, long, TimeUnit)}. + * + * @since NEXT + */ + @J2ktIncompatible + default T invokeAny( + Collection> tasks, Duration timeout) + throws InterruptedException, ExecutionException, TimeoutException { + return invokeAny(tasks, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Duration-based overload of {@link #awaitTermination(long, TimeUnit)}. + * + * @since NEXT + */ + @J2ktIncompatible + default boolean awaitTermination(Duration timeout) throws InterruptedException { + return awaitTermination(toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } }