Skip to content

Commit 766b923

Browse files
authoredOct 30, 2024··
api: Add java.time.Duration overloads to CallOptions, AbstractStub taking TimeUnit and a time value (#11562)
1 parent b5ef09c commit 766b923

File tree

10 files changed

+224
-5
lines changed

10 files changed

+224
-5
lines changed
 

‎api/src/main/java/io/grpc/CallOptions.java

+7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package io.grpc;
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
20+
import static io.grpc.TimeUtils.convertToNanos;
2021

2122
import com.google.common.base.MoreObjects;
2223
import com.google.common.base.Preconditions;
24+
import java.time.Duration;
2325
import java.util.ArrayList;
2426
import java.util.Arrays;
2527
import java.util.Collections;
@@ -176,6 +178,11 @@ public CallOptions withDeadlineAfter(long duration, TimeUnit unit) {
176178
return withDeadline(Deadline.after(duration, unit));
177179
}
178180

181+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
182+
public CallOptions withDeadlineAfter(Duration duration) {
183+
return withDeadlineAfter(convertToNanos(duration), TimeUnit.NANOSECONDS);
184+
}
185+
179186
/**
180187
* Returns the deadline or {@code null} if the deadline is not set.
181188
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc;
18+
19+
import java.time.Duration;
20+
21+
@Internal
22+
public final class InternalTimeUtils {
23+
public static long convert(Duration duration) {
24+
return TimeUtils.convertToNanos(duration);
25+
}
26+
}

‎api/src/main/java/io/grpc/LoadBalancer.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
212212
*
213213
* @since 1.21.0
214214
*/
215-
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
215+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
216216
public static final class ResolvedAddresses {
217217
private final List<EquivalentAddressGroup> addresses;
218218
@NameResolver.ResolutionResultAttr

‎api/src/main/java/io/grpc/SynchronizationContext.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static com.google.common.base.Preconditions.checkState;
21+
import static io.grpc.TimeUtils.convertToNanos;
2122

2223
import java.lang.Thread.UncaughtExceptionHandler;
24+
import java.time.Duration;
2325
import java.util.Queue;
2426
import java.util.concurrent.ConcurrentLinkedQueue;
2527
import java.util.concurrent.Executor;
@@ -162,6 +164,12 @@ public String toString() {
162164
return new ScheduledHandle(runnable, future);
163165
}
164166

167+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
168+
public final ScheduledHandle schedule(
169+
final Runnable task, Duration delay, ScheduledExecutorService timerService) {
170+
return schedule(task, convertToNanos(delay), TimeUnit.NANOSECONDS, timerService);
171+
}
172+
165173
/**
166174
* Schedules a task to be added and run via {@link #execute} after an initial delay and then
167175
* repeated after the delay until cancelled.
@@ -193,6 +201,14 @@ public String toString() {
193201
return new ScheduledHandle(runnable, future);
194202
}
195203

204+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
205+
public final ScheduledHandle scheduleWithFixedDelay(
206+
final Runnable task, Duration initialDelay, Duration delay,
207+
ScheduledExecutorService timerService) {
208+
return scheduleWithFixedDelay(task, convertToNanos(initialDelay), convertToNanos(delay),
209+
TimeUnit.NANOSECONDS, timerService);
210+
}
211+
196212

197213
private static class ManagedRunnable implements Runnable {
198214
final Runnable task;
@@ -246,4 +262,4 @@ public boolean isPending() {
246262
return !(runnable.hasStarted || runnable.isCancelled);
247263
}
248264
}
249-
}
265+
}
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc;
18+
19+
import java.time.Duration;
20+
21+
final class TimeUtils {
22+
private TimeUtils() {}
23+
24+
static long convertToNanos(Duration duration) {
25+
try {
26+
return duration.toNanos();
27+
} catch (ArithmeticException tooBig) {
28+
return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE;
29+
}
30+
}
31+
}

‎api/src/test/java/io/grpc/CallOptionsTest.java

+9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.google.common.base.Objects;
3333
import io.grpc.ClientStreamTracer.StreamInfo;
3434
import io.grpc.internal.SerializingExecutor;
35+
import java.time.Duration;
3536
import java.util.concurrent.Executor;
3637
import org.junit.Test;
3738
import org.junit.runner.RunWith;
@@ -150,6 +151,14 @@ public void withDeadlineAfter() {
150151
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
151152
}
152153

154+
@Test
155+
public void withDeadlineAfterDuration() {
156+
Deadline actual = CallOptions.DEFAULT.withDeadlineAfter(Duration.ofMinutes(1L)).getDeadline();
157+
Deadline expected = Deadline.after(1, MINUTES);
158+
159+
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
160+
}
161+
153162
@Test
154163
public void toStringMatches_noDeadline_default() {
155164
String actual = allSet

‎api/src/test/java/io/grpc/SynchronizationContextTest.java

+46-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import com.google.common.util.concurrent.testing.TestingExecutors;
2929
import io.grpc.SynchronizationContext.ScheduledHandle;
30+
import java.time.Duration;
3031
import java.util.concurrent.BlockingQueue;
3132
import java.util.concurrent.CountDownLatch;
3233
import java.util.concurrent.LinkedBlockingQueue;
@@ -72,7 +73,7 @@ public void uncaughtException(Thread t, Throwable e) {
7273

7374
@Mock
7475
private Runnable task3;
75-
76+
7677
@After public void tearDown() {
7778
assertThat(uncaughtErrors).isEmpty();
7879
}
@@ -246,6 +247,41 @@ public void schedule() {
246247
verify(task1).run();
247248
}
248249

250+
@Test
251+
public void scheduleDuration() {
252+
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
253+
ScheduledHandle handle =
254+
syncContext.schedule(task1, Duration.ofSeconds(10), executorService);
255+
256+
assertThat(executorService.delay)
257+
.isEqualTo(executorService.unit.convert(10, TimeUnit.SECONDS));
258+
assertThat(handle.isPending()).isTrue();
259+
verify(task1, never()).run();
260+
261+
executorService.command.run();
262+
263+
assertThat(handle.isPending()).isFalse();
264+
verify(task1).run();
265+
}
266+
267+
@Test
268+
public void scheduleWithFixedDelayDuration() {
269+
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
270+
ScheduledHandle handle =
271+
syncContext.scheduleWithFixedDelay(task1, Duration.ofSeconds(10),
272+
Duration.ofSeconds(10), executorService);
273+
274+
assertThat(executorService.delay)
275+
.isEqualTo(executorService.unit.convert(10, TimeUnit.SECONDS));
276+
assertThat(handle.isPending()).isTrue();
277+
verify(task1, never()).run();
278+
279+
executorService.command.run();
280+
281+
assertThat(handle.isPending()).isFalse();
282+
verify(task1).run();
283+
}
284+
249285
@Test
250286
public void scheduleDueImmediately() {
251287
MockScheduledExecutorService executorService = new MockScheduledExecutorService();
@@ -357,5 +393,13 @@ static class MockScheduledExecutorService extends ForwardingScheduledExecutorSer
357393
this.unit = unit;
358394
return future = super.schedule(command, delay, unit);
359395
}
396+
397+
@Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long intialDelay,
398+
long delay, TimeUnit unit) {
399+
this.command = command;
400+
this.delay = delay;
401+
this.unit = unit;
402+
return future = super.scheduleWithFixedDelay(command, intialDelay, delay, unit);
403+
}
360404
}
361-
}
405+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2024 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import java.time.Duration;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.junit.runners.JUnit4;
25+
26+
/** Unit tests for {@link TimeUtils}. */
27+
@RunWith(JUnit4.class)
28+
public class TimeUtilsTest {
29+
30+
@Test
31+
public void testConvertNormalDuration() {
32+
Duration duration = Duration.ofSeconds(10);
33+
long expected = 10 * 1_000_000_000L;
34+
35+
assertEquals(expected, TimeUtils.convertToNanos(duration));
36+
}
37+
38+
@Test
39+
public void testConvertNegativeDuration() {
40+
Duration duration = Duration.ofSeconds(-3);
41+
long expected = -3 * 1_000_000_000L;
42+
43+
assertEquals(expected, TimeUtils.convertToNanos(duration));
44+
}
45+
46+
@Test
47+
public void testConvertTooLargeDuration() {
48+
Duration duration = Duration.ofSeconds(Long.MAX_VALUE / 1_000_000_000L + 1);
49+
50+
assertEquals(Long.MAX_VALUE, TimeUtils.convertToNanos(duration));
51+
}
52+
53+
@Test
54+
public void testConvertTooLargeNegativeDuration() {
55+
Duration duration = Duration.ofSeconds(Long.MIN_VALUE / 1_000_000_000L - 1);
56+
57+
assertEquals(Long.MIN_VALUE, TimeUtils.convertToNanos(duration));
58+
}
59+
}

‎stub/src/main/java/io/grpc/stub/AbstractStub.java

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.grpc.stub;
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static io.grpc.InternalTimeUtils.convert;
2021

2122
import io.grpc.CallCredentials;
2223
import io.grpc.CallOptions;
@@ -26,6 +27,7 @@
2627
import io.grpc.Deadline;
2728
import io.grpc.ExperimentalApi;
2829
import io.grpc.ManagedChannelBuilder;
30+
import java.time.Duration;
2931
import java.util.concurrent.Executor;
3032
import java.util.concurrent.TimeUnit;
3133
import javax.annotation.CheckReturnValue;
@@ -149,6 +151,11 @@ public final S withDeadlineAfter(long duration, TimeUnit unit) {
149151
return build(channel, callOptions.withDeadlineAfter(duration, unit));
150152
}
151153

154+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11657")
155+
public final S withDeadlineAfter(Duration duration) {
156+
return withDeadlineAfter(convert(duration), TimeUnit.NANOSECONDS);
157+
}
158+
152159
/**
153160
* Returns a new stub with the given executor that is to be used instead of the default one
154161
* specified with {@link ManagedChannelBuilder#executor}. Note that setting this option may not

‎stub/src/test/java/io/grpc/stub/AbstractStubTest.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@
1616

1717
package io.grpc.stub;
1818

19+
import static com.google.common.truth.Truth.assertAbout;
1920
import static com.google.common.truth.Truth.assertThat;
21+
import static io.grpc.testing.DeadlineSubject.deadline;
22+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
23+
import static java.util.concurrent.TimeUnit.MINUTES;
2024

2125
import io.grpc.CallOptions;
2226
import io.grpc.Channel;
27+
import io.grpc.Deadline;
2328
import io.grpc.stub.AbstractStub.StubFactory;
2429
import io.grpc.stub.AbstractStubTest.NoopStub;
30+
import java.time.Duration;
2531
import org.junit.Test;
2632
import org.junit.runner.RunWith;
2733
import org.junit.runners.JUnit4;
@@ -47,8 +53,22 @@ public NoopStub newStub(Channel channel, CallOptions callOptions) {
4753
.isNull();
4854
}
4955

50-
class NoopStub extends AbstractStub<NoopStub> {
56+
@Test
57+
public void testDuration() {
58+
NoopStub stub = NoopStub.newStub(new StubFactory<NoopStub>() {
59+
@Override
60+
public NoopStub newStub(Channel channel, CallOptions callOptions) {
61+
return create(channel, callOptions);
62+
}
63+
}, channel, CallOptions.DEFAULT);
64+
NoopStub stubInstance = stub.withDeadlineAfter(Duration.ofMinutes(1L));
65+
Deadline actual = stubInstance.getCallOptions().getDeadline();
66+
Deadline expected = Deadline.after(1, MINUTES);
5167

68+
assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
69+
}
70+
71+
class NoopStub extends AbstractStub<NoopStub> {
5272
NoopStub(Channel channel, CallOptions options) {
5373
super(channel, options);
5474
}

0 commit comments

Comments
 (0)
Please sign in to comment.