mirror of https://github.com/grpc/grpc-java.git
api: Add java.time.Duration overloads to CallOptions, AbstractStub taking TimeUnit and a time value (#11562)
This commit is contained in:
parent
b5ef09c548
commit
766b92379b
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue