api: Add java.time.Duration overloads to CallOptions, AbstractStub taking TimeUnit and a time value (#11562)

This commit is contained in:
SreeramdasLavanya 2024-10-30 13:19:53 +00:00 committed by GitHub
parent b5ef09c548
commit 766b92379b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 224 additions and 5 deletions

View File

@ -17,9 +17,11 @@
package io.grpc; package io.grpc;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static io.grpc.TimeUtils.convertToNanos;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -176,6 +178,11 @@ public final class CallOptions {
return withDeadline(Deadline.after(duration, unit)); return withDeadline(Deadline.after(duration, unit));
} }
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public CallOptions withDeadlineAfter(Duration duration) {
return withDeadlineAfter(convertToNanos(duration), TimeUnit.NANOSECONDS);
}
/** /**
* Returns the deadline or {@code null} if the deadline is not set. * Returns the deadline or {@code null} if the deadline is not set.
*/ */

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 The gRPC 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 io.grpc;
import java.time.Duration;
@Internal
public final class InternalTimeUtils {
public static long convert(Duration duration) {
return TimeUtils.convertToNanos(duration);
}
}

View File

@ -212,7 +212,7 @@ public abstract class LoadBalancer {
* *
* @since 1.21.0 * @since 1.21.0
*/ */
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771") @ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public static final class ResolvedAddresses { public static final class ResolvedAddresses {
private final List<EquivalentAddressGroup> addresses; private final List<EquivalentAddressGroup> addresses;
@NameResolver.ResolutionResultAttr @NameResolver.ResolutionResultAttr

View File

@ -18,8 +18,10 @@ package io.grpc;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static io.grpc.TimeUtils.convertToNanos;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.time.Duration;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -162,6 +164,12 @@ public final class SynchronizationContext implements Executor {
return new ScheduledHandle(runnable, future); return new ScheduledHandle(runnable, future);
} }
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public final ScheduledHandle schedule(
final Runnable task, Duration delay, ScheduledExecutorService timerService) {
return schedule(task, convertToNanos(delay), TimeUnit.NANOSECONDS, timerService);
}
/** /**
* Schedules a task to be added and run via {@link #execute} after an initial delay and then * Schedules a task to be added and run via {@link #execute} after an initial delay and then
* repeated after the delay until cancelled. * repeated after the delay until cancelled.
@ -193,6 +201,14 @@ public final class SynchronizationContext implements Executor {
return new ScheduledHandle(runnable, future); return new ScheduledHandle(runnable, future);
} }
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public final ScheduledHandle scheduleWithFixedDelay(
final Runnable task, Duration initialDelay, Duration delay,
ScheduledExecutorService timerService) {
return scheduleWithFixedDelay(task, convertToNanos(initialDelay), convertToNanos(delay),
TimeUnit.NANOSECONDS, timerService);
}
private static class ManagedRunnable implements Runnable { private static class ManagedRunnable implements Runnable {
final Runnable task; final Runnable task;

View File

@ -0,0 +1,31 @@
/*
* Copyright 2024 The gRPC 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 io.grpc;
import java.time.Duration;
final class TimeUtils {
private TimeUtils() {}
static long convertToNanos(Duration duration) {
try {
return duration.toNanos();
} catch (ArithmeticException tooBig) {
return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE;
}
}
}

View File

@ -32,6 +32,7 @@ import static org.mockito.Mockito.mock;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import io.grpc.ClientStreamTracer.StreamInfo; import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.internal.SerializingExecutor; import io.grpc.internal.SerializingExecutor;
import java.time.Duration;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -150,6 +151,14 @@ public class CallOptionsTest {
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected); assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
} }
@Test
public void withDeadlineAfterDuration() {
Deadline actual = CallOptions.DEFAULT.withDeadlineAfter(Duration.ofMinutes(1L)).getDeadline();
Deadline expected = Deadline.after(1, MINUTES);
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
}
@Test @Test
public void toStringMatches_noDeadline_default() { public void toStringMatches_noDeadline_default() {
String actual = allSet String actual = allSet

View File

@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import com.google.common.util.concurrent.testing.TestingExecutors; import com.google.common.util.concurrent.testing.TestingExecutors;
import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.SynchronizationContext.ScheduledHandle;
import java.time.Duration;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@ -246,6 +247,41 @@ public class SynchronizationContextTest {
verify(task1).run(); verify(task1).run();
} }
@Test
public void scheduleDuration() {
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
ScheduledHandle handle =
syncContext.schedule(task1, Duration.ofSeconds(10), executorService);
assertThat(executorService.delay)
.isEqualTo(executorService.unit.convert(10, TimeUnit.SECONDS));
assertThat(handle.isPending()).isTrue();
verify(task1, never()).run();
executorService.command.run();
assertThat(handle.isPending()).isFalse();
verify(task1).run();
}
@Test
public void scheduleWithFixedDelayDuration() {
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
ScheduledHandle handle =
syncContext.scheduleWithFixedDelay(task1, Duration.ofSeconds(10),
Duration.ofSeconds(10), executorService);
assertThat(executorService.delay)
.isEqualTo(executorService.unit.convert(10, TimeUnit.SECONDS));
assertThat(handle.isPending()).isTrue();
verify(task1, never()).run();
executorService.command.run();
assertThat(handle.isPending()).isFalse();
verify(task1).run();
}
@Test @Test
public void scheduleDueImmediately() { public void scheduleDueImmediately() {
MockScheduledExecutorService executorService = new MockScheduledExecutorService(); MockScheduledExecutorService executorService = new MockScheduledExecutorService();
@ -357,5 +393,13 @@ public class SynchronizationContextTest {
this.unit = unit; this.unit = unit;
return future = super.schedule(command, delay, unit); return future = super.schedule(command, delay, unit);
} }
@Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long intialDelay,
long delay, TimeUnit unit) {
this.command = command;
this.delay = delay;
this.unit = unit;
return future = super.scheduleWithFixedDelay(command, intialDelay, delay, unit);
}
} }
} }

View File

@ -0,0 +1,59 @@
/*
* Copyright 2024 The gRPC 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 io.grpc;
import static org.junit.Assert.assertEquals;
import java.time.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link TimeUtils}. */
@RunWith(JUnit4.class)
public class TimeUtilsTest {
@Test
public void testConvertNormalDuration() {
Duration duration = Duration.ofSeconds(10);
long expected = 10 * 1_000_000_000L;
assertEquals(expected, TimeUtils.convertToNanos(duration));
}
@Test
public void testConvertNegativeDuration() {
Duration duration = Duration.ofSeconds(-3);
long expected = -3 * 1_000_000_000L;
assertEquals(expected, TimeUtils.convertToNanos(duration));
}
@Test
public void testConvertTooLargeDuration() {
Duration duration = Duration.ofSeconds(Long.MAX_VALUE / 1_000_000_000L + 1);
assertEquals(Long.MAX_VALUE, TimeUtils.convertToNanos(duration));
}
@Test
public void testConvertTooLargeNegativeDuration() {
Duration duration = Duration.ofSeconds(Long.MIN_VALUE / 1_000_000_000L - 1);
assertEquals(Long.MIN_VALUE, TimeUtils.convertToNanos(duration));
}
}

View File

@ -17,6 +17,7 @@
package io.grpc.stub; package io.grpc.stub;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.InternalTimeUtils.convert;
import io.grpc.CallCredentials; import io.grpc.CallCredentials;
import io.grpc.CallOptions; import io.grpc.CallOptions;
@ -26,6 +27,7 @@ import io.grpc.ClientInterceptors;
import io.grpc.Deadline; import io.grpc.Deadline;
import io.grpc.ExperimentalApi; import io.grpc.ExperimentalApi;
import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelBuilder;
import java.time.Duration;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.CheckReturnValue; import javax.annotation.CheckReturnValue;
@ -149,6 +151,11 @@ public abstract class AbstractStub<S extends AbstractStub<S>> {
return build(channel, callOptions.withDeadlineAfter(duration, unit)); return build(channel, callOptions.withDeadlineAfter(duration, unit));
} }
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
public final S withDeadlineAfter(Duration duration) {
return withDeadlineAfter(convert(duration), TimeUnit.NANOSECONDS);
}
/** /**
* Returns a new stub with the given executor that is to be used instead of the default one * Returns a new stub with the given executor that is to be used instead of the default one
* specified with {@link ManagedChannelBuilder#executor}. Note that setting this option may not * specified with {@link ManagedChannelBuilder#executor}. Note that setting this option may not

View File

@ -16,12 +16,18 @@
package io.grpc.stub; package io.grpc.stub;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static io.grpc.testing.DeadlineSubject.deadline;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import io.grpc.CallOptions; import io.grpc.CallOptions;
import io.grpc.Channel; import io.grpc.Channel;
import io.grpc.Deadline;
import io.grpc.stub.AbstractStub.StubFactory; import io.grpc.stub.AbstractStub.StubFactory;
import io.grpc.stub.AbstractStubTest.NoopStub; import io.grpc.stub.AbstractStubTest.NoopStub;
import java.time.Duration;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
@ -47,8 +53,22 @@ public class AbstractStubTest extends BaseAbstractStubTest<NoopStub> {
.isNull(); .isNull();
} }
class NoopStub extends AbstractStub<NoopStub> { @Test
public void testDuration() {
NoopStub stub = NoopStub.newStub(new StubFactory<NoopStub>() {
@Override
public NoopStub newStub(Channel channel, CallOptions callOptions) {
return create(channel, callOptions);
}
}, channel, CallOptions.DEFAULT);
NoopStub stubInstance = stub.withDeadlineAfter(Duration.ofMinutes(1L));
Deadline actual = stubInstance.getCallOptions().getDeadline();
Deadline expected = Deadline.after(1, MINUTES);
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
}
class NoopStub extends AbstractStub<NoopStub> {
NoopStub(Channel channel, CallOptions options) { NoopStub(Channel channel, CallOptions options) {
super(channel, options); super(channel, options);
} }