Skip to content

Commit

Permalink
api: Add java.time.Duration overloads to CallOptions, AbstractStub ta…
Browse files Browse the repository at this point in the history
…king TimeUnit and a time value (#11562)
  • Loading branch information
SreeramdasLavanya authored Oct 30, 2024
1 parent b5ef09c commit 766b923
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 5 deletions.
7 changes: 7 additions & 0 deletions api/src/main/java/io/grpc/CallOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package io.grpc;

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.Preconditions;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -176,6 +178,11 @@ public CallOptions withDeadlineAfter(long duration, TimeUnit 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.
*/
Expand Down
26 changes: 26 additions & 0 deletions api/src/main/java/io/grpc/InternalTimeUtils.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 1 addition & 1 deletion api/src/main/java/io/grpc/LoadBalancer.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
*
* @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 {
private final List<EquivalentAddressGroup> addresses;
@NameResolver.ResolutionResultAttr
Expand Down
18 changes: 17 additions & 1 deletion api/src/main/java/io/grpc/SynchronizationContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static io.grpc.TimeUtils.convertToNanos;

import java.lang.Thread.UncaughtExceptionHandler;
import java.time.Duration;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -162,6 +164,12 @@ public String toString() {
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
* repeated after the delay until cancelled.
Expand Down Expand Up @@ -193,6 +201,14 @@ public String toString() {
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 {
final Runnable task;
Expand Down Expand Up @@ -246,4 +262,4 @@ public boolean isPending() {
return !(runnable.hasStarted || runnable.isCancelled);
}
}
}
}
31 changes: 31 additions & 0 deletions api/src/main/java/io/grpc/TimeUtils.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
9 changes: 9 additions & 0 deletions api/src/test/java/io/grpc/CallOptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.common.base.Objects;
import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.internal.SerializingExecutor;
import java.time.Duration;
import java.util.concurrent.Executor;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -150,6 +151,14 @@ public void withDeadlineAfter() {
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
public void toStringMatches_noDeadline_default() {
String actual = allSet
Expand Down
48 changes: 46 additions & 2 deletions api/src/test/java/io/grpc/SynchronizationContextTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import com.google.common.util.concurrent.testing.TestingExecutors;
import io.grpc.SynchronizationContext.ScheduledHandle;
import java.time.Duration;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
Expand Down Expand Up @@ -72,7 +73,7 @@ public void uncaughtException(Thread t, Throwable e) {

@Mock
private Runnable task3;

@After public void tearDown() {
assertThat(uncaughtErrors).isEmpty();
}
Expand Down Expand Up @@ -246,6 +247,41 @@ public void schedule() {
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
public void scheduleDueImmediately() {
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
Expand Down Expand Up @@ -357,5 +393,13 @@ static class MockScheduledExecutorService extends ForwardingScheduledExecutorSer
this.unit = 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);
}
}
}
}
59 changes: 59 additions & 0 deletions api/src/test/java/io/grpc/TimeUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
7 changes: 7 additions & 0 deletions stub/src/main/java/io/grpc/stub/AbstractStub.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.grpc.stub;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.InternalTimeUtils.convert;

import io.grpc.CallCredentials;
import io.grpc.CallOptions;
Expand All @@ -26,6 +27,7 @@
import io.grpc.Deadline;
import io.grpc.ExperimentalApi;
import io.grpc.ManagedChannelBuilder;
import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.CheckReturnValue;
Expand Down Expand Up @@ -149,6 +151,11 @@ public final S withDeadlineAfter(long duration, TimeUnit 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
* specified with {@link ManagedChannelBuilder#executor}. Note that setting this option may not
Expand Down
22 changes: 21 additions & 1 deletion stub/src/test/java/io/grpc/stub/AbstractStubTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@

package io.grpc.stub;

import static com.google.common.truth.Truth.assertAbout;
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.Channel;
import io.grpc.Deadline;
import io.grpc.stub.AbstractStub.StubFactory;
import io.grpc.stub.AbstractStubTest.NoopStub;
import java.time.Duration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand All @@ -47,8 +53,22 @@ public NoopStub newStub(Channel channel, CallOptions callOptions) {
.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) {
super(channel, options);
}
Expand Down

0 comments on commit 766b923

Please sign in to comment.