View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.classic;
28  
29  import org.apache.hc.client5.http.HttpRoute;
30  import org.apache.hc.core5.http.HttpHost;
31  import org.apache.hc.core5.util.TimeValue;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  
35  
36  import static org.junit.jupiter.api.Assertions.assertEquals;
37  import static org.junit.jupiter.api.Assertions.assertTrue;
38  
39  import java.time.Instant;
40  import java.util.Map;
41  
42  public class TestExponentialBackoffManager {
43  
44      private ExponentialBackoffManager impl;
45      private MockConnPoolControl connPerRoute;
46      private HttpRoute route;
47      private static final long DEFAULT_COOL_DOWN_MS = 5000; // Adjust this value to match the default cooldown period in ExponentialBackoffManager
48  
49      @BeforeEach
50      public void setUp() {
51          connPerRoute = new MockConnPoolControl();
52          route = new HttpRoute(new HttpHost("localhost", 80));
53          impl = new ExponentialBackoffManager(connPerRoute);
54          impl.setPerHostConnectionCap(10);
55          impl.setCoolDown(TimeValue.ofMilliseconds(DEFAULT_COOL_DOWN_MS));
56          impl.setBackoffFactor(1.75); // Adjust this value to match the default growth rate in ExponentialBackoffManager
57      }
58  
59      @Test
60      public void exponentialBackoffApplied() {
61          connPerRoute.setMaxPerRoute(route, 4);
62          impl.setBackoffFactor(2); // Sets the growth rate to 2 for this test
63          impl.backOff(route);
64          assertEquals(1, connPerRoute.getMaxPerRoute(route)); // Corrected expected value
65      }
66  
67      @Test
68      public void exponentialGrowthRateIsConfigurable() {
69          final int customCoolDownMs = 500;
70          connPerRoute.setMaxPerRoute(route, 4);
71          impl.setBackoffFactor(0.5);
72          impl.setCoolDown(TimeValue.ofMilliseconds(customCoolDownMs));
73          impl.backOff(route);
74          assertEquals(2, connPerRoute.getMaxPerRoute(route));
75  
76          // Manipulate lastRouteBackoffs to simulate that enough time has passed for the cooldown period
77          final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
78          lastRouteBackoffs.put(route, Instant.now().minusMillis(customCoolDownMs + 1));
79  
80          impl.backOff(route);
81          assertEquals(1, connPerRoute.getMaxPerRoute(route));
82      }
83  
84      @Test
85      public void doesNotIncreaseBeyondPerHostMaxOnProbe() {
86          connPerRoute.setDefaultMaxPerRoute(5);
87          connPerRoute.setMaxPerRoute(route, 5);
88          impl.setPerHostConnectionCap(5);
89          impl.probe(route);
90          assertEquals(5, connPerRoute.getMaxPerRoute(route));
91      }
92  
93      @Test
94      public void backoffDoesNotAdjustDuringCoolDownPeriod() {
95          connPerRoute.setMaxPerRoute(route, 4);
96          impl.backOff(route);
97          final long max = connPerRoute.getMaxPerRoute(route);
98  
99          // Manipulate lastRouteBackoffs to simulate that not enough time has passed for the cooldown period
100         final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
101         lastRouteBackoffs.put(route, Instant.now().minusMillis(10));
102 
103         // Perform another backoff
104         impl.backOff(route);
105         assertEquals(max, connPerRoute.getMaxPerRoute(route));
106     }
107 
108     @Test
109     public void backoffStillAdjustsAfterCoolDownPeriod() {
110         connPerRoute.setMaxPerRoute(route, 8);
111         impl.backOff(route);
112         final long max = connPerRoute.getMaxPerRoute(route);
113 
114         // Manipulate lastRouteBackoffs to simulate that enough time has passed for the cooldown period
115         final Map<HttpRoute, Instant> lastRouteBackoffs = impl.getLastRouteBackoffs();
116         lastRouteBackoffs.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
117 
118         // Perform another backoff
119         impl.backOff(route);
120 
121         // Assert that the max connections have decreased
122         assertTrue(max == 1 || max > connPerRoute.getMaxPerRoute(route));
123     }
124 
125 
126     @Test
127     public void probeDoesNotAdjustDuringCooldownPeriod() {
128         connPerRoute.setMaxPerRoute(route, 4);
129         impl.probe(route);
130         final long max = connPerRoute.getMaxPerRoute(route);
131 
132         // Manipulate lastRouteProbes to simulate that not enough time has passed for the cooldown period
133         final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
134         lastRouteProbes.put(route, Instant.now().minusMillis(0));
135 
136         impl.probe(route);
137         assertEquals(max, connPerRoute.getMaxPerRoute(route));
138     }
139 
140     @Test
141     public void probeStillAdjustsAfterCoolDownPeriod() {
142         connPerRoute.setMaxPerRoute(route, 8);
143         impl.probe(route);
144         final long max = connPerRoute.getMaxPerRoute(route);
145 
146         // Manipulate lastRouteProbes to simulate that enough time has passed for the cooldown period
147         final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
148         lastRouteProbes.put(route, Instant.now().minusMillis(DEFAULT_COOL_DOWN_MS + 1));
149 
150         impl.probe(route);
151         assertTrue(max < connPerRoute.getMaxPerRoute(route));
152     }
153 
154     @Test
155     public void willBackoffImmediatelyEvenAfterAProbe() {
156         connPerRoute.setMaxPerRoute(route, 8);
157         impl.probe(route);
158         final long max = connPerRoute.getMaxPerRoute(route);
159         impl.backOff(route);
160         assertTrue(connPerRoute.getMaxPerRoute(route) < max);
161     }
162 
163     @Test
164     public void coolDownPeriodIsConfigurable() {
165         final long cd = 500; // Fixed cooldown period of 500 milliseconds
166         impl.setCoolDown(TimeValue.ofMilliseconds(cd));
167         connPerRoute.setMaxPerRoute(route, 4);
168         impl.probe(route);
169         final int max0 = connPerRoute.getMaxPerRoute(route);
170 
171         // Manipulate lastRouteProbes to simulate that not enough time has passed for the cooldown period
172         final Map<HttpRoute, Instant> lastRouteProbes = impl.getLastRouteProbes();
173         lastRouteProbes.put(route, Instant.now().minusMillis(cd / 2));
174 
175         impl.probe(route);
176         assertEquals(max0, connPerRoute.getMaxPerRoute(route));
177 
178         // Manipulate lastRouteProbes again to simulate that enough time has passed for the cooldown period
179         lastRouteProbes.put(route, Instant.now().minusMillis(cd + 1));
180 
181         impl.probe(route);
182         assertTrue(max0 < connPerRoute.getMaxPerRoute(route));
183     }
184 
185 }