1 /* 2 * Copyright 2018 Google LLC 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google LLC nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 package com.google.api.gax.retrying; 31 32 import static com.google.common.truth.Truth.assertThat; 33 import static org.junit.Assert.assertEquals; 34 import static org.junit.Assert.assertFalse; 35 import static org.junit.Assert.assertTrue; 36 37 import com.google.api.gax.core.FakeApiClock; 38 import com.google.api.gax.rpc.testing.FakeCallContext; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 import org.junit.runners.JUnit4; 42 import org.threeten.bp.Duration; 43 44 @RunWith(JUnit4.class) 45 public class ExponentialRetryAlgorithmTest { 46 private final FakeApiClock clock = new FakeApiClock(0L); 47 private final RetrySettings retrySettings = 48 RetrySettings.newBuilder() 49 .setMaxAttempts(6) 50 .setInitialRetryDelay(Duration.ofMillis(1L)) 51 .setRetryDelayMultiplier(2.0) 52 .setMaxRetryDelay(Duration.ofMillis(8L)) 53 .setInitialRpcTimeout(Duration.ofMillis(1L)) 54 .setRpcTimeoutMultiplier(2.0) 55 .setMaxRpcTimeout(Duration.ofMillis(8L)) 56 .setTotalTimeout(Duration.ofMillis(200L)) 57 .build(); 58 private final ExponentialRetryAlgorithm algorithm = 59 new ExponentialRetryAlgorithm(retrySettings, clock); 60 private final RetrySettings retrySettingsOverride = 61 RetrySettings.newBuilder() 62 .setMaxAttempts(3) 63 .setInitialRetryDelay(Duration.ofMillis(2L)) 64 .setRetryDelayMultiplier(3.0) 65 .setMaxRetryDelay(Duration.ofMillis(18L)) 66 .setInitialRpcTimeout(Duration.ofMillis(2L)) 67 .setRpcTimeoutMultiplier(3.0) 68 .setMaxRpcTimeout(Duration.ofMillis(18L)) 69 .setTotalTimeout(Duration.ofMillis(300L)) 70 .build(); 71 private final RetryingContext retryingContext = 72 FakeCallContext.createDefault().withRetrySettings(retrySettingsOverride); 73 74 @Test testCreateFirstAttempt()75 public void testCreateFirstAttempt() { 76 TimedAttemptSettings attempt = algorithm.createFirstAttempt(); 77 78 // Checking only the most core values, to not make this test too implementation specific. 79 assertEquals(0, attempt.getAttemptCount()); 80 assertEquals(0, attempt.getOverallAttemptCount()); 81 assertEquals(Duration.ZERO, attempt.getRetryDelay()); 82 assertEquals(Duration.ZERO, attempt.getRandomizedRetryDelay()); 83 assertEquals(Duration.ofMillis(1L), attempt.getRpcTimeout()); 84 assertEquals(Duration.ZERO, attempt.getRetryDelay()); 85 } 86 87 @Test testCreateFirstAttemptOverride()88 public void testCreateFirstAttemptOverride() { 89 TimedAttemptSettings attempt = algorithm.createFirstAttempt(retryingContext); 90 91 // Checking only the most core values, to not make this test too implementation specific. 92 assertEquals(0, attempt.getAttemptCount()); 93 assertEquals(0, attempt.getOverallAttemptCount()); 94 assertEquals(Duration.ZERO, attempt.getRetryDelay()); 95 assertEquals(Duration.ZERO, attempt.getRandomizedRetryDelay()); 96 assertEquals(retrySettingsOverride.getInitialRpcTimeout(), attempt.getRpcTimeout()); 97 assertEquals(Duration.ZERO, attempt.getRetryDelay()); 98 } 99 100 @Test testCreateNextAttempt()101 public void testCreateNextAttempt() { 102 TimedAttemptSettings firstAttempt = algorithm.createFirstAttempt(); 103 TimedAttemptSettings secondAttempt = algorithm.createNextAttempt(firstAttempt); 104 105 // Checking only the most core values, to not make this test too implementation specific. 106 assertEquals(1, secondAttempt.getAttemptCount()); 107 assertEquals(1, secondAttempt.getOverallAttemptCount()); 108 assertEquals(Duration.ofMillis(1L), secondAttempt.getRetryDelay()); 109 assertEquals(Duration.ofMillis(2L), secondAttempt.getRpcTimeout()); 110 111 TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); 112 assertEquals(2, thirdAttempt.getAttemptCount()); 113 assertEquals(Duration.ofMillis(2L), thirdAttempt.getRetryDelay()); 114 assertEquals(Duration.ofMillis(4L), thirdAttempt.getRpcTimeout()); 115 } 116 117 @Test testCreateNextAttemptOverride()118 public void testCreateNextAttemptOverride() { 119 TimedAttemptSettings firstAttempt = algorithm.createFirstAttempt(retryingContext); 120 TimedAttemptSettings secondAttempt = algorithm.createNextAttempt(firstAttempt); 121 122 // Checking only the most core values, to not make this test too implementation specific. 123 assertEquals(1, secondAttempt.getAttemptCount()); 124 assertEquals(1, secondAttempt.getOverallAttemptCount()); 125 assertEquals(Duration.ofMillis(2L), secondAttempt.getRetryDelay()); 126 assertEquals(Duration.ofMillis(6L), secondAttempt.getRpcTimeout()); 127 128 TimedAttemptSettings thirdAttempt = algorithm.createNextAttempt(secondAttempt); 129 assertEquals(2, thirdAttempt.getAttemptCount()); 130 assertEquals(Duration.ofMillis(6L), thirdAttempt.getRetryDelay()); 131 assertEquals(Duration.ofMillis(18L), thirdAttempt.getRpcTimeout()); 132 } 133 134 @Test testTruncateToTotalTimeout()135 public void testTruncateToTotalTimeout() { 136 RetrySettings timeoutSettings = 137 retrySettings 138 .toBuilder() 139 .setInitialRpcTimeout(Duration.ofSeconds(4L)) 140 .setMaxRpcTimeout(Duration.ofSeconds(4L)) 141 .setTotalTimeout(Duration.ofSeconds(4L)) 142 .build(); 143 ExponentialRetryAlgorithm timeoutAlg = new ExponentialRetryAlgorithm(timeoutSettings, clock); 144 145 TimedAttemptSettings firstAttempt = timeoutAlg.createFirstAttempt(); 146 TimedAttemptSettings secondAttempt = timeoutAlg.createNextAttempt(firstAttempt); 147 assertThat(secondAttempt.getRpcTimeout()).isAtLeast(firstAttempt.getRpcTimeout()); 148 assertThat(secondAttempt.getRpcTimeout()).isAtMost(Duration.ofSeconds(4L)); 149 150 TimedAttemptSettings thirdAttempt = timeoutAlg.createNextAttempt(secondAttempt); 151 assertThat(thirdAttempt.getRpcTimeout()).isAtMost(Duration.ofSeconds(4L)); 152 } 153 154 @Test testShouldRetryTrue()155 public void testShouldRetryTrue() { 156 TimedAttemptSettings attempt = algorithm.createFirstAttempt(); 157 for (int i = 0; i < 2; i++) { 158 attempt = algorithm.createNextAttempt(attempt); 159 } 160 161 assertTrue(algorithm.shouldRetry(attempt)); 162 } 163 164 @Test testShouldRetryFalseOnMaxAttempts()165 public void testShouldRetryFalseOnMaxAttempts() { 166 TimedAttemptSettings attempt = algorithm.createFirstAttempt(); 167 for (int i = 0; i < 6; i++) { 168 assertTrue(algorithm.shouldRetry(attempt)); 169 attempt = algorithm.createNextAttempt(attempt); 170 } 171 172 assertFalse(algorithm.shouldRetry(attempt)); 173 } 174 175 // First attempt runs at 0ms 176 // Second attempt runs at 60ms if shouldRetry is true 177 // - RPC timeout is 2ms and Time Left is 140ms (shouldRetry == true) 178 // Third attempt runs at 60ms if shouldRetry is true 179 // - RPC timeout is 4ms and Time Left is 120ms (shouldRetry == true) 180 // Fourth attempt runs at 60ms if shouldRetry is true 181 // - RPC timeout is 8ms and Time Left is 20ms (shouldRetry == true) 182 // Fifth attempt runs at 60ms if shouldRetry is true 183 // - RPC timeout is 8ms and Time Left is -40ms (shouldRetry == false) 184 @Test testShouldRetryFalseOnMaxTimeout()185 public void testShouldRetryFalseOnMaxTimeout() { 186 // Simulate each attempt with 60ms of clock time. 187 // "attempt" = RPC Timeout + createNextAttempt() and shouldRetry() 188 TimedAttemptSettings attempt = algorithm.createFirstAttempt(); 189 clock.incrementNanoTime(Duration.ofMillis(60L).toNanos()); 190 for (int i = 0; i < 3; i++) { 191 assertTrue(algorithm.shouldRetry(attempt)); 192 attempt = algorithm.createNextAttempt(attempt); 193 clock.incrementNanoTime(Duration.ofMillis(60L).toNanos()); 194 } 195 assertFalse(algorithm.shouldRetry(attempt)); 196 } 197 } 198