Skip to content

Commit 47ddfa4

Browse files
authored
okhttp: add maxConnectionAge and maxConnectionAgeGrace (#9649)
1 parent 38311e8 commit 47ddfa4

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

okhttp/src/main/java/io/grpc/okhttp/OkHttpServerBuilder.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ public final class OkHttpServerBuilder extends ForwardingServerBuilder<OkHttpSer
6868

6969
static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE;
7070
private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L);
71+
static final long MAX_CONNECTION_AGE_NANOS_DISABLED = Long.MAX_VALUE;
72+
static final long MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE = Long.MAX_VALUE;
73+
private static final long MIN_MAX_CONNECTION_AGE_NANO = TimeUnit.SECONDS.toNanos(1L);
7174

7275
private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
7376
private static final ObjectPool<Executor> DEFAULT_TRANSPORT_EXECUTOR_POOL =
@@ -120,6 +123,8 @@ public static OkHttpServerBuilder forPort(SocketAddress address, ServerCredentia
120123
long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
121124
boolean permitKeepAliveWithoutCalls;
122125
long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5);
126+
long maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
127+
long maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
123128

124129
@VisibleForTesting
125130
OkHttpServerBuilder(
@@ -209,6 +214,45 @@ public OkHttpServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit ti
209214
return this;
210215
}
211216

217+
/**
218+
* Sets a custom max connection age, connection lasting longer than which will be gracefully
219+
* terminated. An unreasonably small value might be increased. A random jitter of +/-10% will be
220+
* added to it. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable
221+
* max connection age.
222+
*/
223+
@Override
224+
public OkHttpServerBuilder maxConnectionAge(long maxConnectionAge, TimeUnit timeUnit) {
225+
checkArgument(maxConnectionAge > 0L, "max connection age must be positive: %s",
226+
maxConnectionAge);
227+
maxConnectionAgeInNanos = timeUnit.toNanos(maxConnectionAge);
228+
if (maxConnectionAgeInNanos >= AS_LARGE_AS_INFINITE) {
229+
maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
230+
}
231+
if (maxConnectionAgeInNanos < MIN_MAX_CONNECTION_AGE_NANO) {
232+
maxConnectionAgeInNanos = MIN_MAX_CONNECTION_AGE_NANO;
233+
}
234+
return this;
235+
}
236+
237+
/**
238+
* Sets a custom grace time for the graceful connection termination. Once the max connection age
239+
* is reached, RPCs have the grace time to complete. RPCs that do not complete in time will be
240+
* cancelled, allowing the connection to terminate. {@code Long.MAX_VALUE} nano seconds or an
241+
* unreasonably large value are considered infinite.
242+
*
243+
* @see #maxConnectionAge(long, TimeUnit)
244+
*/
245+
@Override
246+
public OkHttpServerBuilder maxConnectionAgeGrace(long maxConnectionAgeGrace, TimeUnit timeUnit) {
247+
checkArgument(maxConnectionAgeGrace >= 0L, "max connection age grace must be non-negative: %s",
248+
maxConnectionAgeGrace);
249+
maxConnectionAgeGraceInNanos = timeUnit.toNanos(maxConnectionAgeGrace);
250+
if (maxConnectionAgeGraceInNanos >= AS_LARGE_AS_INFINITE) {
251+
maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
252+
}
253+
return this;
254+
}
255+
212256
/**
213257
* Sets a time waiting for read activity after sending a keepalive ping. If the time expires
214258
* without any read activity on the connection, the connection is considered dead. An unreasonably

okhttp/src/main/java/io/grpc/okhttp/OkHttpServerTransport.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.grpc.okhttp;
1818

19+
import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
1920
import static io.grpc.okhttp.OkHttpServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
2021

2122
import com.google.common.base.Preconditions;
@@ -31,6 +32,7 @@
3132
import io.grpc.internal.GrpcUtil;
3233
import io.grpc.internal.KeepAliveEnforcer;
3334
import io.grpc.internal.KeepAliveManager;
35+
import io.grpc.internal.LogExceptionRunnable;
3436
import io.grpc.internal.MaxConnectionIdleManager;
3537
import io.grpc.internal.ObjectPool;
3638
import io.grpc.internal.SerializingExecutor;
@@ -96,6 +98,7 @@ final class OkHttpServerTransport implements ServerTransport,
9698
private Attributes attributes;
9799
private KeepAliveManager keepAliveManager;
98100
private MaxConnectionIdleManager maxConnectionIdleManager;
101+
private ScheduledFuture<?> maxConnectionAgeMonitor;
99102
private final KeepAliveEnforcer keepAliveEnforcer;
100103

101104
private final Object lock = new Object();
@@ -223,6 +226,15 @@ public void data(boolean outFinished, int streamId, Buffer source, int byteCount
223226
maxConnectionIdleManager.start(this::shutdown, scheduledExecutorService);
224227
}
225228

229+
if (config.maxConnectionAgeInNanos != MAX_CONNECTION_AGE_NANOS_DISABLED) {
230+
long maxConnectionAgeInNanos =
231+
(long) ((.9D + Math.random() * .2D) * config.maxConnectionAgeInNanos);
232+
maxConnectionAgeMonitor = scheduledExecutorService.schedule(
233+
new LogExceptionRunnable(() -> shutdown(config.maxConnectionAgeGraceInNanos)),
234+
maxConnectionAgeInNanos,
235+
TimeUnit.NANOSECONDS);
236+
}
237+
226238
transportExecutor.execute(
227239
new FrameHandler(variant.newReader(Okio.buffer(Okio.source(socket)), false)));
228240
} catch (Error | IOException | RuntimeException ex) {
@@ -238,6 +250,10 @@ public void data(boolean outFinished, int streamId, Buffer source, int byteCount
238250

239251
@Override
240252
public void shutdown() {
253+
shutdown(TimeUnit.SECONDS.toNanos(1L));
254+
}
255+
256+
private void shutdown(Long graceTimeInNanos) {
241257
synchronized (lock) {
242258
if (gracefulShutdown || abruptShutdown) {
243259
return;
@@ -251,7 +267,7 @@ public void shutdown() {
251267
// we also set a timer to limit the upper bound in case the PING is excessively stalled or
252268
// the client is malicious.
253269
secondGoawayTimer = scheduledExecutorService.schedule(
254-
this::triggerGracefulSecondGoaway, 1, TimeUnit.SECONDS);
270+
this::triggerGracefulSecondGoaway, graceTimeInNanos, TimeUnit.NANOSECONDS);
255271
frameWriter.goAway(Integer.MAX_VALUE, ErrorCode.NO_ERROR, new byte[0]);
256272
frameWriter.ping(false, 0, GRACEFUL_SHUTDOWN_PING);
257273
frameWriter.flush();
@@ -348,6 +364,10 @@ private void terminated() {
348364
if (maxConnectionIdleManager != null) {
349365
maxConnectionIdleManager.onTransportTermination();
350366
}
367+
368+
if (maxConnectionAgeMonitor != null) {
369+
maxConnectionAgeMonitor.cancel(false);
370+
}
351371
transportExecutor = config.transportExecutorPool.returnObject(transportExecutor);
352372
scheduledExecutorService =
353373
config.scheduledExecutorServicePool.returnObject(scheduledExecutorService);
@@ -479,6 +499,8 @@ static final class Config {
479499
final long maxConnectionIdleNanos;
480500
final boolean permitKeepAliveWithoutCalls;
481501
final long permitKeepAliveTimeInNanos;
502+
final long maxConnectionAgeInNanos;
503+
final long maxConnectionAgeGraceInNanos;
482504

483505
public Config(
484506
OkHttpServerBuilder builder,
@@ -501,6 +523,8 @@ public Config(
501523
maxConnectionIdleNanos = builder.maxConnectionIdleInNanos;
502524
permitKeepAliveWithoutCalls = builder.permitKeepAliveWithoutCalls;
503525
permitKeepAliveTimeInNanos = builder.permitKeepAliveTimeInNanos;
526+
maxConnectionAgeInNanos = builder.maxConnectionAgeInNanos;
527+
maxConnectionAgeGraceInNanos = builder.maxConnectionAgeGraceInNanos;
504528
}
505529
}
506530

okhttp/src/test/java/io/grpc/okhttp/OkHttpServerTransportTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,27 @@ public void startThenShutdown() throws Exception {
152152
shutdownAndTerminate(/*lastStreamId=*/ 0);
153153
}
154154

155+
@Test
156+
public void maxConnectionAge() throws Exception {
157+
serverBuilder.maxConnectionAge(5, TimeUnit.SECONDS)
158+
.maxConnectionAgeGrace(1, TimeUnit.SECONDS);
159+
initTransport();
160+
handshake();
161+
clientFrameWriter.headers(1, Arrays.asList(
162+
HTTP_SCHEME_HEADER,
163+
METHOD_HEADER,
164+
new Header(Header.TARGET_AUTHORITY, "example.com:80"),
165+
new Header(Header.TARGET_PATH, "/com.example/SimpleService.doit"),
166+
CONTENT_TYPE_HEADER,
167+
TE_HEADER));
168+
clientFrameWriter.synStream(true, false, 1, -1, Arrays.asList(
169+
new Header("some-client-sent-trailer", "trailer-value")));
170+
pingPong();
171+
fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(6)); // > 1.1 * 5
172+
fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(1));
173+
verifyGracefulShutdown(1);
174+
}
175+
155176
@Test
156177
public void maxConnectionIdleTimer() throws Exception {
157178
initTransport();

0 commit comments

Comments
 (0)