From 7c21957f42c1791e48c4980c814ac54047444a67 Mon Sep 17 00:00:00 2001 From: jrhee17 Date: Mon, 12 Aug 2024 17:16:29 +0900 Subject: [PATCH] `ResponseTimeout` can be scheduled at an earlier timing (#5793) Motivation: The original motivation of this PR stems from https://github.com/line/armeria/issues/4591. There has been requests for a timeout which spans over an entire client's lifecycle. Following #5800, this can be achieved easily by adjusting where to call `CancellationScheduler#start`. I propose that we add an enum `ResponseTimeoutMode` which decides when a `CancellationScheduler` will start to be scheduled. By doing so, we can allow users to choose when a response timeout will start to be scheduled per client. Modifications: - Introduced a new `ResponseTimeoutMode` enum and added options in `AbstractClientOptionsBuilder` and `Flags` respectively - Depending on the set `ResponseTimeoutMode`, the timeout is started on 1) context init 2) request start 3) or response read Result: - Closes https://github.com/line/armeria/issues/4591 --- .../client/AbstractClientOptionsBuilder.java | 12 ++ .../client/AbstractHttpRequestHandler.java | 3 + .../BlockingWebClientRequestPreparation.java | 6 + .../armeria/client/ClientBuilder.java | 5 + .../armeria/client/ClientOptions.java | 15 ++ .../armeria/client/ClientOptionsBuilder.java | 5 + .../armeria/client/ClientRequestContext.java | 9 ++ .../client/ClientRequestContextWrapper.java | 5 + .../armeria/client/DefaultRequestOptions.java | 20 ++- .../FutureTransformingRequestPreparation.java | 7 + .../armeria/client/HttpResponseWrapper.java | 4 +- .../armeria/client/RequestOptions.java | 10 ++ .../armeria/client/RequestOptionsBuilder.java | 15 +- .../armeria/client/RequestOptionsSetters.java | 8 ++ .../armeria/client/ResponseTimeoutMode.java | 90 ++++++++++++ .../armeria/client/RestClientBuilder.java | 5 + .../armeria/client/RestClientPreparation.java | 6 + .../TransformingRequestPreparation.java | 6 + .../armeria/client/WebClientBuilder.java | 5 + .../client/WebClientRequestPreparation.java | 11 ++ .../websocket/WebSocketClientBuilder.java | 6 + .../armeria/common/DefaultFlagsProvider.java | 7 + .../com/linecorp/armeria/common/Flags.java | 19 +++ .../armeria/common/FlagsProvider.java | 17 +++ .../common/SystemPropertyFlagsProvider.java | 7 + .../client/DefaultClientRequestContext.java | 27 +++- .../armeria/client/TimeoutModeTest.java | 136 ++++++++++++++++++ .../eureka/EurekaEndpointGroupBuilder.java | 6 + .../eureka/EurekaUpdatingListenerBuilder.java | 6 + .../client/grpc/GrpcClientBuilder.java | 6 + .../retrofit2/ArmeriaRetrofitBuilder.java | 6 + .../scala/ScalaRestClientPreparation.scala | 6 + .../client/thrift/ThriftClientBuilder.java | 6 + 33 files changed, 493 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/com/linecorp/armeria/client/ResponseTimeoutMode.java create mode 100644 core/src/test/java/com/linecorp/armeria/client/TimeoutModeTest.java diff --git a/core/src/main/java/com/linecorp/armeria/client/AbstractClientOptionsBuilder.java b/core/src/main/java/com/linecorp/armeria/client/AbstractClientOptionsBuilder.java index 0c10f80b531..ac8d7cd7709 100644 --- a/core/src/main/java/com/linecorp/armeria/client/AbstractClientOptionsBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/AbstractClientOptionsBuilder.java @@ -506,6 +506,18 @@ public AbstractClientOptionsBuilder contextCustomizer( return this; } + /** + * Sets the {@link ResponseTimeoutMode} which determines when a {@link #responseTimeout(Duration)}} + * will start to be scheduled. + * + * @see ResponseTimeoutMode + */ + @UnstableApi + public AbstractClientOptionsBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return option(ClientOptions.RESPONSE_TIMEOUT_MODE, + requireNonNull(responseTimeoutMode, "responseTimeoutMode")); + } + /** * Builds {@link ClientOptions} with the given options and the * {@linkplain ClientOptions#of() default options}. diff --git a/core/src/main/java/com/linecorp/armeria/client/AbstractHttpRequestHandler.java b/core/src/main/java/com/linecorp/armeria/client/AbstractHttpRequestHandler.java index 5aa065e7706..4785a06296e 100644 --- a/core/src/main/java/com/linecorp/armeria/client/AbstractHttpRequestHandler.java +++ b/core/src/main/java/com/linecorp/armeria/client/AbstractHttpRequestHandler.java @@ -198,6 +198,9 @@ final boolean tryInitialize() { final CancellationScheduler scheduler = cancellationScheduler(); if (scheduler != null) { scheduler.updateTask(newCancellationTask()); + if (ctx.responseTimeoutMode() == ResponseTimeoutMode.CONNECTION_ACQUIRED) { + scheduler.start(); + } } if (ctx.isCancelled()) { // The previous cancellation task wraps the cause with an UnprocessedRequestException diff --git a/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java index 7b1c05f54c3..d876ac8c6a6 100644 --- a/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/BlockingWebClientRequestPreparation.java @@ -223,6 +223,12 @@ public BlockingWebClientRequestPreparation exchangeType(ExchangeType exchangeTyp return this; } + @Override + public BlockingWebClientRequestPreparation responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + delegate.responseTimeoutMode(responseTimeoutMode); + return this; + } + @Override public BlockingWebClientRequestPreparation requestOptions(RequestOptions requestOptions) { delegate.requestOptions(requestOptions); diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientBuilder.java b/core/src/main/java/com/linecorp/armeria/client/ClientBuilder.java index caa4fb860c8..47db606eac5 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientBuilder.java @@ -295,4 +295,9 @@ public ClientBuilder contextCustomizer( public ClientBuilder contextHook(Supplier contextHook) { return (ClientBuilder) super.contextHook(contextHook); } + + @Override + public ClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (ClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientOptions.java b/core/src/main/java/com/linecorp/armeria/client/ClientOptions.java index 20fc04b9fb6..2975cff4eb4 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientOptions.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientOptions.java @@ -156,6 +156,10 @@ public final class ClientOptions public static final ClientOption> CONTEXT_HOOK = ClientOption.define("CONTEXT_HOOK", NOOP_CONTEXT_HOOK); + @UnstableApi + public static final ClientOption RESPONSE_TIMEOUT_MODE = + ClientOption.define("RESPONSE_TIMEOUT_MODE", Flags.responseTimeoutMode()); + private static final List PROHIBITED_HEADER_NAMES = ImmutableList.of( HttpHeaderNames.HTTP2_SETTINGS, HttpHeaderNames.METHOD, @@ -395,6 +399,17 @@ public Supplier contextHook() { return (Supplier) get(CONTEXT_HOOK); } + /** + * Returns the {@link ResponseTimeoutMode} which determines when a {@link #responseTimeoutMillis()} + * will start to be scheduled. + * + * @see ResponseTimeoutMode + */ + @UnstableApi + public ResponseTimeoutMode responseTimeoutMode() { + return get(RESPONSE_TIMEOUT_MODE); + } + /** * Returns a new {@link ClientOptionsBuilder} created from this {@link ClientOptions}. */ diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientOptionsBuilder.java b/core/src/main/java/com/linecorp/armeria/client/ClientOptionsBuilder.java index fa19ec265e3..70fa0cc947a 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientOptionsBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientOptionsBuilder.java @@ -222,4 +222,9 @@ public ClientOptionsBuilder contextCustomizer( public ClientOptionsBuilder contextHook(Supplier contextHook) { return (ClientOptionsBuilder) super.contextHook(contextHook); } + + @Override + public ClientOptionsBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (ClientOptionsBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientRequestContext.java b/core/src/main/java/com/linecorp/armeria/client/ClientRequestContext.java index 4aea2c21933..017197f2bde 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientRequestContext.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientRequestContext.java @@ -612,6 +612,15 @@ default boolean isTimedOut() { @UnstableApi ExchangeType exchangeType(); + /** + * Returns the {@link ResponseTimeoutMode} which determines when a {@link #responseTimeoutMillis()} + * will start to be scheduled. + * + * @see ResponseTimeoutMode + */ + @UnstableApi + ResponseTimeoutMode responseTimeoutMode(); + @Override default ClientRequestContext unwrap() { return (ClientRequestContext) RequestContext.super.unwrap(); diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientRequestContextWrapper.java b/core/src/main/java/com/linecorp/armeria/client/ClientRequestContextWrapper.java index 6e411d99fb0..f03e0a15de3 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientRequestContextWrapper.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientRequestContextWrapper.java @@ -167,6 +167,11 @@ public ExchangeType exchangeType() { return unwrap().exchangeType(); } + @Override + public ResponseTimeoutMode responseTimeoutMode() { + return unwrap().responseTimeoutMode(); + } + @Override public void hook(Supplier contextHook) { unwrap().hook(contextHook); diff --git a/core/src/main/java/com/linecorp/armeria/client/DefaultRequestOptions.java b/core/src/main/java/com/linecorp/armeria/client/DefaultRequestOptions.java index 0a649c251cf..c7564e2429d 100644 --- a/core/src/main/java/com/linecorp/armeria/client/DefaultRequestOptions.java +++ b/core/src/main/java/com/linecorp/armeria/client/DefaultRequestOptions.java @@ -30,7 +30,7 @@ final class DefaultRequestOptions implements RequestOptions { static final DefaultRequestOptions EMPTY = new DefaultRequestOptions(-1, -1, -1, null, - ImmutableMap.of(), null); + ImmutableMap.of(), null, null); private final long responseTimeoutMillis; private final long writeTimeoutMillis; @@ -40,17 +40,21 @@ final class DefaultRequestOptions implements RequestOptions { private final Map, Object> attributeMap; @Nullable private final ExchangeType exchangeType; + @Nullable + private final ResponseTimeoutMode responseTimeoutMode; DefaultRequestOptions(long responseTimeoutMillis, long writeTimeoutMillis, long maxResponseLength, @Nullable Long requestAutoAbortDelayMillis, Map, Object> attributeMap, - @Nullable ExchangeType exchangeType) { + @Nullable ExchangeType exchangeType, + @Nullable ResponseTimeoutMode responseTimeoutMode) { this.responseTimeoutMillis = responseTimeoutMillis; this.writeTimeoutMillis = writeTimeoutMillis; this.maxResponseLength = maxResponseLength; this.requestAutoAbortDelayMillis = requestAutoAbortDelayMillis; this.attributeMap = attributeMap; this.exchangeType = exchangeType; + this.responseTimeoutMode = responseTimeoutMode; } @Override @@ -85,6 +89,12 @@ public ExchangeType exchangeType() { return exchangeType; } + @Nullable + @Override + public ResponseTimeoutMode responseTimeoutMode() { + return responseTimeoutMode; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -101,13 +111,14 @@ public boolean equals(Object o) { writeTimeoutMillis == that.writeTimeoutMillis && maxResponseLength == that.maxResponseLength && attributeMap.equals(that.attributeMap) && - exchangeType == that.exchangeType; + exchangeType == that.exchangeType && + responseTimeoutMode == that.responseTimeoutMode; } @Override public int hashCode() { return Objects.hash(responseTimeoutMillis, writeTimeoutMillis, maxResponseLength, - attributeMap, exchangeType); + attributeMap, exchangeType, responseTimeoutMode); } @Override @@ -119,6 +130,7 @@ public String toString() { .add("requestAutoAbortDelayMillis", requestAutoAbortDelayMillis) .add("attributeMap", attributeMap) .add("exchangeType", exchangeType) + .add("responseTimeoutMode", responseTimeoutMode) .toString(); } } diff --git a/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java index b2bc3e9bca2..2fdce65350d 100644 --- a/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/FutureTransformingRequestPreparation.java @@ -416,4 +416,11 @@ public FutureTransformingRequestPreparation exchangeType(ExchangeType exchang delegate.exchangeType(exchangeType); return this; } + + @Override + public FutureTransformingRequestPreparation responseTimeoutMode( + ResponseTimeoutMode responseTimeoutMode) { + delegate.responseTimeoutMode(responseTimeoutMode); + return this; + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/HttpResponseWrapper.java b/core/src/main/java/com/linecorp/armeria/client/HttpResponseWrapper.java index a6a7e7a3e3d..27aafaabd9c 100644 --- a/core/src/main/java/com/linecorp/armeria/client/HttpResponseWrapper.java +++ b/core/src/main/java/com/linecorp/armeria/client/HttpResponseWrapper.java @@ -294,7 +294,9 @@ void initTimeout() { final CancellationScheduler responseCancellationScheduler = ctxExtension.responseCancellationScheduler(); responseCancellationScheduler.updateTask(newCancellationTask()); - responseCancellationScheduler.start(); + if (ctx.responseTimeoutMode() == ResponseTimeoutMode.REQUEST_SENT) { + responseCancellationScheduler.start(); + } } } diff --git a/core/src/main/java/com/linecorp/armeria/client/RequestOptions.java b/core/src/main/java/com/linecorp/armeria/client/RequestOptions.java index d0baeaa21d2..500954aa0dd 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RequestOptions.java +++ b/core/src/main/java/com/linecorp/armeria/client/RequestOptions.java @@ -112,4 +112,14 @@ default RequestOptionsBuilder toBuilder() { */ @Nullable ExchangeType exchangeType(); + + /** + * Returns the {@link ResponseTimeoutMode} which determines when a {@link #responseTimeoutMillis()} + * will start to be scheduled. + * + * @see ResponseTimeoutMode + */ + @Nullable + @UnstableApi + ResponseTimeoutMode responseTimeoutMode(); } diff --git a/core/src/main/java/com/linecorp/armeria/client/RequestOptionsBuilder.java b/core/src/main/java/com/linecorp/armeria/client/RequestOptionsBuilder.java index eb4f2bf1843..087bd7b8441 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RequestOptionsBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/RequestOptionsBuilder.java @@ -28,6 +28,7 @@ import com.linecorp.armeria.common.ExchangeType; import com.linecorp.armeria.common.annotation.Nullable; +import com.linecorp.armeria.common.annotation.UnstableApi; import io.netty.util.AttributeKey; @@ -41,6 +42,8 @@ public final class RequestOptionsBuilder implements RequestOptionsSetters { private long maxResponseLength = -1; @Nullable private Long requestAutoAbortDelayMillis; + @Nullable + private ResponseTimeoutMode responseTimeoutMode; @Nullable private Map, Object> attributes; @@ -58,6 +61,7 @@ public final class RequestOptionsBuilder implements RequestOptionsSetters { attributes = new HashMap<>(attrs); } exchangeType = options.exchangeType(); + responseTimeoutMode = options.responseTimeoutMode(); } } @@ -135,13 +139,20 @@ public RequestOptionsBuilder exchangeType(ExchangeType exchangeType) { return this; } + @Override + @UnstableApi + public RequestOptionsBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + this.responseTimeoutMode = requireNonNull(responseTimeoutMode, "responseTimeoutMode"); + return this; + } + /** * Returns a newly created {@link RequestOptions} with the properties specified so far. */ public RequestOptions build() { if (responseTimeoutMillis < 0 && writeTimeoutMillis < 0 && maxResponseLength < 0 && requestAutoAbortDelayMillis == null && attributes == null && - exchangeType == null) { + exchangeType == null && responseTimeoutMode == null) { return EMPTY; } else { final Map, Object> attributes; @@ -152,7 +163,7 @@ public RequestOptions build() { } return new DefaultRequestOptions(responseTimeoutMillis, writeTimeoutMillis, maxResponseLength, requestAutoAbortDelayMillis, - attributes, exchangeType); + attributes, exchangeType, responseTimeoutMode); } } } diff --git a/core/src/main/java/com/linecorp/armeria/client/RequestOptionsSetters.java b/core/src/main/java/com/linecorp/armeria/client/RequestOptionsSetters.java index b6c707b0c84..434c105a858 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RequestOptionsSetters.java +++ b/core/src/main/java/com/linecorp/armeria/client/RequestOptionsSetters.java @@ -157,4 +157,12 @@ public interface RequestOptionsSetters { */ @UnstableApi RequestOptionsSetters exchangeType(ExchangeType exchangeType); + + /** + * Sets the {@link ResponseTimeoutMode} which determines when a {@link #responseTimeout(Duration)}} + * will start to be scheduled. + * + * @see ResponseTimeoutMode + */ + RequestOptionsSetters responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode); } diff --git a/core/src/main/java/com/linecorp/armeria/client/ResponseTimeoutMode.java b/core/src/main/java/com/linecorp/armeria/client/ResponseTimeoutMode.java new file mode 100644 index 00000000000..a0a295d6497 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/ResponseTimeoutMode.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you 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: + * + * https://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 com.linecorp.armeria.client; + +import java.time.Duration; + +import com.linecorp.armeria.common.annotation.UnstableApi; +import com.linecorp.armeria.common.util.TimeoutMode; + +/** + * Specifies when to start scheduling a response timeout. + * + *

For example: + *

{@code
+ * |---request start---decorators(3s)---connection acquisition(2s)---request write(5s)---response read(4s)---|
+ * }
+ *
    + *
  • + * If {@link ResponseTimeoutMode#FROM_START} is used, the timeout task will be scheduled + * immediately on request start. If the responseTimeout is less than 3 seconds, the timeout task + * will trigger while the request goes through the decorator, and the request will fail before + * acquiring a new connection. If the responseTimeout is greater than (3s + 2s + 5s + 4s) 14 seconds + * the request will complete successfully. + *
  • + *
  • + * If {@link ResponseTimeoutMode#CONNECTION_ACQUIRED} is used, the timeout task will be scheduled + * after connection acquisition. If the responseTimeout is greater than (5s + 4s) 9 seconds the + * request will complete successfully. + *
  • + *
  • + * If {@link ResponseTimeoutMode#REQUEST_SENT} is used, the the timeout task will be scheduled after + * the request is fully written. If the responseTimeout is greater than 4 seconds the request will + * complete successfully. + *
  • + *
+ * + *

When {@link TimeoutMode#SET_FROM_NOW} is used, the timeout is assumed to be scheduled when + * {@link ClientRequestContext#setResponseTimeout(TimeoutMode, Duration)} was called. If the request + * has already reached {@link ResponseTimeoutMode}, then the timeout is scheduled normally. + * If the request didn't reach {@link ResponseTimeoutMode} yet, the elapsed time is computed once + * {@link ResponseTimeoutMode} is reached and the timeout is scheduled accordingly. + *

{@code
+ * |---request start---decorators(3s)---connection acquisition(2s)---request write(5s)---response read(4s)---|
+ * }
+ * Assume {@link ResponseTimeoutMode#FROM_START} is set, and {@link TimeoutMode#SET_FROM_NOW} + * with 1 second is called in the decorators. Then the timeout task will be triggered 1 second into connection + * acquisition. + *
{@code
+ * |---request start---decorators(3s)---connection acquisition(2s)---request write(5s)---response read(4s)---|
+ * }
+ * Assume {@link ResponseTimeoutMode#REQUEST_SENT} is set, and {@link TimeoutMode#SET_FROM_NOW} + * with 1 second is called in the decorators. The request will continue until the request is fully sent. + * Since (2s + 5s) 7 seconds have elapsed which is greater than the 1-second timeout, the timeout task will be + * invoked immediately before the response read starts. + */ +@UnstableApi +public enum ResponseTimeoutMode { + + /** + * The response timeout is scheduled when the request first starts to execute. More specifically, + * the scheduling will take place as soon as an endpoint is acquired but before the decorator chain + * is traversed. + */ + FROM_START, + + /** + * The response timeout is scheduled after the connection is acquired. + */ + CONNECTION_ACQUIRED, + + /** + * The response timeout is scheduled either after the client fully writes the request + * or when the response bytes are first read. + */ + REQUEST_SENT, +} diff --git a/core/src/main/java/com/linecorp/armeria/client/RestClientBuilder.java b/core/src/main/java/com/linecorp/armeria/client/RestClientBuilder.java index ee677a73788..c577104c969 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RestClientBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/RestClientBuilder.java @@ -254,4 +254,9 @@ public RestClientBuilder contextCustomizer( public RestClientBuilder contextHook(Supplier contextHook) { return (RestClientBuilder) super.contextHook(contextHook); } + + @Override + public RestClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (RestClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java b/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java index 86aea236e12..f913b070436 100644 --- a/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/RestClientPreparation.java @@ -311,4 +311,10 @@ public RestClientPreparation requestOptions(RequestOptions requestOptions) { delegate.requestOptions(requestOptions); return this; } + + @Override + public RestClientPreparation responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + delegate.responseTimeoutMode(responseTimeoutMode); + return this; + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java index 852131f6a5d..535fc1c0e16 100644 --- a/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/TransformingRequestPreparation.java @@ -79,6 +79,12 @@ public TransformingRequestPreparation exchangeType(ExchangeType exchangeTy return this; } + @Override + public TransformingRequestPreparation responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + delegate.responseTimeoutMode(responseTimeoutMode); + return this; + } + @Override public TransformingRequestPreparation requestOptions(RequestOptions requestOptions) { delegate.requestOptions(requestOptions); diff --git a/core/src/main/java/com/linecorp/armeria/client/WebClientBuilder.java b/core/src/main/java/com/linecorp/armeria/client/WebClientBuilder.java index e5f279a6fd9..f6d1d3f656f 100644 --- a/core/src/main/java/com/linecorp/armeria/client/WebClientBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/WebClientBuilder.java @@ -250,4 +250,9 @@ public WebClientBuilder contextCustomizer( Consumer contextCustomizer) { return (WebClientBuilder) super.contextCustomizer(contextCustomizer); } + + @Override + public WebClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (WebClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java b/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java index e10a89515d2..a76d8008569 100644 --- a/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java +++ b/core/src/main/java/com/linecorp/armeria/client/WebClientRequestPreparation.java @@ -329,6 +329,11 @@ public WebClientRequestPreparation requestOptions(RequestOptions requestOptions) if (exchangeType != null) { exchangeType(exchangeType); } + + final ResponseTimeoutMode responseTimeoutMode = requestOptions.responseTimeoutMode(); + if (responseTimeoutMode != null) { + responseTimeoutMode(responseTimeoutMode); + } return this; } @@ -392,6 +397,12 @@ public WebClientRequestPreparation exchangeType(ExchangeType exchangeType) { return this; } + @Override + public WebClientRequestPreparation responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + requestOptionsBuilder().responseTimeoutMode(responseTimeoutMode); + return this; + } + private RequestOptionsBuilder requestOptionsBuilder() { if (requestOptionsBuilder == null) { requestOptionsBuilder = RequestOptions.builder(); diff --git a/core/src/main/java/com/linecorp/armeria/client/websocket/WebSocketClientBuilder.java b/core/src/main/java/com/linecorp/armeria/client/websocket/WebSocketClientBuilder.java index 8f895151658..1109efe8477 100644 --- a/core/src/main/java/com/linecorp/armeria/client/websocket/WebSocketClientBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/websocket/WebSocketClientBuilder.java @@ -45,6 +45,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.client.endpoint.EndpointGroup; @@ -390,4 +391,9 @@ public WebSocketClientBuilder contextCustomizer( public WebSocketClientBuilder contextHook(Supplier contextHook) { return (WebSocketClientBuilder) super.contextHook(contextHook); } + + @Override + public WebSocketClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (WebSocketClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java index 2d4acd9f64f..c49aa2b209a 100644 --- a/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/DefaultFlagsProvider.java @@ -26,6 +26,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.common.util.Sampler; import com.linecorp.armeria.common.util.TlsEngineType; import com.linecorp.armeria.common.util.TransportType; @@ -100,6 +101,7 @@ final class DefaultFlagsProvider implements FlagsProvider { static final String DNS_CACHE_SPEC = "maximumSize=4096"; static final long DEFAULT_UNLOGGED_EXCEPTIONS_REPORT_INTERVAL_MILLIS = 10000; static final long DEFAULT_HTTP1_CONNECTION_CLOSE_DELAY_MILLIS = 3000; + static final ResponseTimeoutMode DEFAULT_RESPONSE_TIMEOUT_MODE = ResponseTimeoutMode.REQUEST_SENT; private DefaultFlagsProvider() {} @@ -511,4 +513,9 @@ public DistributionStatisticConfig distributionStatisticConfig() { public Long defaultHttp1ConnectionCloseDelayMillis() { return DEFAULT_HTTP1_CONNECTION_CLOSE_DELAY_MILLIS; } + + @Override + public ResponseTimeoutMode responseTimeoutMode() { + return DEFAULT_RESPONSE_TIMEOUT_MODE; + } } diff --git a/core/src/main/java/com/linecorp/armeria/common/Flags.java b/core/src/main/java/com/linecorp/armeria/common/Flags.java index fff4fd96d7b..b3327c7d06d 100644 --- a/core/src/main/java/com/linecorp/armeria/common/Flags.java +++ b/core/src/main/java/com/linecorp/armeria/common/Flags.java @@ -46,6 +46,7 @@ import com.linecorp.armeria.client.ClientFactoryBuilder; import com.linecorp.armeria.client.DnsResolverGroupBuilder; import com.linecorp.armeria.client.Endpoint; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.retry.Backoff; import com.linecorp.armeria.client.retry.RetryingClient; import com.linecorp.armeria.client.retry.RetryingRpcClient; @@ -439,6 +440,9 @@ private static boolean validateTransportType(TransportType transportType, String getValue(FlagsProvider::defaultHttp1ConnectionCloseDelayMillis, "defaultHttp1ConnectionCloseDelayMillis", value -> value >= 0); + private static final ResponseTimeoutMode RESPONSE_TIMEOUT_MODE = + getValue(FlagsProvider::responseTimeoutMode, "responseTimeoutMode"); + /** * Returns the specification of the {@link Sampler} that determines whether to retain the stack * trace of the exceptions that are thrown frequently by Armeria. A sampled exception will have the stack @@ -1642,6 +1646,21 @@ public static long defaultHttp1ConnectionCloseDelayMillis() { return DEFAULT_HTTP1_CONNECTION_CLOSE_DELAY_MILLIS; } + /** + * Returns the {@link ResponseTimeoutMode} which determines when a response timeout + * will start to be scheduled. + * + *

The default value of this flag is {@link ResponseTimeoutMode#REQUEST_SENT}. Specify the + * {@code -Dcom.linecorp.armeria.responseTimeoutMode=ResponseTimeoutMode} JVM option to + * override the default value. + * + * @see ResponseTimeoutMode + */ + @UnstableApi + public static ResponseTimeoutMode responseTimeoutMode() { + return RESPONSE_TIMEOUT_MODE; + } + @Nullable private static String nullableCaffeineSpec(Function method, String flagName) { return caffeineSpec(method, flagName, true); diff --git a/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java index 72dfb4f8d6e..3503737bb93 100644 --- a/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/FlagsProvider.java @@ -34,6 +34,7 @@ import com.linecorp.armeria.client.ClientBuilder; import com.linecorp.armeria.client.ClientFactoryBuilder; import com.linecorp.armeria.client.DnsResolverGroupBuilder; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.retry.Backoff; import com.linecorp.armeria.client.retry.RetryingClient; import com.linecorp.armeria.client.retry.RetryingRpcClient; @@ -1232,4 +1233,20 @@ default DistributionStatisticConfig distributionStatisticConfig() { default Long defaultHttp1ConnectionCloseDelayMillis() { return null; } + + /** + * Returns the {@link ResponseTimeoutMode} which determines when a response timeout + * will start to be scheduled. + * + *

The default value of this flag is {@link ResponseTimeoutMode#REQUEST_SENT}. Specify the + * {@code -Dcom.linecorp.armeria.responseTimeoutMode=ResponseTimeoutMode} JVM option to + * override the default value. + * + * @see ResponseTimeoutMode + */ + @Nullable + @UnstableApi + default ResponseTimeoutMode responseTimeoutMode() { + return null; + } } diff --git a/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java b/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java index 2ea4026abdc..4263e19773e 100644 --- a/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java +++ b/core/src/main/java/com/linecorp/armeria/common/SystemPropertyFlagsProvider.java @@ -36,6 +36,7 @@ import com.google.common.collect.Sets; import com.google.common.collect.Streams; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.common.annotation.Nullable; import com.linecorp.armeria.common.util.InetAddressPredicates; import com.linecorp.armeria.common.util.Sampler; @@ -598,6 +599,12 @@ public Long defaultUnloggedExceptionsReportIntervalMillis() { return getLong("defaultUnloggedExceptionsReportIntervalMillis"); } + @Override + @Nullable + public ResponseTimeoutMode responseTimeoutMode() { + return getAndParse("responseTimeoutMode", ResponseTimeoutMode::valueOf); + } + @Nullable private static Long getLong(String name) { return getAndParse(name, Long::parseLong); diff --git a/core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java b/core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java index f8dc94c529b..9a6490c8866 100644 --- a/core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java +++ b/core/src/main/java/com/linecorp/armeria/internal/client/DefaultClientRequestContext.java @@ -40,6 +40,7 @@ import com.linecorp.armeria.client.ClientRequestContext; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.RequestOptions; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.UnprocessedRequestException; import com.linecorp.armeria.client.endpoint.EndpointGroup; import com.linecorp.armeria.common.AttributesGetters; @@ -168,6 +169,8 @@ private static SessionProtocol desiredSessionProtocol(SessionProtocol protocol, @Nullable private volatile CompletableFuture whenInitialized; + private final ResponseTimeoutMode responseTimeoutMode; + /** * Creates a new instance. Note that {@link #init(EndpointGroup)} method must be invoked to finish * the construction of this context. @@ -279,6 +282,7 @@ private DefaultClientRequestContext( } else { this.customizer = customizer.andThen(threadLocalCustomizer); } + responseTimeoutMode = responseTimeoutMode(options, requestOptions); } private static ExchangeType guessExchangeType(RequestOptions requestOptions, @Nullable HttpRequest req) { @@ -541,6 +545,7 @@ private DefaultClientRequestContext(DefaultClientRequestContext ctx, defaultRequestHeaders = ctx.defaultRequestHeaders(); additionalRequestHeaders = ctx.additionalRequestHeaders(); + responseTimeoutMode = ctx.responseTimeoutMode(); for (final Iterator, Object>> i = ctx.ownAttrs(); i.hasNext();) { addAttr(i.next()); @@ -569,8 +574,12 @@ private void initializeResponseCancellationScheduler() { log.endResponse(cause); } }; - responseCancellationScheduler.init(eventLoop().withoutContext()); - responseCancellationScheduler.updateTask(cancellationTask); + if (responseTimeoutMode() == ResponseTimeoutMode.FROM_START) { + responseCancellationScheduler.initAndStart(eventLoop().withoutContext(), cancellationTask); + } else { + responseCancellationScheduler.init(eventLoop().withoutContext()); + responseCancellationScheduler.updateTask(cancellationTask); + } } @Nullable @@ -1053,4 +1062,18 @@ public CompletableFuture initiateConnectionShutdown() { }); return completableFuture; } + + @Override + public ResponseTimeoutMode responseTimeoutMode() { + return responseTimeoutMode; + } + + private static ResponseTimeoutMode responseTimeoutMode(ClientOptions options, + RequestOptions requestOptions) { + final ResponseTimeoutMode requestOptionTimeoutMode = requestOptions.responseTimeoutMode(); + if (requestOptionTimeoutMode != null) { + return requestOptionTimeoutMode; + } + return options.responseTimeoutMode(); + } } diff --git a/core/src/test/java/com/linecorp/armeria/client/TimeoutModeTest.java b/core/src/test/java/com/linecorp/armeria/client/TimeoutModeTest.java new file mode 100644 index 00000000000..094474dece9 --- /dev/null +++ b/core/src/test/java/com/linecorp/armeria/client/TimeoutModeTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you 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: + * + * https://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 com.linecorp.armeria.client; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.linecorp.armeria.common.CommonPools; +import com.linecorp.armeria.common.HttpMethod; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpRequestWriter; +import com.linecorp.armeria.common.HttpResponse; +import com.linecorp.armeria.server.ServerBuilder; +import com.linecorp.armeria.testing.junit5.server.ServerExtension; + +class TimeoutModeTest { + + @RegisterExtension + static ServerExtension server = new ServerExtension() { + @Override + protected void configure(ServerBuilder sb) throws Exception { + sb.service("/", (ctx, req) -> HttpResponse.streaming()); + } + }; + + @Test + void timeoutMode_requestStart() { + final HttpResponse res = server + .webClient(cb -> { + cb.responseTimeoutMode(ResponseTimeoutMode.FROM_START); + cb.responseTimeoutMillis(50); + cb.decorator((delegate, ctx, req) -> { + final CompletableFuture f = new CompletableFuture<>(); + CommonPools.workerGroup().schedule(() -> f.complete(delegate.execute(ctx, req)), + 100, TimeUnit.MILLISECONDS); + return HttpResponse.of(f); + }); + }) + .get("/"); + assertThatThrownBy(() -> res.aggregate().join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(UnprocessedRequestException.class) + .hasRootCauseInstanceOf(ResponseTimeoutException.class); + } + + @Test + void timeoutMode_requestWrite() { + final HttpRequestWriter streaming = HttpRequest.streaming(HttpMethod.POST, "/"); + final HttpResponse res = server + .webClient(cb -> { + cb.responseTimeoutMode(ResponseTimeoutMode.CONNECTION_ACQUIRED); + cb.responseTimeout(Duration.ofMillis(50)); + }) + .execute(streaming); + assertThatThrownBy(() -> res.aggregate().join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(ResponseTimeoutException.class); + } + + @Test + void timeoutMode_responseWrite() { + final HttpResponse res = server + .webClient(cb -> { + cb.responseTimeoutMode(ResponseTimeoutMode.REQUEST_SENT); + cb.responseTimeout(Duration.ofMillis(50)); + }) + .get("/"); + assertThatThrownBy(() -> res.aggregate().join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(ResponseTimeoutException.class); + } + + @Test + void timeoutMode_requestOptions() { + final HttpResponse res = server + .webClient(cb -> { + cb.responseTimeoutMode(ResponseTimeoutMode.REQUEST_SENT); + cb.responseTimeoutMillis(50); + cb.decorator((delegate, ctx, req) -> { + final CompletableFuture f = new CompletableFuture<>(); + CommonPools.workerGroup().schedule(() -> f.complete(delegate.execute(ctx, req)), + 100, TimeUnit.MILLISECONDS); + return HttpResponse.of(f); + }); + }) + .execute(HttpRequest.of(HttpMethod.GET, "/"), + RequestOptions.builder().responseTimeoutMode(ResponseTimeoutMode.FROM_START).build()); + assertThatThrownBy(() -> res.aggregate().join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(UnprocessedRequestException.class) + .hasRootCauseInstanceOf(ResponseTimeoutException.class); + } + + @Test + void timeoutMode_transforming() { + final HttpResponse res = server + .webClient(cb -> { + cb.responseTimeoutMode(ResponseTimeoutMode.REQUEST_SENT); + cb.responseTimeoutMillis(50); + cb.decorator((delegate, ctx, req) -> { + final CompletableFuture f = new CompletableFuture<>(); + CommonPools.workerGroup().schedule(() -> f.complete(delegate.execute(ctx, req)), + 100, TimeUnit.MILLISECONDS); + return HttpResponse.of(f); + }); + }) + .prepare() + .responseTimeoutMode(ResponseTimeoutMode.FROM_START) + .get("/").execute(); + assertThatThrownBy(() -> res.aggregate().join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(UnprocessedRequestException.class) + .hasRootCauseInstanceOf(ResponseTimeoutException.class); + } +} diff --git a/eureka/src/main/java/com/linecorp/armeria/client/eureka/EurekaEndpointGroupBuilder.java b/eureka/src/main/java/com/linecorp/armeria/client/eureka/EurekaEndpointGroupBuilder.java index 1013f83bdd2..26857db0c01 100644 --- a/eureka/src/main/java/com/linecorp/armeria/client/eureka/EurekaEndpointGroupBuilder.java +++ b/eureka/src/main/java/com/linecorp/armeria/client/eureka/EurekaEndpointGroupBuilder.java @@ -42,6 +42,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.client.endpoint.AbstractDynamicEndpointGroupBuilder; @@ -432,6 +433,11 @@ public EurekaEndpointGroupBuilder contextCustomizer( return (EurekaEndpointGroupBuilder) super.contextCustomizer(contextCustomizer); } + @Override + public EurekaEndpointGroupBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (EurekaEndpointGroupBuilder) super.responseTimeoutMode(responseTimeoutMode); + } + @Override public EurekaEndpointGroupBuilder allowEmptyEndpoints(boolean allowEmptyEndpoints) { dynamicEndpointGroupBuilder.allowEmptyEndpoints(allowEmptyEndpoints); diff --git a/eureka/src/main/java/com/linecorp/armeria/server/eureka/EurekaUpdatingListenerBuilder.java b/eureka/src/main/java/com/linecorp/armeria/server/eureka/EurekaUpdatingListenerBuilder.java index 5d684bdaed5..9b48c079eb7 100644 --- a/eureka/src/main/java/com/linecorp/armeria/server/eureka/EurekaUpdatingListenerBuilder.java +++ b/eureka/src/main/java/com/linecorp/armeria/server/eureka/EurekaUpdatingListenerBuilder.java @@ -41,6 +41,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.client.endpoint.EndpointGroup; @@ -551,4 +552,9 @@ public EurekaUpdatingListenerBuilder contextCustomizer( Consumer contextCustomizer) { return (EurekaUpdatingListenerBuilder) super.contextCustomizer(contextCustomizer); } + + @Override + public EurekaUpdatingListenerBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (EurekaUpdatingListenerBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/grpc/src/main/java/com/linecorp/armeria/client/grpc/GrpcClientBuilder.java b/grpc/src/main/java/com/linecorp/armeria/client/grpc/GrpcClientBuilder.java index 7b9c59e5baa..428dcb16be4 100644 --- a/grpc/src/main/java/com/linecorp/armeria/client/grpc/GrpcClientBuilder.java +++ b/grpc/src/main/java/com/linecorp/armeria/client/grpc/GrpcClientBuilder.java @@ -55,6 +55,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.endpoint.EndpointGroup; import com.linecorp.armeria.client.redirect.RedirectConfig; @@ -595,6 +596,11 @@ public GrpcClientBuilder contextCustomizer( return (GrpcClientBuilder) super.contextCustomizer(contextCustomizer); } + @Override + public GrpcClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (GrpcClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } + /** * Sets the specified {@link GrpcExceptionHandlerFunction} that maps a {@link Throwable} * to a gRPC {@link Status}. diff --git a/retrofit2/src/main/java/com/linecorp/armeria/client/retrofit2/ArmeriaRetrofitBuilder.java b/retrofit2/src/main/java/com/linecorp/armeria/client/retrofit2/ArmeriaRetrofitBuilder.java index 2d588ecf697..66d46a84b55 100644 --- a/retrofit2/src/main/java/com/linecorp/armeria/client/retrofit2/ArmeriaRetrofitBuilder.java +++ b/retrofit2/src/main/java/com/linecorp/armeria/client/retrofit2/ArmeriaRetrofitBuilder.java @@ -43,6 +43,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.client.endpoint.EndpointGroup; @@ -450,4 +451,9 @@ public ArmeriaRetrofitBuilder contextCustomizer( Consumer contextCustomizer) { return (ArmeriaRetrofitBuilder) super.contextCustomizer(contextCustomizer); } + + @Override + public ArmeriaRetrofitBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (ArmeriaRetrofitBuilder) super.responseTimeoutMode(responseTimeoutMode); + } } diff --git a/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala b/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala index 4f068d7e93a..b0bf4b37b6f 100644 --- a/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala +++ b/scala/scala_2.13/src/main/scala/com/linecorp/armeria/client/scala/ScalaRestClientPreparation.scala @@ -21,6 +21,7 @@ import com.linecorp.armeria.client.{ RequestOptions, RequestPreparationSetters, ResponseAs, + ResponseTimeoutMode, RestClientPreparation } import com.linecorp.armeria.common.annotation.UnstableApi @@ -262,4 +263,9 @@ final class ScalaRestClientPreparation private[scala] (delegate: RestClientPrepa delegate.cookies(cookies.asJava) this } + + override def responseTimeoutMode(responseTimeoutMode: ResponseTimeoutMode): ScalaRestClientPreparation = { + delegate.responseTimeoutMode(responseTimeoutMode) + this + } } diff --git a/thrift/thrift0.13/src/main/java/com/linecorp/armeria/client/thrift/ThriftClientBuilder.java b/thrift/thrift0.13/src/main/java/com/linecorp/armeria/client/thrift/ThriftClientBuilder.java index 2fc5adf8e0a..7591c0d71aa 100644 --- a/thrift/thrift0.13/src/main/java/com/linecorp/armeria/client/thrift/ThriftClientBuilder.java +++ b/thrift/thrift0.13/src/main/java/com/linecorp/armeria/client/thrift/ThriftClientBuilder.java @@ -42,6 +42,7 @@ import com.linecorp.armeria.client.DecoratingRpcClientFunction; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.HttpClient; +import com.linecorp.armeria.client.ResponseTimeoutMode; import com.linecorp.armeria.client.RpcClient; import com.linecorp.armeria.client.endpoint.EndpointGroup; import com.linecorp.armeria.client.redirect.RedirectConfig; @@ -373,4 +374,9 @@ public ThriftClientBuilder contextCustomizer( Consumer contextCustomizer) { return (ThriftClientBuilder) super.contextCustomizer(contextCustomizer); } + + @Override + public ThriftClientBuilder responseTimeoutMode(ResponseTimeoutMode responseTimeoutMode) { + return (ThriftClientBuilder) super.responseTimeoutMode(responseTimeoutMode); + } }