diff --git a/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java b/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java index daa2b31ad..65ca3d061 100644 --- a/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java +++ b/gax-grpc/src/test/java/com/google/api/gax/grpc/SettingsTest.java @@ -256,39 +256,6 @@ public BatchingCallSettings.Builder fakeMethodBatching() { } } - // RetrySettings - // ==== - - @Test - public void retrySettingsMerge() { - RetrySettings.Builder builder = - RetrySettings.newBuilder() - .setTotalTimeout(Duration.ofMillis(45000)) - .setInitialRpcTimeout(Duration.ofMillis(2000)) - .setRpcTimeoutMultiplier(1.5) - .setMaxRpcTimeout(Duration.ofMillis(30000)) - .setInitialRetryDelay(Duration.ofMillis(100)) - .setRetryDelayMultiplier(1.2) - .setMaxRetryDelay(Duration.ofMillis(1000)); - RetrySettings.Builder mergedBuilder = RetrySettings.newBuilder(); - mergedBuilder.merge(builder); - - RetrySettings settingsA = builder.build(); - RetrySettings settingsB = mergedBuilder.build(); - - Truth.assertThat(settingsA.getTotalTimeout()).isEqualTo(settingsB.getTotalTimeout()); - Truth.assertThat(settingsA.getInitialRetryDelay()).isEqualTo(settingsB.getInitialRetryDelay()); - Truth.assertThat(settingsA.getRpcTimeoutMultiplier()) - .isWithin(0) - .of(settingsB.getRpcTimeoutMultiplier()); - Truth.assertThat(settingsA.getMaxRpcTimeout()).isEqualTo(settingsB.getMaxRpcTimeout()); - Truth.assertThat(settingsA.getInitialRetryDelay()).isEqualTo(settingsB.getInitialRetryDelay()); - Truth.assertThat(settingsA.getRetryDelayMultiplier()) - .isWithin(0) - .of(settingsB.getRetryDelayMultiplier()); - Truth.assertThat(settingsA.getMaxRetryDelay()).isEqualTo(settingsB.getMaxRetryDelay()); - } - // GrpcTransportProvider // ==== diff --git a/gax/src/main/java/com/google/api/gax/retrying/RetrySettings.java b/gax/src/main/java/com/google/api/gax/retrying/RetrySettings.java index 2276eb407..3607bf643 100644 --- a/gax/src/main/java/com/google/api/gax/retrying/RetrySettings.java +++ b/gax/src/main/java/com/google/api/gax/retrying/RetrySettings.java @@ -296,6 +296,22 @@ public abstract static class Builder { */ public abstract Duration getMaxRpcTimeout(); + /** + * Configures the timeout settings with the given timeout such that the logical call will take + * no longer than the given timeout and each RPC attempt will use only the time remaining in the + * logical call as a timeout. + * + *

Using this method in conjunction with individual {@link RetrySettings} timeout field + * setters is not advised, because only the order in which they are invoked determines which + * setter is respected. + */ + public Builder setLogicalTimeout(Duration timeout) { + return setRpcTimeoutMultiplier(1) + .setInitialRpcTimeout(timeout) + .setMaxRpcTimeout(timeout) + .setTotalTimeout(timeout); + } + abstract RetrySettings autoBuild(); public RetrySettings build() { diff --git a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java index 38ed1dd4e..a9366fbc5 100644 --- a/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java +++ b/gax/src/main/java/com/google/api/gax/rpc/ApiCallContext.java @@ -191,6 +191,22 @@ public interface ApiCallContext extends RetryingContext { * StatusCode.Code.UNAVAILABLE, * StatusCode.Code.DEADLINE_EXCEEDED)); * } + * + * Setting a logical call timeout for the context can be done similarly with {@link + * RetrySettings.Builder#setLogicalTimeout(Duration timeout)}. + * + *

Example usage: + * + *

{@code
+   * ApiCallContext context = GrpcCallContext.createDefault()
+   *   .withRetrySettings(RetrySettings.newBuilder()
+   *     .setInitialRetryDelay(Duration.ofMillis(10L))
+   *     .setMaxRetryDelay(Duration.ofSeconds(10L))
+   *     .setRetryDelayMultiplier(1.4)
+   *     .setMaxAttempts(10)
+   *     .setLogicalTimeout(Duration.ofSeconds(30L))
+   *     .build());
+   * }
*/ @BetaApi ApiCallContext withRetrySettings(RetrySettings retrySettings); diff --git a/gax/src/test/java/com/google/api/gax/retrying/RetrySettingsTest.java b/gax/src/test/java/com/google/api/gax/retrying/RetrySettingsTest.java new file mode 100644 index 000000000..8bcdea4cc --- /dev/null +++ b/gax/src/test/java/com/google/api/gax/retrying/RetrySettingsTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.gax.retrying; + +import com.google.common.truth.Truth; +import org.junit.Test; +import org.threeten.bp.Duration; + +public class RetrySettingsTest { + + @Test + public void retrySettingsSetLogicalTimeout() { + Duration timeout = Duration.ofMillis(60000); + RetrySettings retrySettings = RetrySettings.newBuilder().setLogicalTimeout(timeout).build(); + + Truth.assertThat(retrySettings.getRpcTimeoutMultiplier()).isEqualTo(1); + Truth.assertThat(retrySettings.getInitialRpcTimeout()).isEqualTo(timeout); + Truth.assertThat(retrySettings.getMaxRpcTimeout()).isEqualTo(timeout); + Truth.assertThat(retrySettings.getTotalTimeout()).isEqualTo(timeout); + } + + @Test + public void retrySettingsMerge() { + RetrySettings.Builder builder = + RetrySettings.newBuilder() + .setTotalTimeout(Duration.ofMillis(45000)) + .setInitialRpcTimeout(Duration.ofMillis(2000)) + .setRpcTimeoutMultiplier(1.5) + .setMaxRpcTimeout(Duration.ofMillis(30000)) + .setInitialRetryDelay(Duration.ofMillis(100)) + .setRetryDelayMultiplier(1.2) + .setMaxRetryDelay(Duration.ofMillis(1000)); + RetrySettings.Builder mergedBuilder = RetrySettings.newBuilder(); + mergedBuilder.merge(builder); + + RetrySettings settingsA = builder.build(); + RetrySettings settingsB = mergedBuilder.build(); + + Truth.assertThat(settingsA.getTotalTimeout()).isEqualTo(settingsB.getTotalTimeout()); + Truth.assertThat(settingsA.getInitialRetryDelay()).isEqualTo(settingsB.getInitialRetryDelay()); + Truth.assertThat(settingsA.getRpcTimeoutMultiplier()) + .isWithin(0) + .of(settingsB.getRpcTimeoutMultiplier()); + Truth.assertThat(settingsA.getMaxRpcTimeout()).isEqualTo(settingsB.getMaxRpcTimeout()); + Truth.assertThat(settingsA.getInitialRetryDelay()).isEqualTo(settingsB.getInitialRetryDelay()); + Truth.assertThat(settingsA.getRetryDelayMultiplier()) + .isWithin(0) + .of(settingsB.getRetryDelayMultiplier()); + Truth.assertThat(settingsA.getMaxRetryDelay()).isEqualTo(settingsB.getMaxRetryDelay()); + } +}